diff --git a/.github/workflows/deploy-to-production.yml b/.github/workflows/deploy-to-production.yml index b3278972b..f91123579 100644 --- a/.github/workflows/deploy-to-production.yml +++ b/.github/workflows/deploy-to-production.yml @@ -17,6 +17,7 @@ jobs: GATSBY_CPU_COUNT: 2 GATSBY_MAPBOX_TOKEN: ${{ secrets.GATSBY_MAPBOX_TOKEN }} GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }} + GATSBY_ADMG_API: ${{ secrets.GATSBY_ADMG_API }} steps: - name: Cancel Previous Runs diff --git a/.github/workflows/deploy-to-staging.yml b/.github/workflows/deploy-to-staging.yml index e6fd9b1e4..dcc036c4d 100644 --- a/.github/workflows/deploy-to-staging.yml +++ b/.github/workflows/deploy-to-staging.yml @@ -22,6 +22,7 @@ jobs: GATSBY_CPU_COUNT: 2 GATSBY_MAPBOX_TOKEN: ${{ secrets.GATSBY_MAPBOX_TOKEN }} GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }} + GATSBY_ADMG_API: ${{ secrets.GATSBY_ADMG_API }} steps: - name: Cancel Previous Runs diff --git a/gatsby-config.js b/gatsby-config.js index 3aff76a3c..1c2d8e8c5 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -90,8 +90,8 @@ module.exports = { "child-src": "'self' blob:", "connect-src": process.env.NODE_ENV === "development" - ? "'self' https://www.google-analytics.com https://*.tiles.mapbox.com https://api.mapbox.com https://events.mapbox.com http://localhost:* ws://localhost:*" - : "'self' https://www.google-analytics.com https://*.tiles.mapbox.com https://api.mapbox.com https://events.mapbox.com", + ? "'self' https://admg.nasa-impact.net https://www.google-analytics.com https://*.tiles.mapbox.com https://api.mapbox.com https://events.mapbox.com http://localhost:* ws://localhost:*" + : "'self' https://admg.nasa-impact.net https://www.google-analytics.com https://*.tiles.mapbox.com https://api.mapbox.com https://events.mapbox.com https://admgstaging.nasa-impact.net/api/unpublished_drafts", }, }, }, diff --git a/package.json b/package.json index 69818d241..8f3cf39db 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@reach/combobox": "^0.18.0", "@reach/disclosure": "^0.17.0", "@reach/listbox": "^0.17.0", + "@reach/tooltip": "^0.18.0", "@reach/visually-hidden": "^0.17.0", "@turf/area": "^6.5.0", "@turf/bbox": "^6.5.0", @@ -35,11 +36,11 @@ "gatsby-plugin-image": "^2.8.1", "gatsby-plugin-manifest": "^4.8.1", "gatsby-plugin-offline": "^5.8.1", + "gatsby-plugin-postcss": "5.10.0", "gatsby-plugin-react-helmet": "^5.8.0", "gatsby-plugin-sharp": "^4.8.1", "gatsby-plugin-styled-components": "^5.8.0", "gatsby-plugin-typography": "^4.8.0", - "gatsby-plugin-postcss": "5.10.0", "gatsby-source-filesystem": "^4.8.1", "gatsby-transformer-json": "^4.8.0", "gatsby-transformer-sharp": "^4.8.0", @@ -78,9 +79,9 @@ "eslint": "^8.10.0", "eslint-config-prettier": "^8.4.0", "eslint-config-react-app": "^7.0.0", - "eslint-plugin-playwright": "0.12.0", "eslint-plugin-inclusive-language": "^2.2.0", "eslint-plugin-jest": "^26.1.1", + "eslint-plugin-playwright": "0.12.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.29.2", "eslint-plugin-react-hooks": "^4.3.0", @@ -89,10 +90,10 @@ "identity-obj-proxy": "^3.0.0", "jest": "^27.5.1", "jest-styled-components": "^7.0.8", + "postcss": "8.4.23", "prettier": "^2.5.1", "react-test-renderer": "^17.0.1", - "start-server-and-test": "^1.14.0", - "postcss": "8.4.23" + "start-server-and-test": "^1.14.0" }, "license": "MIT", "scripts": { @@ -127,4 +128,4 @@ "overrides": { "playwright": "1.31.2" } -} \ No newline at end of file +} diff --git a/playwright/e2e/campaign.spec.ts b/playwright/e2e/campaign.spec.ts index 5b7e0823e..2e95158fc 100644 --- a/playwright/e2e/campaign.spec.ts +++ b/playwright/e2e/campaign.spec.ts @@ -1,58 +1,63 @@ -const { test, expect } = require('@playwright/test'); +const { test, expect } = require("@playwright/test") import config from "../playwright.config" const baseUrl = config.use?.baseURL -test.describe('Campaign', () => { - let page; - - test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); - await page.goto(baseUrl + '/campaign/OLYMPEX'); - }); - - test('provides information on the campaign', async () => { - - await expect(page.locator('[data-cy=campaign-hero] h1')).toBeVisible(); - await expect(page.locator('[data-cy=campaign-hero-header]')).toHaveCount(1); - - const statsItems = await page.locator('[data-cy=campaign-hero] [data-cy=stats] dd'); - await expect(statsItems).toHaveCount(3); - await expect(statsItems.nth(0)).toContainText('Deployment'); - await expect(statsItems.nth(1)).toContainText('Collection Periods'); - await expect(statsItems.nth(2)).toContainText('Data Products'); - - const statsValues = await page.locator('[data-cy=campaign-hero] [data-cy=stats] dt'); - await expect(statsValues).toHaveCount(3); - await expect(statsValues.nth(0)).toContainText('1'); - await expect(statsValues.nth(1)).toContainText('713'); - await expect(statsValues.nth(2)).toBeVisible(); - - await expect(page.locator('[data-cy=mapboxgl-map]')).toBeVisible(); - - const inpageNavItems = await page.locator('[data-cy=inpage-nav] a'); - await expect(inpageNavItems).toHaveCount(7); - await expect(inpageNavItems.nth(0)).toBeVisible(); - await expect(inpageNavItems.nth(1)).toContainText('Overview'); - await expect(inpageNavItems.nth(2)).toContainText('Focus'); - await expect(inpageNavItems.nth(3)).toContainText('Platforms & Instruments'); - await expect(inpageNavItems.nth(4)).toContainText('Timeline'); - await expect(inpageNavItems.nth(5)).toContainText('Data'); - await expect(inpageNavItems.nth(6)).toContainText('Program Info'); - - const sectionIds = ['program-info', 'platform', 'overview', 'timeline', 'focus']; - - for (const id of sectionIds) { - await page.locator(`[data-cy=${id}-inpage-link]`).click(); - await expect(page.url()).toContain(id); - - // TODO: figure out how to properly test the inpage scroll - await expect(page.locator(`[data-cy=${id}-section] h2`)).toBeVisible(); - } - - - - }); - - // ... additional test may need to be added to cover any changes made to this format page, such as search capability - -}); +test.describe("Campaign", () => { + let page + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + await page.goto(baseUrl + "/campaign/OLYMPEX") + }) + + test("provides information on the campaign", async () => { + await expect(page.locator("[data-cy=campaign-hero] h1")).toBeVisible() + await expect(page.locator("[data-cy=campaign-hero-header]")).toHaveCount(1) + + const statsItems = await page.locator( + "[data-cy=campaign-hero] [data-cy=stats] dd" + ) + await expect(statsItems).toHaveCount(3) + await expect(statsItems.nth(0)).toContainText("Deployment") + await expect(statsItems.nth(1)).toContainText("Platforms") + await expect(statsItems.nth(2)).toContainText("Data Products") + + const statsValues = await page.locator( + "[data-cy=campaign-hero] [data-cy=stats] dt" + ) + await expect(statsValues).toHaveCount(3) + await expect(statsValues.nth(0)).toContainText("1") + await expect(statsValues.nth(1)).toContainText("8") + await expect(statsValues.nth(2)).toBeVisible() + + await expect(page.locator("[data-cy=mapboxgl-map]")).toBeVisible() + + const inpageNavItems = await page.locator("[data-cy=inpage-nav] a") + await expect(inpageNavItems).toHaveCount(7) + await expect(inpageNavItems.nth(0)).toBeVisible() + await expect(inpageNavItems.nth(1)).toContainText("Overview") + await expect(inpageNavItems.nth(2)).toContainText("Focus") + await expect(inpageNavItems.nth(3)).toContainText("Platforms & Instruments") + await expect(inpageNavItems.nth(4)).toContainText("Timeline") + await expect(inpageNavItems.nth(5)).toContainText("Data") + await expect(inpageNavItems.nth(6)).toContainText("Program Info") + + const sectionIds = [ + "program-info", + "platform", + "overview", + "timeline", + "focus", + ] + + for (const id of sectionIds) { + await page.locator(`[data-cy=${id}-inpage-link]`).click() + await expect(page.url()).toContain(id) + + // TODO: figure out how to properly test the inpage scroll + await expect(page.locator(`[data-cy=${id}-section] h2`)).toBeVisible() + } + }) + + // ... additional test may need to be added to cover any changes made to this format page, such as search capability +}) diff --git a/playwright/e2e/contact.spec.ts b/playwright/e2e/contact.spec.ts index 2ec0fffb2..c2a59e00f 100644 --- a/playwright/e2e/contact.spec.ts +++ b/playwright/e2e/contact.spec.ts @@ -1,35 +1,33 @@ -const { chromium } = require("@playwright/test"); -const { test, expect } = require('@playwright/test'); +const { chromium } = require("@playwright/test") +const { test, expect } = require("@playwright/test") import config from "../playwright.config" const baseUrl = config.use?.baseURL test.describe("Contact", () => { - - let browser, page; - - test.beforeAll(async ({ browser }) => { - - page = await browser.newPage(); - await page.goto(baseUrl); - }); - - test.afterAll(async (browser) => { - browser = await chromium.launch(); - await browser.close(); - }); - - test.beforeEach(async (browser) => { - browser = await chromium.launch(); - page = await browser.newPage(); - await page.goto(baseUrl + "/contact"); - }); - - test.afterEach(async () => { - await page.close(); - }); - - test("renders correctly", async () => { - await page.waitForSelector("main h1:text('feedback')"); - await page.waitForSelector("main p:text('NASA inventory')"); - }); -}); + let browser, page + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + await page.goto(baseUrl) + }) + + test.afterAll(async browser => { + browser = await chromium.launch() + await browser.close() + }) + + test.beforeEach(async browser => { + browser = await chromium.launch() + page = await browser.newPage() + await page.goto(baseUrl + "/contact") + }) + + test.afterEach(async () => { + await page.close() + }) + + test("renders correctly", async () => { + await page.waitForSelector("main h1:text('feedback')") + await page.waitForSelector("main p:text('NASA CASEI inventory')") + }) +}) diff --git a/playwright/e2e/platform.spec.ts b/playwright/e2e/platform.spec.ts index bb0047c48..9b1c3c19a 100644 --- a/playwright/e2e/platform.spec.ts +++ b/playwright/e2e/platform.spec.ts @@ -1,112 +1,125 @@ -const { test, expect } = require('@playwright/test'); -import config from "../playwright.config"; -const baseUrl = config.use?.baseURL; +const { test, expect } = require("@playwright/test") +import config from "../playwright.config" +const baseUrl = config.use?.baseURL test.describe("Platform", () => { - - let page; - - test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); - await page.goto(baseUrl); - }); - - test.beforeEach(async ({ page }) => { - await page.goto(`${baseUrl}/platform/DC-8`); - }); - - test("explains the platform", async ({ page }) => { - await page.waitForSelector("[data-cy=platform-hero]"); - const platformHero = await page.$("[data-cy=platform-hero]"); - - // Check platform name - const platformName = await platformHero.$eval("h1", el => el.textContent); - expect(platformName).toContain("Douglas DC-8"); - - // Check platform image - const platformImage = await platformHero.$("img"); - expect(platformImage).toBeTruthy(); - expect(await platformImage.isVisible()).toBeTruthy(); - - // Check platform stats - const statsList = await platformHero.$("[data-cy=stats]"); - const statsItems = await statsList.$$("dd"); - expect(statsItems.length).toBe(2); - expect(await statsItems[0].textContent()).toContain("Campaigns"); - expect(await statsItems[1].textContent()).toContain("Collection Periods"); - - // Check inpage nav - const inpageNav = await page.$("[data-cy=inpage-nav]"); - const inpageNavLinks = await inpageNav.$$("a"); - expect(inpageNavLinks.length).toBe(4); - expect(await inpageNavLinks[0].isVisible()).toBeTruthy(); - expect(await inpageNavLinks[1].textContent()).toContain("Overview"); - expect(await inpageNavLinks[2].textContent()).toContain( - "Related Campaigns & Instruments" - ); - expect(await inpageNavLinks[3].textContent()).toContain("Data"); - - // Check overview section - const overviewSection = await page.$("[data-cy=overview-section]"); - expect(await overviewSection.isVisible()).toBeTruthy(); - expect(await overviewSection.$eval("h2", el => el.textContent)).toContain( - "Overview" - ); - expect(await overviewSection.$eval("h3", el => el.textContent)).toContain( - "Overview" - ); - const linkList = await overviewSection.$("[data-cy=link-list]"); - const linkListItems = await linkList.$$("li"); - expect(linkListItems.length).toBeGreaterThanOrEqual(0); - expect(await overviewSection.$("p")).toBeTruthy(); - - // Check data section - const dataSection = await page.$("[data-cy=data-section]"); - expect(await dataSection.isVisible()).toBeTruthy(); - expect(await dataSection.$eval("h2", el => el.textContent)).toContain( - "Data Products" - ); - const dataProducts = await dataSection.$$("[data-cy=data-product]"); // returns a list of elementHandles - - expect(await dataProducts).toHaveLength(dataProducts.length); - - const doiLabel = dataProducts[0].$("[data-cy=doi-label]"); - const doiLink = dataProducts[0].$("[data-cy=doi-link]"); - expect(doiLabel).toBeTruthy(); - expect(doiLink).toBeTruthy(); - const campaignsLabel = await dataProducts[0].$( - "[data-cy=doi-campaign-label]" - ); - const instrumentsLabel = await dataProducts[0].$( - "[data-cy=doi-instrument-label]" - ); - expect(campaignsLabel).toBeTruthy(); - expect(await campaignsLabel.textContent()).toContain("Campaigns"); - expect(instrumentsLabel).toBeTruthy(); - expect(await instrumentsLabel.textContent()).toContain("Instruments"); - - await page.waitForSelector("[data-cy=campaigns-instruments-section] h2", { visible: true }); - expect(await page.$eval("[data-cy=campaigns-instruments-section] h2", el => el.textContent)).toContain("Related Campaigns & Instruments"); - - await page.waitForSelector("[data-cy=campaign-carousel] .slider"); - await page.waitForSelector("[data-cy=campaigns-card]"); - await page.click("[data-cy=campaigns-card]:first-child"); - await page.waitForURL(/campaign/); - await page.goBack(); - - await page.waitForSelector("[data-cy=campaign-carousel] .slider-control-centerright > button"); - - await page.waitForSelector("[data-cy=instrument-accordion]", { visible: true }); - await page.click("[data-cy=instrument-accordion] [data-cy=accordion-button]:first-child"); - await page.waitForSelector("[data-cy=instrument-accordion-content] [data-cy=instrument-accordion-image-description]"); - await page.waitForSelector("[data-cy=accordion-measurements-label]"); - await page.waitForSelector("[data-cy=accordion-link]"); - await Promise.all([ - page.waitForNavigation(), - page.click("[data-cy=accordion-link]:first-child") - ]); - expect(page.url()).toContain("instrument"); - await page.goBack(); - }); -}); - + let page + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + await page.goto(baseUrl) + }) + + test.beforeEach(async ({ page }) => { + await page.goto(`${baseUrl}/platform/DC-8`) + }) + + test("explains the platform", async ({ page }) => { + await page.waitForSelector("[data-cy=platform-hero]") + const platformHero = await page.$("[data-cy=platform-hero]") + + // Check platform name + const platformName = await platformHero.$eval("h1", el => el.textContent) + expect(platformName).toContain("Douglas DC-8") + + // Check platform image + const platformImage = await platformHero.$("img") + expect(platformImage).toBeTruthy() + expect(await platformImage.isVisible()).toBeTruthy() + + // Check platform stats + const statsList = await platformHero.$("[data-cy=stats]") + const statsItems = await statsList.$$("dd") + expect(statsItems.length).toBe(2) + expect(await statsItems[0].textContent()).toContain("Campaigns") + expect(await statsItems[1].textContent()).toContain("Data Products") + + // Check inpage nav + const inpageNav = await page.$("[data-cy=inpage-nav]") + const inpageNavLinks = await inpageNav.$$("a") + expect(inpageNavLinks.length).toBe(4) + expect(await inpageNavLinks[0].isVisible()).toBeTruthy() + expect(await inpageNavLinks[1].textContent()).toContain("Overview") + expect(await inpageNavLinks[2].textContent()).toContain( + "Related Campaigns & Instruments" + ) + expect(await inpageNavLinks[3].textContent()).toContain("Data") + + // Check overview section + const overviewSection = await page.$("[data-cy=overview-section]") + expect(await overviewSection.isVisible()).toBeTruthy() + expect(await overviewSection.$eval("h2", el => el.textContent)).toContain( + "Overview" + ) + expect(await overviewSection.$eval("h3", el => el.textContent)).toContain( + "Overview" + ) + const linkList = await overviewSection.$("[data-cy=link-list]") + const linkListItems = await linkList.$$("li") + expect(linkListItems.length).toBeGreaterThanOrEqual(0) + expect(await overviewSection.$("p")).toBeTruthy() + + // Check data section + const dataSection = await page.$("[data-cy=data-section]") + expect(await dataSection.isVisible()).toBeTruthy() + expect(await dataSection.$eval("h2", el => el.textContent)).toContain( + "Data Products" + ) + const dataProducts = await dataSection.$$("[data-cy=data-product]") // returns a list of elementHandles + + expect(await dataProducts).toHaveLength(dataProducts.length) + + const doiLabel = dataProducts[0].$("[data-cy=doi-label]") + const doiLink = dataProducts[0].$("[data-cy=doi-link]") + expect(doiLabel).toBeTruthy() + expect(doiLink).toBeTruthy() + const campaignsLabel = await dataProducts[0].$( + "[data-cy=doi-campaign-label]" + ) + const instrumentsLabel = await dataProducts[0].$( + "[data-cy=doi-instrument-label]" + ) + expect(campaignsLabel).toBeTruthy() + expect(await campaignsLabel.textContent()).toContain("Campaigns") + expect(instrumentsLabel).toBeTruthy() + expect(await instrumentsLabel.textContent()).toContain("Instruments") + + await page.waitForSelector("[data-cy=campaigns-instruments-section] h2", { + visible: true, + }) + expect( + await page.$eval( + "[data-cy=campaigns-instruments-section] h2", + el => el.textContent + ) + ).toContain("Related Campaigns & Instruments") + + await page.waitForSelector("[data-cy=campaign-carousel] .slider") + await page.waitForSelector("[data-cy=campaigns-card]") + await page.click("[data-cy=campaigns-card]:first-child") + await page.waitForURL(/campaign/) + await page.goBack() + + await page.waitForSelector( + "[data-cy=campaign-carousel] .slider-control-centerright > button" + ) + + await page.waitForSelector("[data-cy=instrument-accordion]", { + visible: true, + }) + await page.click( + "[data-cy=instrument-accordion] [data-cy=accordion-button]:first-child" + ) + await page.waitForSelector( + "[data-cy=instrument-accordion-content] [data-cy=instrument-accordion-image-description]" + ) + await page.waitForSelector("[data-cy=accordion-measurements-label]") + await page.waitForSelector("[data-cy=accordion-link]") + await Promise.all([ + page.waitForNavigation(), + page.click("[data-cy=accordion-link]:first-child"), + ]) + expect(page.url()).toContain("instrument") + await page.goBack() + }) +}) diff --git a/src/components/__tests__/__snapshots__/data-section.test.js.snap b/src/components/__tests__/__snapshots__/data-section.test.js.snap index c650d32af..2d16a6dd7 100644 --- a/src/components/__tests__/__snapshots__/data-section.test.js.snap +++ b/src/components/__tests__/__snapshots__/data-section.test.js.snap @@ -32,8 +32,10 @@ exports[`Data Section matches snapshot 1`] = ` by specific campaigns - or + , platforms + , or + formats .

- NASA | CASEI - is a comprehensive inventory containing information about all airborne and field campaigns as well as aircrafts, instruments, and data products. + CASEI is a comprehensive inventory of holistic contextual + information for NASA's Earth Science airborne and field campaigns, + including details on instruments, aircraft and other platforms, and + access to data products.

{ ).toJSON() diff --git a/src/components/data-section.js b/src/components/data-section.js index 30dbd3624..c4eb6802f 100644 --- a/src/components/data-section.js +++ b/src/components/data-section.js @@ -13,28 +13,64 @@ import FilterBox from "./filter/filter-box" import Chip from "./chip" const DataSection = ({ id, dois, filterBy, category }) => { - const [filter1, filter2] = filterBy + const [filter1, filter2, filter3] = filterBy let [selectedFilterIds, setSelectedFilterIds] = useState([]) + // manually parse the data formats string to derive array of objects + const parsedDois = dois.map(doi => ({ + ...doi, + formats: + doi.formats && !doi.formats.includes("null") && doi.formats.split("[")[1] + ? doi.formats + ?.split("[")[1] + .split("]")[0] + .split(",") + .map(s => + s.replace(/[^a-zA-Z ]/g, "").replace(/^\s+|\s+$|\s+(?=\s)/g, "") + ) + .filter(f => f !== "") + .map(format => ({ + id: format, + shortname: format, + longname: format, + })) + : [], + })) + const clearFilters = () => setSelectedFilterIds([]) const removeFilter = id => setSelectedFilterIds(selectedFilterIds.filter(f => f !== id)) const filteredDois = selectedFilterIds.length - ? dois.filter(doiFilter(selectedFilterIds)) - : dois + ? parsedDois.filter(doiFilter(selectedFilterIds)) + : parsedDois const campaignList = uniqueElementsById(dois.map(doi => doi.campaigns).flat()) const platformList = uniqueElementsById(dois.map(doi => doi.platforms).flat()) const instrumentList = uniqueElementsById( dois.map(doi => doi.instruments).flat() ) + const formatList = parsedDois.reduce( + (acc, doi) => { + if (doi.formats.length > 0) { + for (const format of doi.formats) { + if (!acc.unique.has(format.id)) { + acc.unique.add(format.id) + acc.values.push(format) + } + } + } + return acc + }, + { unique: new Set(), values: [] } + ).values const { getFilterLabelById } = selector({ campaign: { options: campaignList }, instrument: { options: instrumentList }, platform: { options: platformList }, + format: { options: formatList }, }) return ( @@ -43,12 +79,15 @@ const DataSection = ({ id, dois, filterBy, category }) => { {dois.length ? ( <> - {campaignList.concat(platformList.concat(instrumentList)).length > + {campaignList.length + + platformList.length + + instrumentList.length + + formatList.length > 0 && ( <>

Filter data products from this {category} by specific{" "} - {filter1} or {filter2}. + {filter1}, {filter2}, or {filter3}.

{ selectedFilterIds={selectedFilterIds} /> )} + {filterBy.includes("formats") && !!formatList.length && ( + + )}
)} @@ -108,104 +155,126 @@ const DataSection = ({ id, dois, filterBy, category }) => { gap: 1rem; `} > - {filteredDois.map(doi => ( -
-
- - {doi.doi && ( - + {filteredDois.map(doi => { + return ( +
+
+ + {doi.doi && ( + + )} +
+ + {doi[filter1].length + + doi[filter2].length + + doi[filter3].length ? ( +
+ {filterBy.includes("campaigns") && + !!doi.campaigns.length && ( +
+ + {doi.campaigns.map((campaign, index) => ( + + + + {campaign.longname || campaign.shortname} + + + {index !== doi.campaigns.length - 1 && ", "} + + ))} +
+ )} + {filterBy.includes("platforms") && + !!doi.platforms.length && ( +
+ + {doi.platforms.map((platform, index) => ( + + + + {platform.longname || platform.shortname} + + + {index !== doi.platforms.length - 1 && ", "} + + ))} +
+ )} + + {filterBy.includes("instruments") && + !!doi.instruments.length && ( +
+ + {doi.instruments.map((instrument, index) => ( + + + + {instrument.longname || + instrument.shortname} + + + {index !== doi.instruments.length - 1 && ", "} + + ))} +
+ )} + {filterBy.includes("formats") && + doi.formats && + doi.formats.length > 0 && ( +
+ + {doi.formats?.map((format, index) => ( + + + {format.shortname} + + {index !== doi.formats.length - 1 && ", "} + + ))} +
+ )} +
+ ) : ( + + No related {filter1}, {filter2} or {filter3} + )}
- - {doi[filter1].concat(doi[filter2]).length ? ( -
- {filterBy.includes("campaigns") && - !!doi.campaigns.length && ( -
- - {doi.campaigns.map((campaign, index) => ( - - - - {campaign.longname || campaign.shortname} - - - {index !== doi.campaigns.length - 1 && ", "} - - ))} -
- )} - {filterBy.includes("platforms") && - !!doi.platforms.length && ( -
- - {doi.platforms.map((platform, index) => ( - - - - {platform.longname || platform.shortname} - - - {index !== doi.platforms.length - 1 && ", "} - - ))} -
- )} - {filterBy.includes("instruments") && - !!doi.instruments.length && ( -
- - {doi.instruments.map((instrument, index) => ( - - - - {instrument.longname || - instrument.shortname} - - - {index !== doi.instruments.length - 1 && ", "} - - ))} -
- )} -
- ) : ( - - No related {filter1} or {filter2} - - )} -
- ))} + ) + })}
) : ( @@ -233,6 +302,7 @@ DataSection.propTypes = { longname: PropTypes.string.isRequired, }) ), + data_formats: PropTypes.arrayOf(PropTypes.string), instruments: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, @@ -243,9 +313,9 @@ DataSection.propTypes = { }) ).isRequired, filterBy: PropTypes.arrayOf( - PropTypes.oneOf(["campaigns", "platforms", "instruments"]) + PropTypes.oneOf(["campaigns", "platforms", "instruments", "formats"]) ), - category: PropTypes.oneOf(["campaign", "platform", "instrument"]), + category: PropTypes.oneOf(["campaign", "platform", "instrument", "format"]), } export default DataSection diff --git a/src/components/explore/explore-map.js b/src/components/explore/explore-map.js index a20c36be3..bd06098f3 100644 --- a/src/components/explore/explore-map.js +++ b/src/components/explore/explore-map.js @@ -82,7 +82,7 @@ const ExploreMap = ({ allData, filteredData, setGeoFilter, aoi, setAoi }) => { aoi={aoi} setAoi={setAoi} /> - + diff --git a/src/components/explore/explore-tools.js b/src/components/explore/explore-tools.js index 3394aeca9..c7fd5b03b 100644 --- a/src/components/explore/explore-tools.js +++ b/src/components/explore/explore-tools.js @@ -155,7 +155,7 @@ const ExploreTools = React.forwardRef( selectedFilterIds={selectedFilterIds} addFilter={addFilter} removeFilter={removeFilter} - label="Measurement Types" + label="gcmd" options={getFilterOptionsById("measurement")} secondaryOptions={getFilterOptionsById("gcmd")} category={category} @@ -163,17 +163,44 @@ const ExploreTools = React.forwardRef( getMatchTerm={option => option.shortname} getFilterOptions={getGcmdOptions} filterValue={option => option.shortname} - filterLayoutWidth={"60%"} + filterLayoutWidth={"50%"} + hasLinkOut={true} /> + + item !== "") .join(" ")}` } - filterLayoutWidth={"40%"} + filterLayoutWidth={"41.75%"} /> - - - )} @@ -281,6 +290,14 @@ const ExploreTools = React.forwardRef( label="Measurement Type" options={getFilterOptionsById("type")} /> + + )} diff --git a/src/components/explore/filter-text-dropdown.js b/src/components/explore/filter-text-dropdown.js index 5bf3337f4..e53fd4c0e 100644 --- a/src/components/explore/filter-text-dropdown.js +++ b/src/components/explore/filter-text-dropdown.js @@ -1,7 +1,7 @@ import React, { useState } from "react" import PropTypes from "prop-types" -import { CloseIcon, SearchIcon } from "../../icons" +import { CloseIcon, SearchIcon, InformationIcon } from "../../icons" import { NEGATIVE, POSITIVE } from "../../utils/constants" import { colors } from "../../theme" import { @@ -58,6 +58,7 @@ const DropdownByTextInput = ({ getFilterOptions, filterValue, filterLayoutWidth, + hasLinkOut, }) => { const [term, setTerm] = useState("") @@ -114,7 +115,23 @@ const DropdownByTextInput = ({ placeholder={placeholder} style={{ border: "unset" }} /> + {hasLinkOut && !term && ( + + + + )} + {filteredOptions && ( {filteredOptions.length > 0 ? ( @@ -187,6 +204,7 @@ DropdownByTextInput.propTypes = { getFilterOptions: PropTypes.func, filterValue: PropTypes.func, filterLayoutWidth: PropTypes.string, + hasLinkOut: PropTypes.bool, } // https://reactjs.org/docs/forwarding-refs.html#displaying-a-custom-name-in-devtools diff --git a/src/components/explore/products-table.js b/src/components/explore/products-table.js index 6a77cbc93..e53be28cc 100644 --- a/src/components/explore/products-table.js +++ b/src/components/explore/products-table.js @@ -5,9 +5,12 @@ import { NEGATIVE } from "../../utils/constants" import { colors } from "../../theme" import { ExternalLinkIcon } from "../../icons" import { Link } from "gatsby" +import { Tooltip } from "@reach/tooltip" +import "@reach/tooltip/styles.css" export function ProductsTable({ dois }) { const linkLimit = 3 + return ( {" "} - {doi.doi} + {doi.shortname}
- {doi.platforms.map((item, idx) => { - if (idx < linkLimit) { - const spacer = - idx < Math.min(doi.platforms.length, linkLimit) - 1 - ? ", " - : "" - return ( - - - {item.shortname + spacer} - - - ) - } - })} + {doi.platforms.length ? ( + doi.platforms.map((item, idx) => { + if (idx < linkLimit) { + const spacer = + idx < Math.min(doi.platforms.length, linkLimit) - 1 + ? ", " + : "" + return ( + + + {item.shortname + spacer} + + + ) + } + }) + ) : ( + + {"---"} + + )} {doi.platforms.length > linkLimit ? ( <>{`, +${doi.platforms.length - linkLimit}`} @@ -91,27 +110,44 @@ export function ProductsTable({ dois }) { - {doi.instruments.map((item, idx) => { - if (idx < linkLimit) { - const spacer = - idx < Math.min(doi.instruments.length, linkLimit) - 1 - ? ", " - : "" - return ( - - - {item.shortname + spacer} - - - ) - } - })} + {doi.instruments.length ? ( + doi.instruments.map((item, idx) => { + if (idx < linkLimit) { + const spacer = + idx < + Math.min(doi.instruments.length, linkLimit) - 1 + ? ", " + : "" + return ( + + + {item.shortname + spacer} + + + ) + } + }) + ) : ( + + {"---"} + + )} {doi.instruments.length > linkLimit ? ( <>{`, +${doi.instruments.length - linkLimit}`} diff --git a/src/components/footer.js b/src/components/footer.js index 562a0f61c..f0ca774a6 100644 --- a/src/components/footer.js +++ b/src/components/footer.js @@ -56,9 +56,10 @@ const Footer = ({ shortname }) => { {shortname}

- {shortname} is a comprehensive inventory containing information - about all airborne and field campaigns as well as aircrafts, - instruments, and data products. + {`CASEI is a comprehensive inventory of holistic contextual + information for NASA's Earth Science airborne and field campaigns, + including details on instruments, aircraft and other platforms, and + access to data products.`}

diff --git a/src/components/layout/section.js b/src/components/layout/section.js index 411d6c1d2..b24a7078e 100644 --- a/src/components/layout/section.js +++ b/src/components/layout/section.js @@ -59,7 +59,8 @@ export const SectionContent = styled.div` withBackground ? colors[mode].background : null}; max-width: 100%; min-height: ${({ minHeight }) => (minHeight ? minHeight : null)}; - padding: ${({ withPadding }) => (withPadding ? `5rem` : null)}; + padding: ${({ withPadding, slimPadding }) => + slimPadding ? `2rem 3rem` : withPadding ? `5rem` : null}; > *, h3 { diff --git a/src/components/map/geojson-source.js b/src/components/map/geojson-source.js index 45bd72e89..97df94175 100644 --- a/src/components/map/geojson-source.js +++ b/src/components/map/geojson-source.js @@ -2,7 +2,13 @@ import React, { useEffect, useState } from "react" import PropTypes from "prop-types" // GeoJsonSource component -export default function GeoJsonSource({ geojson, id, map, children }) { +export default function GeoJsonSource({ + geojson, + id, + map, + children, + isDrawing, +}) { const [source, setSource] = useState(null) // Effect to handle component unmounting @@ -10,7 +16,9 @@ export default function GeoJsonSource({ geojson, id, map, children }) { // Clean up function to remove source from map when the component is unmounted return () => { // TODO Error: Source "explore-source" cannot be removed while layer "explore-hover-layer" is using it. - map.removeSource(`${id}-source`) + if (isDrawing) { + map.removeSource(`${id}-source`) + } } }, []) @@ -57,4 +65,5 @@ GeoJsonSource.propTypes = { PropTypes.element, PropTypes.arrayOf(PropTypes.element), ]), + isDrawing: PropTypes.bool, } diff --git a/src/components/map/hover-layer.js b/src/components/map/hover-layer.js index 77b412a0e..4024a753e 100644 --- a/src/components/map/hover-layer.js +++ b/src/components/map/hover-layer.js @@ -54,7 +54,7 @@ export default function HoverLayer({ id, map, sourceId, isDrawing }) { id: `${id}-hover-layer`, type: "fill", source: sourceId, - layout: {}, + layout: { visibility: isDrawing ? "visible" : "none" }, // control visibility here}, paint: { "fill-color": colors[POSITIVE].linkText, "fill-opacity": [ diff --git a/src/components/no-results-message.js b/src/components/no-results-message.js index 73bcb63ea..5ce47612b 100644 --- a/src/components/no-results-message.js +++ b/src/components/no-results-message.js @@ -1,4 +1,7 @@ +import { Link } from "gatsby" import React from "react" +import { colors } from "../theme" +import { NEGATIVE } from "../utils/constants" export const NoResultsMessage = () => (
( `} >

No results found in the CASEI metadata inventory—yet!

-

- We are constantly adding curated metadata to the inventory, so please - check back soon! -

+ + We are constantly adding curated metadata to the inventory, so please + check back soon! + +

Search Tips