import { getItemData , getItemGroups , getUser } from "@esri/arcgis-rest-portal" ; import { getAllLayersAndTables , getService , parseServiceUrl , ILayerDefinition , } from "@esri/arcgis-rest-feature-layer" ; import { IHubContent , IHubRequestOptions , IPipeable , cloneObject , createOperationPipeline , getProp , getItemHomeUrl , getItemApiUrl , getItemDataUrl , getItemThumbnailUrl , getLayerIdFromUrl , includes , isFeatureService , isMapOrFeatureServerUrl , isNil , OperationStack , setContentHubId , setContentType , } from "@esri/hub-common" ; import { getContentMetadata } from "./metadata" ; const getHubId = ( content , layerId ) => { const { item } = content ; return item . access === "public" && ! content . isProxied ? `${ item . id }_${ layerId }` : content . hubId ; } ; const getLayer = ( content , layerId ? ) => { const { url , layers } = content ; const idFromUrl = getLayerIdFromUrl ( url ) ; const id = idFromUrl ? parseInt ( idFromUrl , 10 ) : layerId ; return layers && ! isNil ( id ) ? layers . find ( ( layer ) => layer . id === id ) : isFeatureService ( content . type ) && getOnlyQueryLayer ( layers ) ; } ; const getOnlyQueryLayer = ( layers ) => { const layer = layers && layers . length === 1 && layers [ 0 ] ; return layer && layer . capabilities . includes ( "Query" ) && layer ; } ; const shouldUseLayerInfo = ( content ) => { return ( content . layer && content . layers && content . layers . length > 1 && ! getLayerIdFromUrl ( content . url ) ) ; } ; interface IMetadataPaths { updateFrequency : string ; metadataUpdateFrequency : string ; metadataUpdatedDate : string ; reviseDate : string ; pubDate : string ; createDate : string ; } enum { string } export enum UpdateFrequency { Continual = "continual" , Daily = "daily" , Weekly = "weekly" , Fortnightly = "fortnightly" , Monthly = "monthly" , Quarterly = "quarterly" , Biannually = "biannually" , Annually = "annually" , AsNeeded = "as-needed" , Irregular = "irregular" , NotPlanned = "not-planned" , Unknown = "unknown" , Semimonthly = "semimonthly" } enum DatePrecision { Year = "year" , Month = "month" , Day = "day" , Time = "time" } function getMetadataPath ( identifier ) { const metadataPaths = { updateFrequency : "metadata.metadata.dataIdInfo.resMaint.maintFreq.MaintFreqCd.@_value" , reviseDate : "metadata.metadata.dataIdInfo.idCitation.date.reviseDate" , pubDate : "metadata.metadata.dataIdInfo.idCitation.date.pubDate" , createDate : "metadata.metadata.dataIdInfo.idCitation.date.createDate" , metadataUpdateFrequency : "metadata.metadata.mdMaint.maintFreq.MaintFreqCd.@_value" , metadataUpdatedDate : "metadata.metadata.mdDateSt" , } ; return metadataPaths [ identifier ] ; } function getValueFromMetadata ( content , identifier ) { const path = getMetadataPath ( identifier ) ; return path && getProp ( content , path ) ; } param { string } isoString return { date export function parseISODateString ( isoString ) { isoString = `${ isoString }` ; let date ; let precision ; if ( /^\d{4}$/ . test ( isoString ) ) { date = new Date ( + isoString , 0 , 1 ) ; precision = DatePrecision . Year ; } else if ( /^\d{4}-\d{1,2}$/ . test ( isoString ) ) { const parts = isoString . split ( "-" ) ; date = new Date ( + parts [ 0 ] , + parts [ 1 ] - 1 , 1 ) ; precision = DatePrecision . Month ; } else if ( /^\d{4}-\d{1,2}-\d{1,2}$/ . test ( isoString ) ) { const parts = isoString . split ( "-" ) ; date = new Date ( + parts [ 0 ] , + parts [ 1 ] - 1 , + parts [ 2 ] ) ; precision = DatePrecision . Day ; } else if ( ! Number . isNaN ( Date . parse ( isoString ) ) ) { date = new Date ( isoString ) ; precision = isoString . includes ( "T" ) ? DatePrecision . Time : DatePrecision . Day ; } return date && precision && { date , precision } ; } private param { IHubContent } content returns { IHubContent } export function _enrichDates ( content ) { const newContent = cloneObject ( content ) ; const updateFrequencyMap = { "001" : UpdateFrequency . Continual , "002" : UpdateFrequency . Daily , "003" : UpdateFrequency . Weekly , "004" : UpdateFrequency . Fortnightly , "005" : UpdateFrequency . Monthly , "006" : UpdateFrequency . Quarterly , "007" : UpdateFrequency . Biannually , "008" : UpdateFrequency . Annually , "009" : UpdateFrequency . AsNeeded , "010" : UpdateFrequency . Irregular , "011" : UpdateFrequency . NotPlanned , "012" : UpdateFrequency . Unknown , "013" : UpdateFrequency . Semimonthly , } as { [ index ] : UpdateFrequency ; } ; const updateFrequencyValue = getValueFromMetadata ( newContent , "updateFrequency" ) ; if ( updateFrequencyValue ) { newContent . updateFrequency = updateFrequencyMap [ updateFrequencyValue ] ; } const metadataUpdateFrequencyValue = getValueFromMetadata ( newContent , "metadataUpdateFrequency" ) ; if ( metadataUpdateFrequencyValue ) { newContent . metadataUpdateFrequency = updateFrequencyMap [ metadataUpdateFrequencyValue ] ; } const metadataUpdatedDate = parseISODateString ( getValueFromMetadata ( newContent , "metadataUpdatedDate" ) ) ; if ( metadataUpdatedDate ) { newContent . metadataUpdatedDate = metadataUpdatedDate . date ; newContent . metadataUpdatedDatePrecision = metadataUpdatedDate . precision ; newContent . metadataUpdatedDateSource = getMetadataPath ( "metadataUpdatedDate" ) ; } else { newContent . metadataUpdatedDate = newContent . updatedDate ; newContent . metadataUpdatedDatePrecision = DatePrecision . Day ; newContent . metadataUpdatedDateSource = newContent . updatedDateSource ; } const reviseDate = parseISODateString ( getValueFromMetadata ( newContent , "reviseDate" ) ) ; const layerLastEditDate = getProp ( newContent , "layer.editingInfo.lastEditDate" ) ; const serverLastEditDate = getProp ( newContent , "server.editingInfo.lastEditDate" ) ; newContent . updatedDatePrecision = DatePrecision . Day ; if ( reviseDate ) { newContent . updatedDate = reviseDate . date ; newContent . updatedDatePrecision = reviseDate . precision ; newContent . updatedDateSource = getMetadataPath ( "reviseDate" ) ; } else if ( layerLastEditDate ) { newContent . updatedDate = new Date ( layerLastEditDate ) ; newContent . updatedDateSource = "layer.editingInfo.lastEditDate" ; } else if ( serverLastEditDate ) { newContent . updatedDate = new Date ( serverLastEditDate ) ; newContent . updatedDateSource = "server.editingInfo.lastEditDate" ; } const pubDate = parseISODateString ( getValueFromMetadata ( newContent , "pubDate" ) ) ; const createDate = parseISODateString ( getValueFromMetadata ( newContent , "createDate" ) ) ; newContent . publishedDatePrecision = DatePrecision . Day ; if ( pubDate ) { newContent . publishedDate = pubDate . date ; newContent . publishedDatePrecision = pubDate . precision ; newContent . publishedDateSource = getMetadataPath ( "pubDate" ) ; } else if ( createDate ) { newContent . publishedDate = createDate . date ; newContent . publishedDatePrecision = createDate . precision ; newContent . publishedDateSource = getMetadataPath ( "createDate" ) ; } return newContent ; } const enrichGroupIds = ( input ) : Promise < IPipeable < IHubContent > > => { const { data : content , stack , requestOptions } = input ; const opId = stack . start ( "enrichGroupIds" ) ; return getItemGroups ( content . id , requestOptions ) . then ( ( response ) => { const { admin , member , other } = response ; const groupIds = [ ... admin , ... member , ... other ] . map ( ( group ) => group . id ) ; stack . finish ( opId ) ; return { data : { ... content , groupIds } , stack , requestOptions , } ; } ) . catch ( ( error ) => handleEnrichmentError ( error , input , opId ) ) ; } ; const enrichMetadata = ( input ) : Promise < IPipeable < IHubContent > > => { const { data : content , stack , requestOptions } = input ; const opId = stack . start ( "enrichMetadata" ) ; return getContentMetadata ( content . id , requestOptions as IHubRequestOptions ) . then ( ( metadata ) => { stack . finish ( opId ) ; const data = { ... content , metadata } ; return { data , stack , requestOptions } ; } ) . catch ( ( error ) => handleEnrichmentError ( error , input , opId ) ) ; } ; const enrichOwner = ( input ) : Promise < IPipeable < IHubContent > > => { const { data : content , stack , requestOptions } = input ; const opId = stack . start ( "enrichOwner" ) ; const options = { username : content . owner , ... requestOptions , } ; return getUser ( options ) . then ( ( ownerUser ) => { stack . finish ( opId ) ; const orgId = ownerUser . orgId ; const data = { ... content , orgId } ; return { data , stack , requestOptions } ; } ) . catch ( ( error ) => handleEnrichmentError ( error , input , opId ) ) ; } ; const enrichData = ( input ) : Promise < IPipeable < IHubContent > > => { const { data : content , stack , requestOptions } = input ; const opId = stack . start ( "enrichData" ) ; return getItemData ( content . id , requestOptions ) . then ( ( itemData ) => { stack . finish ( opId ) ; const data = { ... content , data : itemData } ; return { data , stack , requestOptions } ; } ) . catch ( ( error ) => handleEnrichmentError ( error , input , opId ) ) ; } ; const enrichServer = ( input ) : Promise < IPipeable < IHubContent > > => { const { data : content , stack , requestOptions } = input ; const opId = stack . start ( "enrichServer" ) ; const url = content . url ; const options = { ... requestOptions , url , } ; return getService ( options ) . then ( ( server ) => { stack . finish ( opId ) ; const data = { ... content , server } ; return { data , stack , requestOptions } ; } ) . catch ( ( error ) => handleEnrichmentError ( error , input , opId ) ) ; } ; const enrichLayers = ( input ) : Promise < IPipeable < IHubContent > > => { const { data : content , stack , requestOptions } = input ; const opId = stack . start ( "enrichLayers" ) ; const url = content . url ; const options = { ... requestOptions , url , } ; return ( getAllLayersAndTables ( options ) . then ( ( response ) => { const merged = [ ... response . layers , ... response . tables ] ; return merged . filter ( ( layer ) => ( layer . type as string ) !== "Group Layer" ) ; } ) . then ( ( layers ) => { stack . finish ( opId ) ; const data = { ... content , layers } ; return { data , stack , requestOptions } ; } ) . catch ( ( error ) => handleEnrichmentError ( error , input , opId ) ) ) ; } ; const handleEnrichmentError = ( error , input , opId ) : IPipeable < IHubContent > => { const { data : content , stack , requestOptions } = input ; stack . finish ( opId , { error } ) ; const message = typeof error === "string" ? error : error . message ; const errors = content . errors || [ ] ; errors . push ( { type : "Other" , message , } ) ; const data = { ... content , errors } ; return { data , stack , requestOptions } ; } ; interface IEnrichmentOperations { [ key ] : ( input : IPipeable < IHubContent > ) => Promise < IPipeable < IHubContent > > ; } const enrichmentOperations = { groupIds : enrichGroupIds , metadata : enrichMetadata , server : enrichServer , layers : enrichLayers , data : enrichData , orgId : enrichOwner , } ; const shouldFetchData = ( content ) => { const hubTypes = [ "template" , "solution" ] ; const types = [ "Web Map" , "Web Scene" ] ; return ( ! content . data && ( includes ( hubTypes , content . hubType ) || includes ( types , content . type ) ) ) ; } ; const isHubCreatedContent = ( content ) => { const hubTypeKeywords = [ "Enterprise Sites" , "ArcGIS Hub" ] ; const contentTypeKeywords = content . typeKeywords || [ ] ; return ( content . type === "Web Map" && contentTypeKeywords . some ( ( typeKeyword ) => hubTypeKeywords . indexOf ( typeKeyword ) > - 1 ) ) ; } ; const shouldFetchOrgId = ( content ) => { return ! content . orgId && isHubCreatedContent ( content ) ; } ; const getMissingEnrichments = ( content ) => { const enrichments = [ ] ; if ( ! content . groupIds ) { enrichments . push ( "groupIds" ) ; } if ( typeof content . metadata === "undefined" ) { enrichments . push ( "metadata" ) ; } if ( shouldFetchOrgId ( content ) ) { enrichments . push ( "orgId" ) ; } if ( shouldFetchData ( content ) ) { enrichments . push ( "data" ) ; } if ( isMapOrFeatureServerUrl ( content . url ) ) { if ( ! content . server ) { enrichments . push ( "server" ) ; } if ( ! content . layers ) { enrichments . push ( "layers" ) ; } } return enrichments ; } ; param content param requestOptions returns export export const getPortalUrls = ( content , requestOptions ) => { const authentication = requestOptions . authentication ; const token = authentication && authentication . token ; const portalHomeUrl = getItemHomeUrl ( content . id , requestOptions ) ; const portalApiUrl = getItemApiUrl ( content , requestOptions , token ) ; const portalDataUrl = getItemDataUrl ( content , requestOptions , token ) ; const thumbnailUrl = getItemThumbnailUrl ( content , requestOptions , { token , } ) ; return { portalHomeUrl , portalApiUrl , portalDataUrl , thumbnailUrl , } ; } ; export interface IFetchEnrichmentOptions extends IHubRequestOptions { /**
     * comma separated list of enrichment (property) names to fetch
     * if omitted, the default enrichments will be fetched
     * to skip fetching enrichments, pass an empty array []
     */ enrichments ? : string [ ] ; } export interface IEnrichContentOptions extends IFetchEnrichmentOptions { layerId ? : number ; } param content param requestOptions returns export const fetchEnrichments = ( content , requestOptions ? ) : Promise < IHubContent > => { const enrichments = ( requestOptions && requestOptions . enrichments ) || getMissingEnrichments ( content ) ; const operations = enrichments . reduce ( ( ops , enrichment ) => { const operation = enrichmentOperations [ enrichment ] ; operation && ops . push ( operation ) ; return ops ; } , [ ] ) ; const pipeline = createOperationPipeline ( operations ) ; return pipeline ( { data : content , stack : new OperationStack ( ) , requestOptions , } ) . then ( ( output ) => { return output . data as IHubContent ; } ) ; } ; param content param requestOptions returns export const enrichContent = ( content , requestOptions ? ) => { const defaults = { errors : [ ] , } ; const contentWithPortalUrls = { ... defaults , ... content , ... getPortalUrls ( content , requestOptions ) , } ; return fetchEnrichments ( contentWithPortalUrls , requestOptions ) . then ( ( fetched ) => { const enriched = _enrichDates ( fetched ) ; return getLayerContent ( enriched , requestOptions . layerId ) || enriched ; } ) ; } ; param content param layerId returns export const getLayerContent = ( content , layerId ? ) : IHubContent => { const layer = getLayer ( content , layerId ) ; if ( ! layer ) { return ; } const { type , name , description } = layer ; const hubId = getHubId ( content , layer . id ) ; const updatedContent = setContentType ( setContentHubId ( content , hubId ) , type ) ; const layerContent = { ... updatedContent , layer } ; if ( shouldUseLayerInfo ( layerContent ) ) { layerContent . title = name ; if ( description ) { layerContent . description = description ; layerContent . summary = description ; } layerContent . url = `${ parseServiceUrl ( content . url ) }/${ layer . id }` ; } return layerContent ; } ;