diff --git a/src/__tests__/providerCatalog.spec.ts b/src/__tests__/providerCatalog.spec.ts index 6754a9cf..6fc30150 100644 --- a/src/__tests__/providerCatalog.spec.ts +++ b/src/__tests__/providerCatalog.spec.ts @@ -140,7 +140,7 @@ describe("GET /:provider", () => { count: mockCollections.length, cursor: "foundCursor", items: mockCollections.map((coll) => ({ - id: `${coll.shortName}_${coll.version}`, + id: `${coll.id}`, title: coll.title ?? faker.random.words(4), })), }); diff --git a/src/domains/__tests__/collections.spec.ts b/src/domains/__tests__/collections.spec.ts index 7dbd2ea5..55432d28 100644 --- a/src/domains/__tests__/collections.spec.ts +++ b/src/domains/__tests__/collections.spec.ts @@ -276,10 +276,7 @@ describe("collectionsToStac", () => { // Check if all expected fields are present and correctly populated expect(stacCollection).to.have.property("type", "Collection"); - expect(stacCollection).to.have.property( - "id", - `${mockCollection.shortName}_${mockCollection.version}` - ); + expect(stacCollection).to.have.property("id", `${mockCollection.entryId}`); expect(stacCollection).to.have.property("title", mockCollection.title); expect(stacCollection).to.have.property("description", mockCollection.description); expect(stacCollection).to.have.property("stac_version", "1.0.0"); @@ -313,8 +310,130 @@ describe("collectionsToStac", () => { ]); expect(stacCollection.summaries).to.have.property("instruments"); expect(stacCollection.summaries?.instruments).to.deep.equal([ - mockCollection.platforms[0].instruments[0].shortName, + mockCollection.platforms?.[0]?.instruments?.[0]?.shortName, ]); }); }); + describe("when processing platform and instruments", () => { + it("should correctly handle a collection with multiple Platforms and Instruments", () => { + const [mockCollection] = generateCollections(1); + mockCollection.platforms = [ + { + type: "Earth Observation Satellites", + shortName: "Terra", + longName: "Earth Observing System, Terra (AM-1)", + instruments: [ + { + shortName: "ASTER", + longName: "Advanced Spaceborne Thermal Emission and Reflection Radiometer", + }, + { + shortName: "MODIS", + longName: "Moderate-Resolution Imaging Spectroradiometer", + }, + ], + }, + ]; + const stacCollection = collectionToStac(mockCollection); + expect(stacCollection.summaries).to.have.property("platform"); + expect(stacCollection.summaries?.platform).to.deep.equal(["Terra"]); + expect(stacCollection.summaries).to.have.property("instruments"); + expect(stacCollection.summaries?.instruments).to.deep.equal(["ASTER", "MODIS"]); + }); + + it("should handle a collection with platforms but no instruments", () => { + const [mockCollection] = generateCollections(1); + mockCollection.platforms = [ + { + type: "Earth Observation Satellites", + shortName: "Terra", + longName: "Earth Observing System, Terra (AM-1)", + }, + ]; + const stacCollection = collectionToStac(mockCollection); + + expect(stacCollection.summaries).to.have.property("platform"); + expect(stacCollection.summaries?.platform).to.deep.equal(["Terra"]); + expect(stacCollection.summaries).to.have.property("instruments"); + expect(stacCollection.summaries?.instruments).to.deep.equal(["Not Provided"]); + }); + + it("should handle a collection with some platforms having instruments and others not", () => { + const [mockCollection] = generateCollections(1); + mockCollection.platforms = [ + { + type: "Earth Observation Satellites", + shortName: "Terra", + longName: "Earth Observing System, Terra (AM-1)", + instruments: [ + { + shortName: "ASTER", + longName: "Advanced Spaceborne Thermal Emission and Reflection Radiometer", + }, + ], + }, + { + type: "Earth Observation Satellites", + shortName: "SPOT-4", + longName: "Systeme Probatoire Pour l'Observation de la Terre-4", + }, + { + type: "Earth Observation Satellites", + shortName: "SPOT-5", + longName: "Systeme Probatoire Pour l'Observation de la Terre-5", + instruments: [ + { + shortName: "VEGETATION-2", + longName: "VEGETATION INSTRUMENT 2 (SPOT 5)", + }, + { + shortName: "VEGETATION-3", + longName: "VEGETATION INSTRUMENT 3 (SPOT 3)", + }, + ], + }, + ]; + + const stacCollection = collectionToStac(mockCollection); + + expect(stacCollection.summaries).to.have.property("platform"); + expect(stacCollection.summaries?.platform).to.deep.equal(["Terra", "SPOT-4", "SPOT-5"]); + expect(stacCollection.summaries).to.have.property("instruments"); + expect(stacCollection.summaries?.instruments).to.deep.equal([ + "ASTER", + "VEGETATION-2", + "VEGETATION-3", + ]); + }); + + it("should handle a collection with empty instruments arrays", () => { + const [mockCollection] = generateCollections(1); + mockCollection.platforms = [ + { + type: "Earth Observation Satellites", + shortName: "Terra", + longName: "Earth Observing System, Terra (AM-1)", + instruments: [ + { + shortName: "ASTER", + longName: "Advanced Spaceborne Thermal Emission and Reflection Radiometer", + }, + ], + }, + { + type: "Earth Observation Satellites", + shortName: "SPOT-4", + longName: "Systeme Probatoire Pour l'Observation de la Terre-4", + instruments: [], + }, + ]; + + const stacCollection = collectionToStac(mockCollection); + + expect(stacCollection.summaries).to.have.property("platform"); + expect(stacCollection.summaries?.platform).to.deep.equal(["Terra", "SPOT-4"]); + expect(stacCollection.summaries).to.have.property("instruments"); + expect(stacCollection.summaries?.instruments).to.deep.equal(["ASTER"]); + }); + }); }); diff --git a/src/domains/__tests__/items.spec.ts b/src/domains/__tests__/items.spec.ts index c8ac8622..cc82508c 100644 --- a/src/domains/__tests__/items.spec.ts +++ b/src/domains/__tests__/items.spec.ts @@ -41,7 +41,7 @@ describe("granuleToStac", () => { start_datetime: "2009-09-14T00:00:00.000Z", end_datetime: "2010-09-14T00:00:00.000Z", }, - collection: "short_1", + collection: "TEST_COLLECTION_1", links: [ { href: "undefined/search/concepts/G000000000-TEST_PROV.json", @@ -129,7 +129,7 @@ describe("granuleToStac", () => { start_datetime: "2009-09-14T00:00:00.000Z", end_datetime: "2010-09-14T00:00:00.000Z", }, - collection: "short_1", + collection: "TEST_COLLECTION_1", links: [ { href: "undefined/search/concepts/G000000000-TEST_PROV.json", @@ -212,7 +212,7 @@ describe("granuleToStac", () => { start_datetime: "2009-09-14T00:00:00.000Z", end_datetime: "2010-09-14T00:00:00.000Z", }, - collection: "short_1", + collection: "TEST_COLLECTION_1", links: [ { href: "undefined/search/concepts/G000000000-TEST_PROV.json", diff --git a/src/domains/__tests__/stac.spec.ts b/src/domains/__tests__/stac.spec.ts index a414ea67..741f224b 100644 --- a/src/domains/__tests__/stac.spec.ts +++ b/src/domains/__tests__/stac.spec.ts @@ -413,8 +413,8 @@ describe("sortByToSortKeys", () => { [ { input: "properties.eo:cloud_cover", output: ["cloudCover"] }, { input: "-properties.eo:cloud_cover", output: ["-cloudCover"] }, - { input: "id", output: ["shortName"] }, - { input: "-id", output: ["-shortName"] }, + { input: "id", output: ["entryId"] }, + { input: "-id", output: ["-entryId"] }, { input: "title", output: ["entryTitle"] }, { input: "-title", output: ["-entryTitle"] }, { input: "someOtherField", output: ["someOtherField"] }, @@ -439,7 +439,7 @@ describe("sortByToSortKeys", () => { { field: "id", direction: "asc" }, { field: "title", direction: "desc" }, ]; - expect(sortByToSortKeys(input)).to.deep.equal(["-cloudCover", "shortName", "-entryTitle"]); + expect(sortByToSortKeys(input)).to.deep.equal(["-cloudCover", "entryId", "-entryTitle"]); }); }); }); diff --git a/src/domains/collections.ts b/src/domains/collections.ts index 8a7c4849..ffc67d87 100644 --- a/src/domains/collections.ts +++ b/src/domains/collections.ts @@ -31,19 +31,18 @@ const collectionsQuery = gql` conceptId description: abstract directDistributionInformation + entryId lines + platforms points polygons - platforms provider relatedUrls scienceKeywords - shortName timeEnd timeStart title useConstraints - version } } } @@ -56,9 +55,8 @@ const collectionIdsQuery = gql` cursor items { conceptId - shortName + entryId title - version } } } @@ -188,11 +186,19 @@ const createSummaries = (collection: Collection): Summaries => { instruments: string[]; } - return platforms.reduce( + const summaries = platforms.reduce( (summaries, platform) => { const { platform: currPlatforms, instruments: currInstruments } = summaries; const { instruments, shortName } = platform; + // If instruments is not present, return early with only the platform added + if (!instruments) { + return { + platform: [...currPlatforms, shortName], + instruments: currInstruments, + }; + } + return { platform: [...currPlatforms, shortName], instruments: [ @@ -203,6 +209,12 @@ const createSummaries = (collection: Collection): Summaries => { }, { platform: [], instruments: [] } ); + + if (summaries.instruments.length === 0) { + summaries.instruments = ["Not Provided"]; + } + + return summaries; }; const generateProviders = (collection: Collection) => [ @@ -220,13 +232,12 @@ const generateProviders = (collection: Collection) => [ * Convert a GraphQL collection item into a STACCollection. */ export const collectionToStac = (collection: Collection): STACCollection => { - const { description, title } = collection; + const { entryId, description, title } = collection; const { license, licenseLink } = extractLicense(collection); const assets = extractAssets(collection); const extent = createExtent(collection); - const id = collectionToId(collection); const keywords = createKeywords(collection); const links = generateCollectionLinks(collection, [licenseLink]); const provider = generateProviders(collection); @@ -234,7 +245,7 @@ export const collectionToStac = (collection: Collection): STACCollection => { return { type: "Collection", - id, + id: entryId, title, description, stac_version: STAC_VERSION, @@ -290,20 +301,9 @@ export const getCollections = async ( return { cursor, count, items: collections as STACCollection[] }; }; -/** - * Return a STAC ID for a given collection. - * STAC ID should correspond to a CMR entry_id - * TODO: handle this type of situation ~> 10.3334/cdiac/otg.vos_alligatorhope_1999-2001_Not applicable as entry_id - */ -export const collectionToId = (collection: { shortName: string; version?: string | null }) => { - const { shortName, version } = collection; - - return version ? `${shortName}_${version}` : shortName; -}; - -const attachId = (collection: { shortName: string; version?: string | null }) => ({ +const attachId = (collection: { entryId: string }) => ({ ...collection, - id: collectionToId(collection), + id: collection.entryId, }); /** @@ -339,7 +339,6 @@ export const getCollectionIds = async ( count, items: collectionIds, } = await paginateQuery(collectionIdsQuery, params, opts, collectionIdsHandler); - return { cursor, count, items: collectionIds as { id: string; title: string }[] }; }; diff --git a/src/domains/items.ts b/src/domains/items.ts index 4132ba6b..95841a87 100644 --- a/src/domains/items.ts +++ b/src/domains/items.ts @@ -10,7 +10,6 @@ import { cmrSpatialToExtent } from "./bounding-box"; import { cmrSpatialToGeoJSONGeometry } from "./geojson"; import { mergeMaybe, stacContext } from "../utils"; import { extractAssets, paginateQuery } from "./stac"; -import { collectionToId } from "./collections"; import { ItemNotFound } from "../models/errors"; const STAC_VERSION = process.env.STAC_VERSION ?? "1.0.0"; @@ -26,8 +25,7 @@ const granulesQuery = gql` conceptId collection { conceptId - shortName - version + entryId title } cloudCover @@ -216,7 +214,7 @@ export const granuleToStac = (granule: Granule): STACItem => { return { ...item, - collection: collectionToId(granule.collection), + collection: granule.collection.entryId, }; }; diff --git a/src/domains/stac.ts b/src/domains/stac.ts index d9cd76dd..860f7611 100644 --- a/src/domains/stac.ts +++ b/src/domains/stac.ts @@ -357,7 +357,7 @@ export const sortByToSortKeys = (sortBys?: string | SortObject[] | string[]): st if (fieldName.match(/^eo:cloud_cover$/i)) { mappedField = "cloudCover"; } else if (fieldName.match(/^id$/i)) { - mappedField = "shortName"; + mappedField = "entryId"; } else if (fieldName.match(/^title$/i)) { mappedField = "entryTitle"; } else { diff --git a/src/models/GraphQLModels.ts b/src/models/GraphQLModels.ts index 156fcb5d..43fe2a10 100644 --- a/src/models/GraphQLModels.ts +++ b/src/models/GraphQLModels.ts @@ -142,7 +142,7 @@ export type Platform = { type: string; shortName: string; longName: string; - instruments: Instrument[]; + instruments?: Instrument[]; }; export type ScienceKeywords = { @@ -164,8 +164,7 @@ export type DirectDistributionInformation = { export type CollectionBase = { conceptId: string; - version: string; - shortName: string; + entryId: string; title: string; }; diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 01da1e12..7e1c2b9d 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -7,15 +7,9 @@ export const generateSTACCollections = (quantity: number) => { return Array(quantity) .fill(undefined) .map(() => { - const shortName = faker.commerce.product(); - const version = `v${Math.random() * 100}`; - const id = `${shortName}_${version}`; - return { - id, + id: "TEST_COLLECTION", title: faker.animal.cat(), - shortName, - version, stac_version: "1.0.0", type: "Collection", description: faker.hacker.phrase(), @@ -143,7 +137,7 @@ export const generateGranules = ( quantity: number, opts: { collection?: { - shortName: string; + entryId: string; version: string; conceptId: string; }; @@ -158,8 +152,7 @@ export const generateGranules = ( conceptId: `G00000000${idx}-${opts?.provider ?? "TEST_PROV"}`, collection: { conceptId: opts?.collection?.conceptId ?? "C123456789-TEST_PROV", - shortName: "short", - version: "1", + entryId: "TEST_COLLECTION_1", }, title: faker.random.words(8).replace(/\s+/gi, "_"), } as Granule; @@ -184,8 +177,7 @@ export const generateCollections = ( summary: faker.lorem.paragraph(), description: "this is the abstract but aliased as description", title: "mock_coll", - shortName: faker.random.words(4).replace(/\s+/, "_"), - version: faker.random.alphaNumeric(), + entryId: faker.random.words(4).replace(/\s+/, "_"), boxes: null, lines: null, polygons: null,