Skip to content

Commit

Permalink
Convert httpClient calls to @esri/arcgis-rest-request (#228)
Browse files Browse the repository at this point in the history
* Convert httpClient calls to @esri/arcgis-rest-request with ArcGISIdentityManager for authentication

* Fix addFields and deleteFields

* Fixed outFields and returnGeometry for query generation

* [service] prepend form name to all event form fields for clarity and to avoid ESRI column name conflicts

* [service] Fix check to determine if an observation sync to ESRI is a create or update

* [service] OAuth refresh token flow in work

* [service] Add IdentityManager to ObservationSender construction

* [service] Remove httpClient

* [service] fix FeatureQuerier response from request

* [service] Refactor ArcGISIdentityManager management

* draft changes

* [service] ArcGIS field names are lowercase, account for this when adding/removing fields

* Undo revert of FeatureQuerier

* [service] Remove HttpClient.ts, no longer used

* [service] updateConfig inner async methods await before we query

* [server] Attribute sync fix upon adding feature server

* [web/service] API update to include feature service authentication status

* [service] fix regression in token, username/password validation req parameter parsing

* [service] Clean up TODO comments

* [service] form field mapping need to account for form name and field name to avoid conflicts

* Reduce response logging that may cause confusion

* [service] include event forms in detecting config changes

* Merge develop

---------

Co-authored-by: Rick Saccoccia <[email protected]>
Co-authored-by: William Newman <[email protected]>
Co-authored-by: Ryan Slatten <[email protected]>
  • Loading branch information
4 people authored Nov 18, 2024
1 parent f5ac5c1 commit bfb3e8d
Show file tree
Hide file tree
Showing 27 changed files with 1,500 additions and 1,912 deletions.
9 changes: 5 additions & 4 deletions plugins/arcgis/service/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugins/arcgis/service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@esri/arcgis-rest-feature-service": "^4.0.6",
"@esri/arcgis-rest-request": "^4.2.3",
"@terraformer/arcgis": "2.1.2",
"form-data": "^4.0.0"
"form-data": "^4.0.1"
},
"peerDependencies": {
"@ngageoint/mage.service": "^6.2.9 || ^6.3.0-beta",
Expand Down
115 changes: 4 additions & 111 deletions plugins/arcgis/service/src/ArcGISConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,10 @@ export interface FeatureServiceConfig {
*/
url: string

/**
* Username and password for ArcGIS authentication
*/
auth?: ArcGISAuthConfig

/**
* Create layers that don't exist
*/
createLayers?: boolean

/**
* The administration url to the arc feature service.
*/
adminUrl?: string

/**
* Administration access token
*/
adminToken?: string
/**
* Serialized ArcGISIdentityManager
*/
identityManager: string

/**
* The feature layers.
Expand All @@ -49,104 +34,12 @@ export interface FeatureLayerConfig {
*/
geometryType?: string

/**
* Access token
*/
token?: string // TODO - can this be removed? Will Layers have a token too?
/**
* The event ids or names that sync to this arc feature layer.
*/
events?: (number|string)[]

/**
* Add layer fields from form fields
*/
addFields?: boolean

/**
* Delete editable layer fields missing from form fields
*/
deleteFields?: boolean

}

export enum AuthType {
Token = 'token',
UsernamePassword = 'usernamePassword',
OAuth = 'oauth'
}


/**
* Contains token-based authentication configuration.
*/
export interface TokenAuthConfig {
type: AuthType.Token
token: string
authTokenExpires?: string
}

/**
* Contains username and password for ArcGIS server authentication.
*/
export interface UsernamePasswordAuthConfig {
type: AuthType.UsernamePassword
/**
* The username for authentication.
*/
username: string

/**
* The password for authentication.
*/
password: string
}

/**
* Contains OAuth authentication configuration.
*/
export interface OAuthAuthConfig {

type: AuthType.OAuth

/**
* The Client Id for OAuth
*/
clientId: string

/**
* The redirectUri for OAuth
*/
redirectUri?: string

/**
* The temporary auth token for OAuth
*/
authToken?: string

/**
* The expiration date for the temporary token
*/
authTokenExpires?: number

/**
* The Refresh token for OAuth
*/
refreshToken?: string

/**
* The expiration date for the Refresh token
*/
refreshTokenExpires?: number
}

/**
* Union type for authentication configurations.
*/
export type ArcGISAuthConfig =
| TokenAuthConfig
| UsernamePasswordAuthConfig
| OAuthAuthConfig

/**
* Attribute configurations
Expand Down
118 changes: 0 additions & 118 deletions plugins/arcgis/service/src/ArcGISIdentityManagerFactory.ts

This file was deleted.

2 changes: 1 addition & 1 deletion plugins/arcgis/service/src/ArcGISPluginConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export const defaultArcGISPluginConfig = Object.freeze<ArcGISPluginConfig>({
textAreaFieldLength: 256,
observationIdField: 'description',
idSeparator: '-',
// eventIdField: 'event_id',
eventIdField: 'event_id',
lastEditedDateField: 'last_edited_date',
eventNameField: 'event_name',
userIdField: 'user_id',
Expand Down
57 changes: 57 additions & 0 deletions plugins/arcgis/service/src/ArcGISService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ArcGISIdentityManager } from '@esri/arcgis-rest-request'
import { FeatureServiceConfig } from './ArcGISConfig'
import { PluginStateRepository } from '@ngageoint/mage.service/lib/plugins.api'

export interface ArcGISIdentityService {
signin(featureService: FeatureServiceConfig): Promise<ArcGISIdentityManager>
updateIndentityManagers(): Promise<void>
}

export function createArcGISIdentityService(
stateRepo: PluginStateRepository<any>
): ArcGISIdentityService {
const identityManagerCache: Map<string, Promise<ArcGISIdentityManager>> = new Map()

return {
async signin(featureService: FeatureServiceConfig): Promise<ArcGISIdentityManager> {
let cached = await identityManagerCache.get(featureService.url)
if (!cached) {
const identityManager = ArcGISIdentityManager.deserialize(featureService.identityManager)
const promise = identityManager.getUser().then(() => identityManager)
identityManagerCache.set(featureService.url, promise)
return promise
} else {
return cached
}
},
async updateIndentityManagers() {
const config = await stateRepo.get()
for (let [url, persistedIdentityManagerPromise] of identityManagerCache) {
const persistedIdentityManager = await persistedIdentityManagerPromise
const featureService: FeatureServiceConfig | undefined = config.featureServices.find((service: FeatureServiceConfig) => service.url === url)
if (featureService) {
const identityManager = ArcGISIdentityManager.deserialize(featureService.identityManager)
if (identityManager.token !== persistedIdentityManager.token || identityManager.refreshToken !== persistedIdentityManager.refreshToken) {
featureService.identityManager = persistedIdentityManager.serialize()
await stateRepo.put(config)
}
}
}
}
}
}

export function getPortalUrl(featureService: FeatureServiceConfig | string): string {
const url = getFeatureServiceUrl(featureService)
return `https://${url.hostname}/arcgis/sharing/rest`
}

export function getServerUrl(featureService: FeatureServiceConfig | string): string {
const url = getFeatureServiceUrl(featureService)
return `https://${url.hostname}/arcgis`
}

export function getFeatureServiceUrl(featureService: FeatureServiceConfig | string): URL {
const url = typeof featureService === 'string' ? featureService : featureService.url
return new URL(url)
}
10 changes: 5 additions & 5 deletions plugins/arcgis/service/src/FeatureLayerProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LayerInfo } from "./LayerInfo";
import { ObservationBinner } from "./ObservationBinner";
import { ObservationBins } from "./ObservationBins";
import { ObservationsSender } from "./ObservationsSender";

import { ArcGISIdentityManager } from "@esri/arcgis-rest-request";
/**
* Processes new, updated, and deleted observations and sends the changes to a specific arc feature layer.
*/
Expand Down Expand Up @@ -42,12 +42,12 @@ export class FeatureLayerProcessor {
* @param config Contains certain parameters that can be configured.
* @param console Used to log messages to the console.
*/
constructor(layerInfo: LayerInfo, config: ArcGISPluginConfig, console: Console) {
constructor(layerInfo: LayerInfo, config: ArcGISPluginConfig, identityManager: ArcGISIdentityManager, console: Console) {
this.layerInfo = layerInfo;
this.lastTimeStamp = 0;
this.featureQuerier = new FeatureQuerier(layerInfo, config, console);
this.featureQuerier = new FeatureQuerier(layerInfo, config, identityManager,console);
this._binner = new ObservationBinner(layerInfo, this.featureQuerier, config);
this.sender = new ObservationsSender(layerInfo, config, console);
this.sender = new ObservationsSender(layerInfo, config, identityManager, console);
}

/**
Expand Down Expand Up @@ -85,7 +85,7 @@ export class FeatureLayerProcessor {

for (const arcObservation of observations.deletions) {
if (this.layerInfo.geometryType == arcObservation.esriGeometryType) {
this.sender.sendDelete(arcObservation.id)
this.sender.sendDelete(Number(arcObservation.id));
}
}
}
Expand Down
Loading

0 comments on commit bfb3e8d

Please sign in to comment.