@ngageoint/mage.arcgis.service
Version:
A mage service plugin that synchronizes mage observations to a configured ArcGIS feature layer.
465 lines • 19 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FeatureServiceAdmin = void 0;
const entities_events_forms_1 = require("@ngageoint/mage.service/lib/entities/events/entities.events.forms");
const ObservationsTransformer_1 = require("./ObservationsTransformer");
const form_data_1 = __importDefault(require("form-data"));
const arcgis_rest_request_1 = require("@esri/arcgis-rest-request");
/**
* Administers hosted feature services such as layer creation and updates.
*/
class FeatureServiceAdmin {
/**
* Constructor.
* @param {ArcGISPluginConfig} config The plugins configuration.
* @param {ArcGISIdentityService} identityService The identity service.
* @param {Console} console Used to log to the console.
*/
constructor(config, identityService, console) {
this._config = config;
this._identityService = identityService;
this._console = console;
}
/**
* Create the layer
* @param {FeatureServiceConfig} service feature service
* @param {FeatureLayerConfig} featureLayer feature layer
* @param {number} nextId next service layer id
* @param {MageEventRepository} eventRepo event repository
* @returns {Promise<number>} layer id
*/
createLayer(service, featureLayer, nextId, eventRepo) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Is this function needed? This allows the server to create a feature layer on the arcgis server.
this._console.info('FeatureServiceAdmin createLayer()');
const layer = { type: 'Feature Layer' };
const layerIdentifier = featureLayer.layer;
const layerIdentifierNumber = Number(layerIdentifier);
if (isNaN(layerIdentifierNumber)) {
layer.name = String(layerIdentifier);
layer.id = nextId;
}
else {
layer.id = layerIdentifierNumber;
}
const events = yield this.layerEvents(featureLayer, eventRepo);
if (layer.name == null) {
layer.name = this.layerName(events);
}
if (featureLayer.geometryType != null) {
layer.geometryType = featureLayer.geometryType;
}
else {
layer.geometryType = 'esriGeometryPoint';
}
layer.fields = this.fields(events);
// TODO What other layer properties are needed or required?
// https://developers.arcgis.com/rest/services-reference/online/add-to-definition-feature-service-.htm#GUID-63F2BD08-DCF4-485D-A3E6-C7116E17DDD8
this.create(service, layer);
return layer.id;
});
}
/**
* Update the layer fields
* @param {FeatureServiceConfig} service feature service
* @param {FeatureLayerConfig} featureLayer feature layer
* @param {LayerInfoResult} layerInfo layer info
* @param {MageEventRepository} eventRepo event repository
*/
updateLayer(service, featureLayer, layerInfo, eventRepo) {
return __awaiter(this, void 0, void 0, function* () {
this._console.info('FeatureServiceAdmin updateLayer()');
const events = yield this.layerEvents(featureLayer, eventRepo);
const promises = [];
const eventFields = this.fields(events);
const layerFields = layerInfo.fields;
const layerFieldSet = new Set();
for (const field of layerFields) {
layerFieldSet.add(field.name);
}
const addFields = [];
for (const field of eventFields) {
if (!layerFieldSet.has(field.name)) {
addFields.push(field);
const layerField = {};
layerField.name = field.name;
layerField.editable = true;
layerFields.push(layerField);
}
}
if (addFields.length > 0) {
promises.push(this.addFields(service, layerInfo.id, addFields));
}
const eventFieldSet = new Set();
for (const field of eventFields) {
eventFieldSet.add(field.name);
}
const deleteFields = [];
const remainingFields = [];
for (const field of layerFields) {
if (field.editable && !eventFieldSet.has(field.name)) {
deleteFields.push(field);
}
else {
remainingFields.push(field);
}
}
if (deleteFields.length > 0) {
layerInfo.fields = remainingFields;
promises.push(this.deleteFields(service, layerInfo.id, deleteFields));
}
yield Promise.all(promises);
return eventFields;
});
}
/**
* Get the Mage layer events
* @param {FeatureLayerConfig} layer feature layer
* @param {MageEventRepository} eventRepo event repository
* @returns {Promise<MageEvent[]>} Mage layer events
*/
layerEvents(layer, eventRepo) {
return __awaiter(this, void 0, void 0, function* () {
const layerEventIds = new Set();
if (layer.eventIds != null) {
for (const layerEventId of layer.eventIds) {
layerEventIds.add(layerEventId);
}
}
let mageEvents;
if (layerEventIds.size > 0) {
mageEvents = yield eventRepo.findAll();
}
else {
mageEvents = yield eventRepo.findActiveEvents();
}
const events = [];
for (const mageEvent of mageEvents) {
if (layerEventIds.size === 0 || layerEventIds.has(mageEvent.id)) {
const event = yield eventRepo.findById(mageEvent.id);
if (event != null) {
events.push(event);
}
}
}
return events;
});
}
/**
* Create a layer name
* @param {MageEvent[]} events layer events
* @returns {string} layer name
*/
layerName(events) {
let layerName = '';
for (let i = 0; i < events.length; i++) {
if (i > 0) {
layerName += ', ';
}
layerName += events[i].name;
}
return layerName;
}
/**
* Builder the layer fields
* @param {MageEvent[]} events layer events
* @returns {Field[]} fields
*/
fields(events) {
const fields = [];
fields.push(this.createTextField(this._config.observationIdField));
if (this._config.eventIdField != null) {
fields.push(this.createIntegerField(this._config.eventIdField));
}
if (this._config.eventNameField != null) {
fields.push(this.createTextField(this._config.eventNameField));
}
if (this._config.userIdField != null) {
fields.push(this.createTextField(this._config.userIdField));
}
if (this._config.usernameField != null) {
fields.push(this.createTextField(this._config.usernameField));
}
if (this._config.userDisplayNameField != null) {
fields.push(this.createTextField(this._config.userDisplayNameField));
}
if (this._config.deviceIdField != null) {
fields.push(this.createTextField(this._config.deviceIdField));
}
if (this._config.createdAtField != null) {
fields.push(this.createDateTimeField(this._config.createdAtField));
}
if (this._config.lastModifiedField != null) {
fields.push(this.createDateTimeField(this._config.lastModifiedField));
}
if (this._config.geometryType != null) {
fields.push(this.createTextField(this._config.geometryType));
}
const fieldNames = new Set();
for (const field of fields) {
fieldNames.add(field.name);
}
this.eventsFields(events, fields, fieldNames);
return fields;
}
/**
* Create a field
* @param {string} name field name
* @param {FormFieldType} type form field type
* @param {boolean} [integer] integer flag when numeric
* @returns {Field} field
*/
createField(name, type, integer) {
const field = this.initField(type, integer);
if (field != null) {
field.name = ObservationsTransformer_1.ObservationsTransformer.replaceSpaces(name);
field.alias = field.name;
field.editable = true;
}
return field;
}
/**
* Create a text field
* @param {string} name field name
* @returns {Field} field
*/
createTextField(name) {
return this.createField(name, entities_events_forms_1.FormFieldType.Text);
}
/**
* Create a numeric field
* @param {string} name field name
* @param {boolean} [integer] integer flag
* @returns {Field} field
*/
createNumericField(name, integer) {
return this.createField(name, entities_events_forms_1.FormFieldType.Numeric, integer);
}
/**
* Create an integer field
* @param {string} name field name
* @returns {Field} field
*/
createIntegerField(name) {
return this.createNumericField(name, true);
}
/**
* Create a date time field
* @param {string} name field name
* @returns {Field} field
*/
createDateTimeField(name) {
return this.createField(name, entities_events_forms_1.FormFieldType.DateTime);
}
/**
* Build fields from the layer events
* @param {MageEvent[]} events layer events
* @param {Field[]} fields created fields
* @param {Set<string>} fieldNames set of all field names
*/
eventsFields(events, fields, fieldNames) {
const forms = new Set();
for (const event of events) {
this.eventFields(event, forms, fields, fieldNames);
}
}
/**
* Build fields from the layer event
* @param {MageEvent} event layer event
* @param {Set<FormId>} forms set of processed forms
* @param {Field[]} fields created fields
* @param {Set<string>} fieldNames set of all field names
*/
eventFields(event, forms, fields, fieldNames) {
for (const form of event.activeForms) {
if (!forms.has(form.id)) {
forms.add(form.id);
for (const formField of form.fields) {
if (!formField.archived) {
this.createFormField(form, formField, fields, fieldNames);
}
}
}
}
}
/**
* Build a field from the form field
* @param {Form} form form
* @param {FormField} formField form field
* @param {Field[]} fields created fields
* @param {Set<string>} fieldNames set of all field names
*/
createFormField(form, formField, fields, fieldNames) {
const field = this.initField(formField.type);
if (field != null) {
const sanitizedName = ObservationsTransformer_1.ObservationsTransformer.replaceSpaces(formField.title);
const sanitizedFormName = ObservationsTransformer_1.ObservationsTransformer.replaceSpaces(form.name);
const name = `${sanitizedFormName}_${sanitizedName}`.toLowerCase();
fieldNames.add(name);
field.name = name;
field.alias = field.name;
field.editable = true;
field.defaultValue = formField.value;
fields.push(field);
}
}
/**
* Initialize a field by type
* @param {FormFieldType} type form field type
* @param {boolean} [integer] numeric integer field type
* @returns {Field | null} field or null
*/
initField(type, integer) {
const field = {};
switch (type) {
case entities_events_forms_1.FormFieldType.CheckBox:
case entities_events_forms_1.FormFieldType.Dropdown:
case entities_events_forms_1.FormFieldType.Email:
case entities_events_forms_1.FormFieldType.MultiSelectDropdown:
case entities_events_forms_1.FormFieldType.Password:
case entities_events_forms_1.FormFieldType.Radio:
case entities_events_forms_1.FormFieldType.Text:
field.type = 'esriFieldTypeString';
field.actualType = 'nvarchar';
field.sqlType = 'sqlTypeNVarchar';
field.length = this._config.textFieldLength;
break;
case entities_events_forms_1.FormFieldType.TextArea:
field.type = 'esriFieldTypeString';
field.actualType = 'nvarchar';
field.sqlType = 'sqlTypeNVarchar';
field.length = this._config.textAreaFieldLength;
break;
case entities_events_forms_1.FormFieldType.DateTime:
field.type = 'esriFieldTypeDate';
field.sqlType = 'sqlTypeOther';
field.length = 10;
break;
case entities_events_forms_1.FormFieldType.Numeric:
if (integer) {
field.type = 'esriFieldTypeInteger';
field.actualType = 'int';
field.sqlType = 'sqlTypeInteger';
}
else {
field.type = 'esriFieldTypeDouble';
field.actualType = 'float';
field.sqlType = 'sqlTypeFloat';
}
break;
case entities_events_forms_1.FormFieldType.Geometry:
case entities_events_forms_1.FormFieldType.Attachment:
case entities_events_forms_1.FormFieldType.Hidden:
default:
break;
}
return field.type != null ? field : null;
}
/**
* Create the feature layer
* @param {FeatureServiceConfig} service feature service
* @param {Layer} layer layer
*/
create(service, layer) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.adminUrl(service) + 'addToDefinition';
this._console.info('ArcGIS feature service addToDefinition (create feature layer) url ' + url);
const form = new form_data_1.default();
form.append('addToDefinition', JSON.stringify(layer));
const identityManager = yield this._identityService.signin(service);
yield (0, arcgis_rest_request_1.request)(url, {
authentication: identityManager,
httpMethod: 'POST',
params: form
}).catch((error) => this._console.error('FeatureServiceAdmin create() error ' + error));
});
}
/**
* Add fields to the layer
* @param {FeatureLayerConfig} service feature service
* @param {FeatureLayerConfig} featureLayer feature layer
* @param {Field[]} fields fields to add
*/
addFields(service, featureLayer, fields) {
return __awaiter(this, void 0, void 0, function* () {
const layer = { fields: fields };
const url = this.adminUrl(service) + featureLayer.toString() + '/addToDefinition';
this._console.info('ArcGIS feature layer addToDefinition (add fields) url ' + url);
try {
const identityManager = yield this._identityService.signin(service);
yield (0, arcgis_rest_request_1.request)(url, {
authentication: identityManager,
params: {
addToDefinition: layer,
f: "json"
}
}).catch((error) => {
this._console.error('Error in addFields: ' + error);
});
}
catch (error) {
this._console.error('FeatureServiceAdmin addFields() error ' + error);
}
});
}
/**
* Delete fields from the layer
* @param {FeatureServiceConfig} service feature service
* @param {FeatureLayerConfig} featureLayer feature layer
* @param {LayerField[]} fields fields to delete
*/
deleteFields(service, featureLayer, fields) {
return __awaiter(this, void 0, void 0, function* () {
const deleteFields = [];
for (const layerField of fields) {
const field = {};
field.name = layerField.name;
deleteFields.push(field);
}
const layer = {};
layer.fields = deleteFields;
const url = this.adminUrl(service) + featureLayer.toString() + '/deleteFromDefinition';
this._console.info('ArcGIS feature layer deleteFromDefinition (delete fields) url ' + url);
try {
const identityManager = yield this._identityService.signin(service);
yield (0, arcgis_rest_request_1.request)(url, {
authentication: identityManager,
httpMethod: 'POST',
params: {
deleteFromDefinition: layer,
f: 'json'
}
}).catch((error) => this._console.error('FeatureServiceAdmin deleteFields() error ' + error));
}
catch (error) {
this._console.error('FeatureServiceAdmin deleteFields() error ' + error);
}
});
}
/**
* Get the administration url
* @param {FeatureServiceConfig} service feature service
* @returns {string} url
*/
adminUrl(service) {
let url = service.url.replace('/services/', '/admin/services/');
if (!url.endsWith('/')) {
url += '/';
}
return url;
}
}
exports.FeatureServiceAdmin = FeatureServiceAdmin;
//# sourceMappingURL=FeatureServiceAdmin.js.map