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

[service] sync configured events instead of checking changes of all e… #235

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 8 additions & 6 deletions plugins/arcgis/service/src/ArcGISConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MageEventId } from "@ngageoint/mage.service/lib/entities/events/entities.events"

/**
* Contains an arc feature service url and layers.
*/
Expand All @@ -8,10 +10,10 @@ export interface FeatureServiceConfig {
*/
url: string

/**
* Serialized ArcGISIdentityManager
*/
identityManager: string
/**
* Serialized ArcGISIdentityManager
*/
identityManager: string

/**
* The feature layers.
Expand All @@ -35,9 +37,9 @@ export interface FeatureLayerConfig {
geometryType?: string

/**
* The event ids or names that sync to this arc feature layer.
* The event ids that sync to this arc feature layer.
*/
events?: (number|string)[]
eventIds?: MageEventId[]
}


Expand Down
6 changes: 5 additions & 1 deletion plugins/arcgis/service/src/EventDeletionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class EventDeletionHandler {
this._config = config;
}

public updateConfig(newConfig: ArcGISPluginConfig): void {
this._config = newConfig;
}

/**
*
* @param activeEvents The current set of active events.
Expand Down Expand Up @@ -83,7 +87,7 @@ export class EventDeletionHandler {
}

/**
* Called when the query is finished. It goes through the results and gathers all even Ids currently stored
* Called when the query is finished. It goes through the results and gathers all event Ids currently stored
* in the arc layer. It then will remove any events from the arc layer that do not exist.
* @param layerProcessor The feature layer processor.
* @param result The returned results.
Expand Down
2 changes: 1 addition & 1 deletion plugins/arcgis/service/src/EventLayerProcessorOrganizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class EventLayerProcessorOrganizer {
for (const event of events) {
let syncProcessors = new Array<FeatureLayerProcessor>();
for (const layerProcessor of layerProcessors) {
if (layerProcessor.layerInfo.hasEvent(event.name)) {
if (layerProcessor.layerInfo.hasEvent(event.id)) {
syncProcessors.push(layerProcessor);
}
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/arcgis/service/src/FeatureQuerier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class FeatureQuerier {
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('where', `${this._config.observationIdField} = '${observationId}'`);
}
queryUrl.searchParams.set('outFields', this.outFields(fields))
queryUrl.searchParams.set('returnGeometry', geometry === false ? 'false' : 'true')
Expand Down
18 changes: 9 additions & 9 deletions plugins/arcgis/service/src/FeatureServiceAdmin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArcGISPluginConfig } from "./ArcGISPluginConfig"
import { FeatureServiceConfig, FeatureLayerConfig } from "./ArcGISConfig"
import { MageEvent, MageEventRepository } from '@ngageoint/mage.service/lib/entities/events/entities.events'
import { MageEvent, MageEventId, MageEventRepository } from '@ngageoint/mage.service/lib/entities/events/entities.events'
import { Layer, Field } from "./AddLayersRequest"
import { Form, FormField, FormFieldType, FormId } from '@ngageoint/mage.service/lib/entities/events/entities.events.forms'
import { ObservationsTransformer } from "./ObservationsTransformer"
Expand Down Expand Up @@ -120,29 +120,29 @@ export class FeatureServiceAdmin {
}

/**
* Get the layer events
* Get the Mage layer events
* @param layer feature layer
* @param eventRepo event repository
* @returns layer events
* @returns Mage layer events
*/
private async layerEvents(layer: FeatureLayerConfig, eventRepo: MageEventRepository): Promise<MageEvent[]> {
const layerEvents: Set<number|string> = new Set()
if (layer.events != null) {
for (const layerEvent of layer.events) {
layerEvents.add(layerEvent)
const layerEventIds: Set<MageEventId> = new Set()
if (layer.eventIds != null) {
for (const layerEventId of layer.eventIds) {
layerEventIds.add(layerEventId)
}
}

let mageEvents
if (layerEvents.size > 0) {
if (layerEventIds.size > 0) {
mageEvents = await eventRepo.findAll()
} else {
mageEvents = await eventRepo.findActiveEvents()
}

const events: MageEvent[] = []
for (const mageEvent of mageEvents) {
if (layerEvents.size == 0 || layerEvents.has(mageEvent.name) || layerEvents.has(mageEvent.id)) {
if (layerEventIds.size == 0 || layerEventIds.has(mageEvent.id)) {
const event = await eventRepo.findById(mageEvent.id)
if (event != null) {
events.push(event)
Expand Down
17 changes: 8 additions & 9 deletions plugins/arcgis/service/src/LayerInfo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MageEventId } from "@ngageoint/mage.service/lib/entities/events/entities.events";
import { LayerInfoResult, LayerField } from "./LayerInfoResult";

/**
Expand Down Expand Up @@ -28,7 +29,7 @@ export class LayerInfo {
/**
* The events that are synching to this layer.
*/
events: Set<string> = new Set<string>()
events: Set<MageEventId> = new Set<MageEventId>()

/**
* Constructor.
Expand All @@ -37,12 +38,10 @@ export class LayerInfo {
* @param layerInfo The layer info.
* @param token The access token.
*/
constructor(url: string, events: string[], layerInfo: LayerInfoResult) {
constructor(url: string, events: MageEventId[], layerInfo: LayerInfoResult) {
this.url = url
if (events != undefined && events != null && events.length == 0) {
this.events.add('nothing to sync')
}
if (events != undefined || events != null) {

if (events && events.length > 0) {
for (const event of events) {
this.events.add(event);
}
Expand All @@ -69,11 +68,11 @@ export class LayerInfo {

/**
* Determine if the layer is enabled for the event.
* @param event The event.
* @param eventId The event.
* @return true if enabled
*/
hasEvent(event: string) {
return this.events.size == 0 || this.events.has(event)
hasEvent(eventId: MageEventId) {
return this.events.size == 0 || this.events.has(eventId)
}

}
52 changes: 21 additions & 31 deletions plugins/arcgis/service/src/ObservationProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PagingParameters } from '@ngageoint/mage.service/lib/entities/entities.global';
import { MageEventId } from "@ngageoint/mage.service/lib/entities/events/entities.events";
import { MageEventRepository } from '@ngageoint/mage.service/lib/entities/events/entities.events';
import { EventScopedObservationRepository, ObservationRepositoryForEvent } from '@ngageoint/mage.service/lib/entities/observations/entities.observations';
import { UserRepository } from '@ngageoint/mage.service/lib/entities/users/entities.users';
Expand Down Expand Up @@ -164,19 +165,23 @@ export class ObservationProcessor {
private async updateConfig(): Promise<ArcGISPluginConfig> {
const config = await this.safeGetConfig()

// Include form definitions while detecting changes in config
const eventForms = await this._eventRepo.findAll();
// Include configured eventform definitions while detecting changes in config
const eventIds = config.featureServices
.flatMap(service => service.layers)
.flatMap(layer => layer.eventIds)
.filter((eventId): eventId is MageEventId => typeof eventId === 'number');

const eventForms = await this._eventRepo.findAllByIds(eventIds);
const fullConfig = { ...config, eventForms };

const configJson = JSON.stringify(fullConfig)
if (this._previousConfig == null || this._previousConfig != configJson) {
this._transformer = new ObservationsTransformer(config, console);
this._geometryChangeHandler = new GeometryChangedHandler(this._transformer);
this._eventDeletionHandler = new EventDeletionHandler(this._console, config);
this._eventDeletionHandler.updateConfig(config);
this._layerProcessors = [];
await this.getFeatureServiceLayers(config);
this._previousConfig = configJson
this._firstRun = true;
}
return config
}
Expand All @@ -186,7 +191,6 @@ export class ObservationProcessor {
*/
async start() {
this._isRunning = true;
this._firstRun = true;
this.processAndScheduleNext();
}

Expand All @@ -207,7 +211,7 @@ export class ObservationProcessor {
try {
const identityManager = await this._identityService.signin(service)
const response = await request(service.url, { authentication: identityManager })
this.handleFeatureService(response, service, config)
await this.handleFeatureService(response, service, config)
} catch (err) {
console.error(err)
}
Expand Down Expand Up @@ -235,25 +239,7 @@ export class ObservationProcessor {
}

for (const featureLayer of featureServiceConfig.layers) {
const eventNames: string[] = []
const events = featureLayer.events
if (events != null) {
for (const event of events) {
const eventId = Number(event);
if (isNaN(eventId)) {
eventNames.push(String(event));
} else {
const mageEvent = await this._eventRepo.findById(eventId)
if (mageEvent != null) {
eventNames.push(mageEvent.name);
}
}
}
}
if (eventNames.length > 0) {
featureLayer.events = eventNames
}

// TODO - this used to convert event ids to names and set back on featureLayer.events. What is impact of not doing?
const layer = serviceLayers.get(featureLayer.layer)

let layerId = undefined
Expand All @@ -270,7 +256,7 @@ export class ObservationProcessor {
const featureService = new FeatureService(console, featureServiceConfig, identityManager)
const layerInfo = await featureService.queryLayerInfo(layerId);
const url = `${featureServiceConfig.url}/${layerId}`;
this.handleLayerInfo(url, featureServiceConfig, featureLayer, layerInfo, config);
await this.handleLayerInfo(url, featureServiceConfig, featureLayer, layerInfo, config);
}
}
}
Expand All @@ -286,10 +272,10 @@ export class ObservationProcessor {
*/
private async handleLayerInfo(url: string, featureServiceConfig: FeatureServiceConfig, featureLayer: FeatureLayerConfig, layerInfo: LayerInfoResult, config: ArcGISPluginConfig) {
if (layerInfo.geometryType != null) {
const events = featureLayer.events as string[]
const admin = new FeatureServiceAdmin(config, this._identityService, this._console)
const eventIds = featureLayer.eventIds || []
await admin.updateLayer(featureServiceConfig, featureLayer, layerInfo, this._eventRepo)
const info = new LayerInfo(url, events, layerInfo)
const info = new LayerInfo(url, eventIds, layerInfo)
const identityManager = await this._identityService.signin(featureServiceConfig)
const layerProcessor = new FeatureLayerProcessor(info, config, identityManager, this._console);
this._layerProcessors.push(layerProcessor);
Expand All @@ -310,9 +296,13 @@ export class ObservationProcessor {
layerProcessor.processPendingUpdates();
}
this._console.info('ArcGIS plugin processing new observations...');
const activeEvents = await this._eventRepo.findActiveEvents();
this._eventDeletionHandler.checkForEventDeletion(activeEvents, this._layerProcessors, this._firstRun);
const eventsToProcessors = this._organizer.organize(activeEvents, this._layerProcessors);
const enabledEvents = (await this._eventRepo.findActiveEvents()).filter(event =>
this._layerProcessors.some(layerProcessor =>
layerProcessor.layerInfo.hasEvent(event.id)
)
);
this._eventDeletionHandler.checkForEventDeletion(enabledEvents, this._layerProcessors, this._firstRun);
const eventsToProcessors = this._organizer.organize(enabledEvents, this._layerProcessors);
const nextQueryTime = Date.now();
for (const pair of eventsToProcessors) {
this._console.info('ArcGIS getting newest observations for event ' + pair.event.name);
Expand Down
52 changes: 43 additions & 9 deletions plugins/arcgis/service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { ObservationRepositoryToken } from '@ngageoint/mage.service/lib/plugins.
import { MageEventRepositoryToken } from '@ngageoint/mage.service/lib/plugins.api/plugins.api.events'
import { UserRepositoryToken } from '@ngageoint/mage.service/lib/plugins.api/plugins.api.users'
import { SettingPermission } from '@ngageoint/mage.service/lib/entities/authorization/entities.permissions'
import { MageEventId } from '@ngageoint/mage.service/lib/entities/events/entities.events'
import { ObservationProcessor } from './ObservationProcessor'
import { ArcGISIdentityManager, request } from "@esri/arcgis-rest-request"
import { FeatureServiceConfig } from './ArcGISConfig'
import { FeatureServiceConfig, FeatureLayerConfig } from './ArcGISConfig'
import { URL } from "node:url"
import express from 'express'
import { ArcGISIdentityService, createArcGISIdentityService, getPortalUrl } from './ArcGISService'
Expand Down Expand Up @@ -166,16 +167,49 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
const config = await stateRepo.get()
const { featureServices: updatedServices, ...updateConfig } = req.body

// Map exisiting identityManager, client does not send this
const featureServices: FeatureServiceConfig[] = updatedServices.map((updateService: FeatureServiceConfig) => {
const existingService = config.featureServices.find((featureService: FeatureServiceConfig) => featureService.url === updateService.url)

// Convert event names to event IDs
// Fetch all events and create a mapping of event names to event IDs
const allEvents = await eventRepo.findAll();
const eventNameToIdMap = new Map<string, MageEventId>();
allEvents.forEach(event => {
eventNameToIdMap.set(event.name, event.id);
});

// Process the incoming feature services with eventIds instead of event names
const featureServices: FeatureServiceConfig[] = updatedServices.map((updateService: any) => {
const existingService = config.featureServices.find(
(featureService: FeatureServiceConfig) => featureService.url === updateService.url
);

// Process layers
const layers: FeatureLayerConfig[] = updateService.layers.map((layer: any) => {
// Extract event names from the incoming layer data
const eventNames: string[] = layer.events || [];

// Convert event names to event IDs using the mapping
const eventIds = eventNames
.map(eventName => eventNameToIdMap.get(eventName))
.filter((id): id is MageEventId => id !== undefined);

// Construct the FeatureLayerConfig with eventIds
const featureLayerConfig: FeatureLayerConfig = {
layer: layer.layer,
geometryType: layer.geometryType,
eventIds: eventIds,
};

return featureLayerConfig;
});

return {
url: updateService.url,
layers: updateService.layers,
identityManager: existingService?.identityManager
}
})

layers: layers,
// Map exisiting identityManager, client does not send this
identityManager: existingService?.identityManager,
};
});

await stateRepo.patch({ ...updateConfig, featureServices })

// Sync configuration with feature servers by restarting observation processor
Expand Down
Loading