Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix observation sync issues #236

Draft
wants to merge 30 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e2da608
Convert httpClient calls to @esri/arcgis-rest-request with ArcGISIden…
Oct 28, 2024
e40718f
Fix addFields and deleteFields
Oct 31, 2024
07986a6
Fixed outFields and returnGeometry for query generation
Nov 1, 2024
8592110
[service] prepend form name to all event form fields for clarity and …
newmanw Nov 1, 2024
feeea9e
[service] Fix check to determine if an observation sync to ESRI is a …
newmanw Nov 1, 2024
c38a342
Merge branch 'develop' into switch-httpClient-to-esri-library
newmanw Nov 1, 2024
041f96c
[service] OAuth refresh token flow in work
newmanw Nov 1, 2024
45b317b
[service] Add IdentityManager to ObservationSender construction
Nov 4, 2024
e66b6de
[service] Remove httpClient
Nov 4, 2024
7691ad7
[service] fix FeatureQuerier response from request
Nov 4, 2024
ed561a7
[service] Refactor ArcGISIdentityManager management
newmanw Nov 7, 2024
10b5444
draft changes
ryanslatten Nov 7, 2024
86fb706
Merge branch 'switch-httpClient-to-esri-library' of https://github.co…
ryanslatten Nov 7, 2024
157d209
[service] ArcGIS field names are lowercase, account for this when add…
newmanw Nov 7, 2024
6379dae
Undo revert of FeatureQuerier
ryanslatten Nov 8, 2024
a38cf9b
[service] Remove HttpClient.ts, no longer used
Nov 8, 2024
df19f92
[service] updateConfig inner async methods await before we query
Nov 8, 2024
46cdaf4
[server] Attribute sync fix upon adding feature server
Nov 9, 2024
f7cb430
[web/service] API update to include feature service authentication st…
newmanw Nov 13, 2024
c9fe35e
[service] fix regression in token, username/password validation req p…
newmanw Nov 13, 2024
a028aaf
[service] Clean up TODO comments
newmanw Nov 14, 2024
53d392e
[service] form field mapping need to account for form name and field …
newmanw Nov 14, 2024
75a3841
Reduce response logging that may cause confusion
ryanslatten Nov 14, 2024
4d37e6a
Merge branch 'switch-httpClient-to-esri-library' of https://github.co…
ryanslatten Nov 14, 2024
eb31a93
[service] include event forms in detecting config changes
Nov 15, 2024
20410f6
Merge develop
newmanw Nov 18, 2024
03b2297
Merge branch 'develop' into switch-httpClient-to-esri-library
newmanw Nov 18, 2024
5fefc3a
fix failing errors for observations
ryanslatten Nov 27, 2024
8db0006
Merge branch 'develop' of https://github.com/ngageoint/mage-server in…
ryanslatten Nov 27, 2024
4625296
fix issues with updates
ryanslatten Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/arcgis/service/src/FeatureLayerProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class FeatureLayerProcessor {

for (const arcObservation of observations.deletions) {
if (this.layerInfo.geometryType == arcObservation.esriGeometryType) {
this.sender.sendDelete(Number(arcObservation.id));
this.sender.sendDelete(arcObservation.id);
}
}
}
Expand Down
88 changes: 28 additions & 60 deletions plugins/arcgis/service/src/FeatureQuerier.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ArcGISPluginConfig } from "./ArcGISPluginConfig";
import { LayerInfo } from "./LayerInfo";
import { QueryObjectResult } from "./QueryObjectResult";
import { ArcGISIdentityManager, request } from "@esri/arcgis-rest-request";
import { ArcGISIdentityManager } from "@esri/arcgis-rest-request";
import { queryFeatures } from '@esri/arcgis-rest-feature-service';

/**
* Performs various queries on observations for a specific arc feature layer.
Expand Down Expand Up @@ -52,22 +53,17 @@ export class FeatureQuerier {
* @param geometry query the geometry, default is true
*/
async queryObservation(observationId: string, response: (result: QueryObjectResult) => void, fields?: string[], geometry?: boolean) {
const queryUrl = new URL(this._url)
if (this._config.eventIdField == null) {
queryUrl.searchParams.set('where', `${this._config.observationIdField} LIKE '${observationId}${this._config.idSeparator}%'`);
} else {
queryUrl.searchParams.set('where', `${this._config.observationIdField} = ${observationId}`);
}
queryUrl.searchParams.set('outFields', this.outFields(fields))
queryUrl.searchParams.set('returnGeometry', geometry === false ? 'false' : 'true')
this._console.info('ArcGIS query: ' + queryUrl)

const queryResponse = await request(queryUrl.toString(), {
const where = !this._config.eventIdField
? `${this._config.observationIdField} LIKE '${observationId}${this._config.idSeparator}%'`
: `${this._config.observationIdField} = '${observationId}'`;
this._console.info('ArcGIS query observation: ' + this._url.toString() + where);
await queryFeatures({
url: this._url.toString(),
authentication: this._identityManager,
params: { f: 'json' }
});

response(queryResponse as QueryObjectResult);
where,
returnGeometry: geometry,
outFields: fields?.length ? fields : '*'
}).then((queryResponse) => response(queryResponse as QueryObjectResult)).catch((error) => this._console.error('Error in FeatureQuerier.queryObservation :: ' + error));
}

/**
Expand All @@ -77,19 +73,14 @@ export class FeatureQuerier {
* @param geometry query the geometry, default is true
*/
async queryObservations(response: (result: QueryObjectResult) => void, fields?: string[], geometry?: boolean) {
const queryUrl = new URL(this._url)
queryUrl.searchParams.set('where', `${this._config.observationIdField} IS NOT NULL`);
queryUrl.searchParams.set('outFields', this.outFields(fields));
queryUrl.searchParams.set('returnGeometry', geometry === false ? 'false' : 'true');

this._console.info('ArcGIS query: ' + queryUrl)

const queryResponse = await request(queryUrl.toString(), {
this._console.info('ArcGIS query observation: ' + this._url.toString());
await queryFeatures({
url: this._url.toString(),
authentication: this._identityManager,
params: { f: 'json' }
});

response(queryResponse as QueryObjectResult);
where: `${this._config.observationIdField} IS NOT NULL`,
returnGeometry: geometry,
outFields: fields?.length ? fields : '*'
}).then((queryResponse) => response(queryResponse as QueryObjectResult)).catch((error) => this._console.error('Error in FeatureQuerier.queryObservations :: ' + error));
}

/**
Expand All @@ -98,37 +89,14 @@ export class FeatureQuerier {
* @param field field to query
*/
async queryDistinct(response: (result: QueryObjectResult) => void, field: string) {
const queryUrl = new URL(this._url);
queryUrl.searchParams.set('where', `${field} IS NOT NULL`);
queryUrl.searchParams.set('returnDistinctValues', 'true');
queryUrl.searchParams.set('outFields', this.outFields([field]));
queryUrl.searchParams.set('returnGeometry', 'false');
this._console.info('ArcGIS query: ' + queryUrl)

try {
const queryResponse = await request(queryUrl.toString(), {
authentication: this._identityManager,
params: { f: 'json' }

});

response(queryResponse as QueryObjectResult);
} catch (err) {
console.error("could not query", err)
}
}

/**
* Build the out fields query parameter
* @param fields query fields
* @returns out fields
*/
private outFields(fields?: string[]): string {
if (fields != null && fields.length > 0) {
return fields.join(',');
} else {
return '*';
}
this._console.info('ArcGIS query observation: ' + this._url.toString());
await queryFeatures({
url: this._url.toString(),
authentication: this._identityManager,
where: `${field} IS NOT NULL`,
returnGeometry: false,
outFields: field ? [field] : '*',
returnDistinctValues: true
}).then((queryResponse) => response(queryResponse as QueryObjectResult)).catch((error) => this._console.error('Error in FeatureQuerier.queryDistinct :: ' + error));
}

}
3 changes: 2 additions & 1 deletion plugins/arcgis/service/src/ObservationBinner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export class ObservationBinner {
const bins = new ObservationBins();

for (const arcObservation of observations.observations) {
if (arcObservation.lastModified != arcObservation.createdAt) {
// TODO: Would probably want a better way to determine which observations need to be updated in arcgis
if (observations.firstRun || arcObservation.lastModified != arcObservation.createdAt) {
bins.updates.add(arcObservation);
} else if (!this._addedObs.has(arcObservation.id)) {
bins.adds.add(arcObservation);
Expand Down
2 changes: 1 addition & 1 deletion plugins/arcgis/service/src/ObservationProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ export class ObservationProcessor {
}
arcObjects.firstRun = this._firstRun;
for (const layerProcessor of layerProcessors) {
layerProcessor.processArcObjects(JSON.parse(JSON.stringify(arcObjects)));
layerProcessor.processArcObjects(arcObjects);
}
newNumberLeft -= latestObs.items.length;
pagingSettings.pageIndex++;
Expand Down
86 changes: 24 additions & 62 deletions plugins/arcgis/service/src/ObservationsSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import { AttachmentInfosResult, AttachmentInfo } from './AttachmentInfosResult';
import environment from '@ngageoint/mage.service/lib/environment/env'
import fs from 'fs'
import path from 'path'
import FormData from 'form-data';
import { ArcGISIdentityManager, IFeature, request } from "@esri/arcgis-rest-request"
import { addFeatures, updateFeatures, deleteFeatures, getAttachments, updateAttachment, addAttachment, deleteAttachments } from "@esri/arcgis-rest-feature-service";
import { addFeatures, updateFeatures, getAttachments, updateAttachment, addAttachment, deleteAttachments } from "@esri/arcgis-rest-feature-service";

/**
* Class that transforms observations into a json string that can then be sent to an arcgis server.
Expand All @@ -21,16 +20,6 @@ export class ObservationsSender {
*/
private _url: string;

/**
* The full url to the feature layer receiving observations.
*/
private _urlAdd: string;

/**
* The full url to the feature layer receiving updates.
*/
private _urlUpdate: string;

/**
* Used to log to the console.
*/
Expand All @@ -56,8 +45,6 @@ export class ObservationsSender {
*/
constructor(layerInfo: LayerInfo, config: ArcGISPluginConfig, identityManager: ArcGISIdentityManager, console: Console) {
this._url = layerInfo.url;
this._urlAdd = this._url + '/addFeatures';
this._urlUpdate = this._url + '/updateFeatures';
this._console = console;
this._attachmentDirectory = environment.attachmentBaseDirectory;
this._config = config;
Expand All @@ -70,14 +57,13 @@ export class ObservationsSender {
* @param observations The observations to convert.
*/
sendAdds(observations: ArcObjects) {
this._console.info('ArcGIS addFeatures url ' + this._urlAdd);
this._console.info('ArcGIS addFeatures');

let responseHandler = this.addResponseHandler(observations);
addFeatures({
url: this._url,
authentication: this._identityManager,
features: observations.objects as IFeature[]
}).then(responseHandler).catch((error) => this._console.error(error));
}).then(this.responseHandler(observations)).catch((error) => this._console.error(error));
}

/**
Expand All @@ -87,68 +73,47 @@ export class ObservationsSender {
* @returns The json string of the observations.
*/
sendUpdates(observations: ArcObjects) {
this._console.info('ArcGIS updateFeatures url ' + this._urlUpdate);
this._console.info('ArcGIS updateFeatures');

let responseHandler = this.updateResponseHandler(observations);
updateFeatures({
url: this._url,
authentication: this._identityManager,
features: observations.objects as IFeature[]
}).then(responseHandler).catch((error) => this._console.error(error));
}).then(this.responseHandler(observations, true)).catch((error) => this._console.error(error));
}

/**
* Delete an observation.
* @param id The observation id.
*/
sendDelete(id: number) {
const url = this._url + '/deleteFeatures'
this._console.info('ArcGIS deleteFeatures url ' + url + ', ' + this._config.observationIdField + ': ' + id)
sendDelete(id: string) {
this._console.info('ArcGIS deleteFeatures id: ' + id)

deleteFeatures({
url,
request(`${this._url}/deleteFeatures`, {
httpMethod: 'POST',
authentication: this._identityManager,
objectIds: [id]
}).catch((error) => this._console.error(error));
params: {
where: `${this._config.observationIdField} LIKE \'%${id}\'`
}
}).catch((error) => this._console.error('Error in ObservationSender.sendDelete :: ' + error));
}

/**
* Deletes all observations that are apart of a specified event.
* @param id The event id.
*/
sendDeleteEvent(id: number) {
this._console.info('ArcGIS deleteFeatures by event ' + this._config.observationIdField + ': ' + id);

const url = this._url + '/deleteFeatures'

this._console.info('ArcGIS deleteFeatures by event url ' + url + ', ' + this._config.observationIdField + ': ' + id)

const form = new FormData()

if (this._config.eventIdField == null) {
form.append('where', this._config.observationIdField + ' LIKE\'%' + this._config.idSeparator + id + '\'')
} else {
form.append('where', this._config.eventIdField + '=' + id)
}

this.sendDelete(id);
}

/**
* Creates an add observation response handler.
* @param observations The observations sent.
* @returns The response handler.
*/
private addResponseHandler(observations: ArcObjects): (chunk: any) => void {
return this.responseHandler(observations, false)
}

/**
* Creates an update observation response handler.
* @param observations The observations sent.
* @returns The response handler.
*/
private updateResponseHandler(observations: ArcObjects): (chunk: any) => void {
return this.responseHandler(observations, true)
request(`${this._url}/deleteFeatures`, {
httpMethod: 'POST',
authentication: this._identityManager,
params: {
where: !!this._config.eventIdField
? this._config.eventIdField + '=' + id
: this._config.observationIdField + ' LIKE\'%' + this._config.idSeparator + id + '\''
}
}).catch((error) => this._console.error('Error in ObservationSender.sendDeleteEvent :: ' + error));
}

/**
Expand All @@ -157,7 +122,7 @@ export class ObservationsSender {
* @param update The update or add flag value.
* @returns The response handler.
*/
private responseHandler(observations: ArcObjects, update: boolean): (chunk: any) => void {
private responseHandler(observations: ArcObjects, update?: boolean): (chunk: any) => void {
const console = this._console
return (chunk: any) => {
console.log('ArcGIS ' + (update ? 'Update' : 'Add') + ' Response: ' + JSON.stringify(chunk))
Expand Down Expand Up @@ -207,7 +172,6 @@ export class ObservationsSender {
*/
private queryAndUpdateAttachments(observation: ArcObservation, objectId: number) {
// Query for existing attachments
const queryUrl = this._url + '/' + objectId + '/attachments'
getAttachments({
url: this._url,
authentication: this._identityManager,
Expand All @@ -225,7 +189,6 @@ export class ObservationsSender {
* @param attachmentInfos The arc attachment infos.
*/
private updateAttachments(observation: ArcObservation, objectId: number, attachmentInfos: AttachmentInfo[]) {

// Build a mapping between existing arc attachment names and the attachment infos
let nameAttachments = new Map<string, AttachmentInfo>()
if (attachmentInfos != null) {
Expand Down Expand Up @@ -319,7 +282,6 @@ export class ObservationsSender {
* @param attachmentInfos The arc attachment infos.
*/
private deleteAttachments(objectId: number, attachmentInfos: AttachmentInfo[]) {

const attachmentIds: number[] = []

for (const attachmentInfo of attachmentInfos) {
Expand Down
Loading