From 12f13a679bc3570c6c97c4ce407be2c86a2faf06 Mon Sep 17 00:00:00 2001 From: Benjamin Poreh Date: Thu, 16 Nov 2023 13:59:07 -0500 Subject: [PATCH] EDSC-3797: Added an option for concatenation for custom downloads via Harmony (#1693) * EDSC-3797: made initial implementation of the code * EDSC-3797: added tests. * EDSC-3797: supportsConcat test * EDSC-3797: fixed up the issues for default stuff and added the necessary tests. * small fix * fix * EDSC-3797: changed the newly added gray-text class to use text-muted * removed console.logs * EDSC-3797: pushing up changes so that enableConcatenateDownload is not undefined when the lambda job is created. * removed console.log * EDSC-3797: added tests to make sure behavior is consistent when aggregation exists but concatenate does not. * Fixed buildAccessMethods test * EDSC-3797: re-added deleted test * removed console.log * EDSC-3797: addresses Matthew's comments * EDSC-3797: changed the wording on the description of the concatenation test for constructing the orderPayload * EDSC-3797: slight change to one of the test descriptions * EDSC-3797: added encoder and decoder for concatenateDownload. * EDSC-3797: added tests to check concatenate Download variable in the url works as expected. * EDSC-3797: set enabledConcatenateDownload to be equal to the defaultConcatenate initially. * Update static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js Co-authored-by: Ed Olivares <34591886+eudoroolivares2016@users.noreply.github.com> * EDSC-3797: reverted certain variables * EDSC-3797: updated the wording and moved around tests to match best practices. * spelling mistake fix --------- Co-authored-by: Ed Olivares <34591886+eudoroolivares2016@users.noreply.github.com> --- .../__tests__/constructOrderPayload.test.js | 111 ++++++++++++++++ .../constructOrderPayload.js | 9 +- .../components/AccessMethod/AccessMethod.js | 75 ++++++++++- .../__tests__/AccessMethod.test.js | 85 +++++++++++- .../__tests__/buildAccessMethods.test.js | 11 ++ .../__tests__/defaultConcatenation.test.js | 50 ++++++++ .../__tests__/supportsConcatenation.test.js | 59 +++++++++ .../util/accessMethods/buildAccessMethods.js | 5 + .../accessMethods/defaultConcatenation.js | 17 +++ .../supportsBoundingBoxSubsetting.js | 2 +- .../accessMethods/supportsConcatenation.js | 14 ++ .../supportsShapefileSubsetting.js | 2 +- .../supportsTemporalSubsetting.js | 2 +- static/src/js/util/retrievals.js | 4 + .../url/__tests__/projectCollections.test.js | 121 +++++++++++++++++- static/src/js/util/url/collectionsEncoders.js | 36 +++++- 16 files changed, 583 insertions(+), 20 deletions(-) create mode 100644 static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js create mode 100644 static/src/js/util/accessMethods/__tests__/supportsConcatenation.test.js create mode 100644 static/src/js/util/accessMethods/defaultConcatenation.js create mode 100644 static/src/js/util/accessMethods/supportsConcatenation.js diff --git a/serverless/src/submitHarmonyOrder/__tests__/constructOrderPayload.test.js b/serverless/src/submitHarmonyOrder/__tests__/constructOrderPayload.test.js index f005867542..b7909f16da 100644 --- a/serverless/src/submitHarmonyOrder/__tests__/constructOrderPayload.test.js +++ b/serverless/src/submitHarmonyOrder/__tests__/constructOrderPayload.test.js @@ -693,4 +693,115 @@ describe('constructOrderPayload', () => { }) }) }) + + describe('when supportsConcatenation = true and enableConcatenateDownload = true', () => { + test('constructs a payload containing concatenate = true', async () => { + nock(/cmr/) + .matchHeader('Authorization', 'Bearer access-token') + .get('/search/granules.json?point%5B%5D=-77%2C%2034') + .reply(200, { + feed: { + entry: [{ + id: 'G10000001-EDSC' + }, { + id: 'G10000005-EDSC' + }] + } + }) + + const granuleParams = { + point: ['-77, 34'] + } + + const accessMethod = { + supportsConcatenation: true, + enableConcatenateDownload: true + } + + const accessToken = 'access-token' + + const response = await constructOrderPayload({ + accessMethod, + granuleParams, + accessToken + }) + + expect(response.getAll('concatenate')).toEqual([ + 'true' + ]) + }) + }) + + describe('when supportsConcatenation = false or enableConcatenateDownload = false', () => { + test('constructed payload does not contain concatenate', async () => { + nock(/cmr/) + .matchHeader('Authorization', 'Bearer access-token') + .get('/search/granules.json?point%5B%5D=-77%2C%2034') + .reply(200, { + feed: { + entry: [{ + id: 'G10000001-EDSC' + }, { + id: 'G10000005-EDSC' + }] + } + }) + + const granuleParams = { + point: ['-77, 34'] + } + + const accessMethod = { + supportsConcatenation: true, + enableConcatenateDownload: false + } + + const accessToken = 'access-token' + + const response = await constructOrderPayload({ + accessMethod, + granuleParams, + accessToken + }) + + expect(response.getAll('concatenate')).not.toEqual([ + 'true' + ]) + }) + + test('constructed payload does not contain concatenate', async () => { + nock(/cmr/) + .matchHeader('Authorization', 'Bearer access-token') + .get('/search/granules.json?point%5B%5D=-77%2C%2034') + .reply(200, { + feed: { + entry: [{ + id: 'G10000001-EDSC' + }, { + id: 'G10000005-EDSC' + }] + } + }) + + const granuleParams = { + point: ['-77, 34'] + } + + const accessMethod = { + supportsConcatenation: false + } + + const accessToken = 'access-token' + + const response = await constructOrderPayload({ + accessMethod, + granuleParams, + accessToken + }) + + expect(response.getAll('concatenate')).not.toEqual([ + 'true' + ]) + }) + }) }) diff --git a/serverless/src/submitHarmonyOrder/constructOrderPayload.js b/serverless/src/submitHarmonyOrder/constructOrderPayload.js index 6d25616e8f..ee83110514 100644 --- a/serverless/src/submitHarmonyOrder/constructOrderPayload.js +++ b/serverless/src/submitHarmonyOrder/constructOrderPayload.js @@ -47,11 +47,13 @@ export const constructOrderPayload = async ({ const { enableTemporalSubsetting, enableSpatialSubsetting, + enableConcatenateDownload, mbr, selectedOutputFormat, selectedOutputProjection, supportsBoundingBoxSubsetting, - supportsShapefileSubsetting + supportsShapefileSubsetting, + supportsConcatenation } = accessMethod // OGC uses duplicate parameter names for subsetting and the @@ -215,6 +217,11 @@ export const constructOrderPayload = async ({ orderPayload.append('outputCrs', selectedOutputProjection) } + // Adds supportsConcatenation to the payload and it's value + if (supportsConcatenation && enableConcatenateDownload) { + orderPayload.append('concatenate', true) + } + // EDSC-3440: Add skipPreview=true to all Harmony orders orderPayload.append('skipPreview', true) diff --git a/static/src/js/components/AccessMethod/AccessMethod.js b/static/src/js/components/AccessMethod/AccessMethod.js index d6084e163c..c6e708ef47 100644 --- a/static/src/js/components/AccessMethod/AccessMethod.js +++ b/static/src/js/components/AccessMethod/AccessMethod.js @@ -61,6 +61,10 @@ export class AccessMethod extends Component { enableTemporalSubsetting = !isRecurring } = selectedMethod || {} + const { + enableConcatenateDownload + } = selectedMethod || false + const { enableSpatialSubsetting = !( boundingBox === undefined @@ -73,7 +77,8 @@ export class AccessMethod extends Component { this.state = { enableTemporalSubsetting, - enableSpatialSubsetting + enableSpatialSubsetting, + enableConcatenateDownload } this.handleAccessMethodSelection = this.handleAccessMethodSelection.bind(this) @@ -81,6 +86,7 @@ export class AccessMethod extends Component { this.handleOutputProjectionSelection = this.handleOutputProjectionSelection.bind(this) this.handleToggleTemporalSubsetting = this.handleToggleTemporalSubsetting.bind(this) this.handleToggleSpatialSubsetting = this.handleToggleSpatialSubsetting.bind(this) + this.handleConcatenationSelection = this.handleConcatenationSelection.bind(this) } UNSAFE_componentWillReceiveProps() { @@ -141,6 +147,25 @@ export class AccessMethod extends Component { }) } + handleConcatenationSelection(event) { + const { metadata, onUpdateAccessMethod, selectedAccessMethod } = this.props + const { conceptId: collectionId } = metadata + + const { target } = event + const { checked } = target + + this.setState({ enableConcatenateDownload: checked }) + + onUpdateAccessMethod({ + collectionId, + method: { + [selectedAccessMethod]: { + enableConcatenateDownload: checked + } + } + }) + } + handleToggleTemporalSubsetting(event) { const { metadata, onUpdateAccessMethod, selectedAccessMethod } = this.props const { conceptId: collectionId } = metadata @@ -207,11 +232,6 @@ export class AccessMethod extends Component { } render() { - const { - enableTemporalSubsetting, - enableSpatialSubsetting - } = this.state - const { accessMethods, index, @@ -337,9 +357,17 @@ export class AccessMethod extends Component { supportsTemporalSubsetting = false, supportsShapefileSubsetting = false, supportsBoundingBoxSubsetting = false, - supportsVariableSubsetting = false + supportsVariableSubsetting = false, + supportsConcatenation = false, + defaultConcatenation = false } = selectedMethod || {} + const { + enableTemporalSubsetting, + enableSpatialSubsetting, + enableConcatenateDownload = defaultConcatenation + } = this.state + const isOpendap = (selectedAccessMethod && selectedAccessMethod === 'opendap') // Harmony access methods are postfixed with an index given that there can be more than one @@ -392,6 +420,7 @@ export class AccessMethod extends Component { || supportsBoundingBoxSubsetting || supportedOutputFormatOptions.length > 0 || supportedOutputProjectionOptions.length > 0 + || supportsConcatenation || (form && isActive) const { @@ -496,6 +525,38 @@ export class AccessMethod extends Component { ) } + { + supportsConcatenation && ( + + + + + Enable Concatenation + + + Data will be concatenated along a newly created dimension + + + ) + } + checked={enableConcatenateDownload} + disabled={isRecurring} + onChange={this.handleConcatenationSelection} + /> + + + ) + } { supportsTemporalSubsetting && ( { const checkbox = screen.getByRole('checkbox') await user.click(checkbox) - expect(screen.getByRole('checkbox').checked).toEqual(true) + expect(checkbox.checked).toEqual(true) }) test('calls onUpdateAccessMethod', async () => { @@ -1140,6 +1140,89 @@ describe('AccessMethod component', () => { expect(screen.getByText(/using the harmony-service-name/)).toBeInTheDocument() }) }) + + describe('when the service type is `Harmony` and concatenation is available', () => { + test('the `Combine Data` option is avaialable when concatenation service is true', () => { + const collectionId = 'collectionId' + const serviceName = 'harmony-service-name' + setup({ + accessMethods: { + harmony0: { + isValid: true, + type: 'Harmony', + name: serviceName, + supportsConcatenation: true, + defaultConcatenation: true + } + }, + metadata: { + conceptId: collectionId + }, + selectedAccessMethod: 'harmony0' + }) + + expect(screen.getByText(/The requested data will be processed/)).toBeInTheDocument() + expect(screen.getByText(/Combine Data/)).toBeInTheDocument() + expect(screen.getByRole('checkbox').checked).toEqual(true) + }) + + test('when the `Combine Data` option is clicked, the enableConcatenateDownload changes', async () => { + const user = userEvent.setup() + const collectionId = 'collectionId' + const serviceName = 'harmony-service-name' + const { onUpdateAccessMethod } = setup({ + accessMethods: { + harmony0: { + isValid: true, + type: 'Harmony', + name: serviceName, + supportsConcatenation: true, + defaultConcatenation: false + } + }, + metadata: { + conceptId: collectionId + }, + selectedAccessMethod: 'harmony0' + }) + + expect(screen.getByText(/The requested data will be processed/)).toBeInTheDocument() + expect(screen.getByText(/Combine Data/)).toBeInTheDocument() + await user.click(screen.getByRole('checkbox')) + + expect(onUpdateAccessMethod).toHaveBeenCalledTimes(1) + expect(onUpdateAccessMethod).toHaveBeenCalledWith({ + collectionId: 'collectionId', + method: { harmony0: { enableConcatenateDownload: true } } + }) + + expect(screen.getByRole('checkbox').checked).toEqual(true) + }) + }) + + describe('when the service type is `Harmony` and concatenation is unavailable', () => { + test('when the `Combine Data` option is clicked, the enableConcatenateDownload changes', async () => { + const collectionId = 'collectionId' + const serviceName = 'harmony-service-name' + setup({ + accessMethods: { + harmony0: { + isValid: true, + type: 'Harmony', + name: serviceName, + supportsConcatenation: false, + enableConcatenateDownload: false + } + }, + metadata: { + conceptId: collectionId + }, + selectedAccessMethod: 'harmony0' + }) + + expect(screen.queryAllByText(/Combine Data/)).toHaveLength(0) + }) + }) }) }) }) diff --git a/static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.js b/static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.js index 84308dce68..7a1d7e248a 100644 --- a/static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.js +++ b/static/src/js/util/accessMethods/__tests__/buildAccessMethods.test.js @@ -132,6 +132,11 @@ describe('buildAccessMethods', () => { allowMultipleValues: true } }, + aggregation: { + concatenate: { + concatenateDefault: true + } + }, supportedOutputProjections: [{ projectionName: 'Polar Stereographic' }, { @@ -258,6 +263,9 @@ describe('buildAccessMethods', () => { supportsShapefileSubsetting: false, supportsTemporalSubsetting: false, supportsVariableSubsetting: true, + supportsConcatenation: true, + defaultConcatenation: true, + enableConcatenateDownload: true, type: 'Harmony', url: 'https://example.com', variables: { @@ -667,6 +675,8 @@ describe('buildAccessMethods', () => { expect(methods).toEqual({ harmony0: { + defaultConcatenation: false, + enableConcatenateDownload: false, enableTemporalSubsetting: true, enableSpatialSubsetting: true, hierarchyMappings: [ @@ -696,6 +706,7 @@ describe('buildAccessMethods', () => { ], supportedOutputProjections: [], supportsBoundingBoxSubsetting: true, + supportsConcatenation: false, supportsShapefileSubsetting: false, supportsTemporalSubsetting: false, supportsVariableSubsetting: true, diff --git a/static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js b/static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js new file mode 100644 index 0000000000..51fec7659c --- /dev/null +++ b/static/src/js/util/accessMethods/__tests__/defaultConcatenation.test.js @@ -0,0 +1,50 @@ +import { defaultConcatenation } from '../defaultConcatenation' + +describe('defaultConcatenation', () => { + describe('when concatenation is checked by default', () => { + test('returns true', () => { + const response = defaultConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: { + aggregation: { + concatenate: { + concatenateDefault: true + } + } + } + }) + + expect(response).toBeTruthy() + }) + }) + + describe('when concatenation is not in aggregation', () => { + test('returns false', () => { + const response = defaultConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: { + aggregation: {} + } + }) + + expect(response).toBeFalsy() + }) + }) + + describe('when concatenationDefault is set to false', () => { + test('returns false', () => { + const response = defaultConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: { + aggregation: { + concatenate: { + concatenateDefault: false + } + } + } + }) + + expect(response).toBeFalsy() + }) + }) +}) diff --git a/static/src/js/util/accessMethods/__tests__/supportsConcatenation.test.js b/static/src/js/util/accessMethods/__tests__/supportsConcatenation.test.js new file mode 100644 index 0000000000..7274eb8c34 --- /dev/null +++ b/static/src/js/util/accessMethods/__tests__/supportsConcatenation.test.js @@ -0,0 +1,59 @@ +import { supportsConcatenation } from '../supportsConcatenation' + +describe('supportsConcatenation', () => { + describe('when concatenation is supported', () => { + test('returns true', () => { + const response = supportsConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: { + aggregation: { + concatenate: { + concatenateDefault: false + } + } + } + }) + + expect(response).toBeTruthy() + }) + + test('returns true when concatenateDefault is true', () => { + const response = supportsConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: { + aggregation: { + concatenate: { + concatenateDefault: true + } + } + } + }) + + expect(response).toBeTruthy() + }) + }) + + describe('when concatenation is not in aggregation', () => { + test('returns false', () => { + const response = supportsConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: { + aggregation: {} + } + }) + + expect(response).toBeFalsy() + }) + }) + + describe('when concatenation is not supported', () => { + test('returns false', () => { + const response = supportsConcatenation({ + conceptId: 'S100000-EDSC', + serviceOptions: null + }) + + expect(response).toBeFalsy() + }) + }) +}) diff --git a/static/src/js/util/accessMethods/buildAccessMethods.js b/static/src/js/util/accessMethods/buildAccessMethods.js index 97fa1be835..7fc431535e 100644 --- a/static/src/js/util/accessMethods/buildAccessMethods.js +++ b/static/src/js/util/accessMethods/buildAccessMethods.js @@ -4,6 +4,8 @@ import { isDownloadable } from '../../../../../sharedUtils/isDownloadable' import { generateFormDigest } from './generateFormDigest' import { getVariables } from './getVariables' import { supportsBoundingBoxSubsetting } from './supportsBoundingBoxSubsetting' +import { supportsConcatenation } from './supportsConcatenation' +import { defaultConcatenation } from './defaultConcatenation' import { supportsShapefileSubsetting } from './supportsShapefileSubsetting' import { supportsTemporalSubsetting } from './supportsTemporalSubsetting' import { supportsVariableSubsetting } from './supportsVariableSubsetting' @@ -168,6 +170,9 @@ export const buildAccessMethods = (collectionMetadata, isOpenSearch) => { supportsShapefileSubsetting: supportsShapefileSubsetting(serviceItem), supportsTemporalSubsetting: supportsTemporalSubsetting(serviceItem), supportsVariableSubsetting: supportsVariableSubsetting(serviceItem), + supportsConcatenation: supportsConcatenation(serviceItem), + defaultConcatenation: defaultConcatenation(serviceItem), + enableConcatenateDownload: defaultConcatenation(serviceItem), type: serviceType, url: urlValue, variables diff --git a/static/src/js/util/accessMethods/defaultConcatenation.js b/static/src/js/util/accessMethods/defaultConcatenation.js new file mode 100644 index 0000000000..92ca6fe4fc --- /dev/null +++ b/static/src/js/util/accessMethods/defaultConcatenation.js @@ -0,0 +1,17 @@ +/** + * Determine whether or not the provided UMM S record supports default Concatenation + * @param {Object} service UMM S record to parse + */ +export const defaultConcatenation = (service) => { + const { serviceOptions = {} } = service + + // If there are no service options the record can not defaultConcatenation + if (serviceOptions == null) return false + + const { aggregation = {} } = serviceOptions + const { concatenate = {} } = aggregation + + const { concatenateDefault = false } = concatenate + + return concatenateDefault +} diff --git a/static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js b/static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js index 4dd3344edd..86ceab08a0 100644 --- a/static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js +++ b/static/src/js/util/accessMethods/supportsBoundingBoxSubsetting.js @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash' export const supportsBoundingBoxSubsetting = (service) => { const { serviceOptions = {} } = service - // If there are no service options the record can not support variable subsetting + // If there are no service options the record can not support bounding box subsetting if (serviceOptions == null) return false const { subset = {} } = serviceOptions diff --git a/static/src/js/util/accessMethods/supportsConcatenation.js b/static/src/js/util/accessMethods/supportsConcatenation.js new file mode 100644 index 0000000000..01beeee3d3 --- /dev/null +++ b/static/src/js/util/accessMethods/supportsConcatenation.js @@ -0,0 +1,14 @@ +/** + * Determine whether or not the provided UMM S record supports Concatenation + * @param {Object} service UMM S record to parse + */ +export const supportsConcatenation = (service) => { + const { serviceOptions = {} } = service + + // If there are no service options the record can not support supports concatenation + if (serviceOptions == null) return false + + const { aggregation = {} } = serviceOptions + + return 'concatenate' in aggregation +} diff --git a/static/src/js/util/accessMethods/supportsShapefileSubsetting.js b/static/src/js/util/accessMethods/supportsShapefileSubsetting.js index c811213e43..f8b2095a60 100644 --- a/static/src/js/util/accessMethods/supportsShapefileSubsetting.js +++ b/static/src/js/util/accessMethods/supportsShapefileSubsetting.js @@ -5,7 +5,7 @@ export const supportsShapefileSubsetting = (service) => { const { serviceOptions = {} } = service - // If there are no service options the record can not support variable subsetting + // If there are no service options the record can not support shapefile subsetting if (serviceOptions == null) return false const { subset = {} } = serviceOptions diff --git a/static/src/js/util/accessMethods/supportsTemporalSubsetting.js b/static/src/js/util/accessMethods/supportsTemporalSubsetting.js index 8ee7465a1b..820691665a 100644 --- a/static/src/js/util/accessMethods/supportsTemporalSubsetting.js +++ b/static/src/js/util/accessMethods/supportsTemporalSubsetting.js @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash' export const supportsTemporalSubsetting = (service) => { const { serviceOptions = {} } = service - // If there are no service options the record can not support variable subsetting + // If there are no service options the record can not support temporal subsetting if (serviceOptions == null) return false const { subset = {} } = serviceOptions diff --git a/static/src/js/util/retrievals.js b/static/src/js/util/retrievals.js index 44b4834678..d44ccf1b3d 100644 --- a/static/src/js/util/retrievals.js +++ b/static/src/js/util/retrievals.js @@ -29,6 +29,7 @@ const permittedCollectionMetadataFields = [ 'versionId' ] const permittedAccessMethodFields = [ + 'enableConcatenateDownload', 'enableTemporalSubsetting', 'enableSpatialSubsetting', 'maxItemsPerOrder', @@ -41,6 +42,8 @@ const permittedAccessMethodFields = [ 'selectedOutputProjection', 'supportsBoundingBoxSubsetting', 'supportsShapefileSubsetting', + 'supportsConcatenation', + 'defaultConcatenation', 'type', 'url' ] @@ -111,6 +114,7 @@ export const prepareRetrievalParams = (state) => { const { variables, selectedVariables } = accessMethods[selectedAccessMethod] returnValue.granule_params = params + returnValue.access_method = pick( accessMethods[selectedAccessMethod], permittedAccessMethodFields diff --git a/static/src/js/util/url/__tests__/projectCollections.test.js b/static/src/js/util/url/__tests__/projectCollections.test.js index 779cccee5a..bafdc6c7f3 100644 --- a/static/src/js/util/url/__tests__/projectCollections.test.js +++ b/static/src/js/util/url/__tests__/projectCollections.test.js @@ -172,6 +172,7 @@ describe('url#decodeUrlParams', () => { harmony: { enableTemporalSubsetting: false, enableSpatialSubsetting: true, + enableConcatenateDownload: false, selectedOutputFormat: undefined, selectedOutputProjection: undefined, selectedVariables: undefined @@ -202,7 +203,7 @@ describe('url#decodeUrlParams', () => { } } - expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][ets]=f')).toEqual(expectedResult) + expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][cd]=f&pg[1][ets]=f')).toEqual(expectedResult) }) test('decodes enable temporal subsetting correctly when true', () => { @@ -218,6 +219,7 @@ describe('url#decodeUrlParams', () => { isVisible: true, accessMethods: { harmony: { + enableConcatenateDownload: false, enableSpatialSubsetting: true, enableTemporalSubsetting: true, selectedOutputFormat: undefined, @@ -266,6 +268,7 @@ describe('url#decodeUrlParams', () => { isVisible: true, accessMethods: { harmony: { + enableConcatenateDownload: true, enableSpatialSubsetting: true, enableTemporalSubsetting: true, selectedOutputFormat: undefined, @@ -298,7 +301,7 @@ describe('url#decodeUrlParams', () => { } } - expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony')).toEqual(expectedResult) + expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][cd]=t')).toEqual(expectedResult) }) }) @@ -316,6 +319,7 @@ describe('url#decodeUrlParams', () => { isVisible: true, accessMethods: { harmony: { + enableConcatenateDownload: false, enableTemporalSubsetting: true, enableSpatialSubsetting: false, selectedOutputFormat: undefined, @@ -366,6 +370,7 @@ describe('url#decodeUrlParams', () => { harmony: { enableTemporalSubsetting: false, enableSpatialSubsetting: true, + enableConcatenateDownload: false, selectedOutputFormat: undefined, selectedOutputProjection: undefined, selectedVariables: undefined @@ -396,7 +401,7 @@ describe('url#decodeUrlParams', () => { } } - expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][ets]=f&pg[1][ess]=t')).toEqual(expectedResult) + expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][cd]=f&pg[1][ets]=f&pg[1][ess]=t')).toEqual(expectedResult) }) }) @@ -415,6 +420,7 @@ describe('url#decodeUrlParams', () => { harmony: { enableSpatialSubsetting: true, enableTemporalSubsetting: true, + enableConcatenateDownload: false, selectedOutputFormat: undefined, selectedOutputProjection: undefined, selectedVariables: undefined @@ -560,7 +566,7 @@ describe('url#encodeUrlQuery', () => { } } - expect(encodeUrlQuery(props)).toEqual('/path/here?p=!collectionId1!collectionId2&pg[1][v]=f&pg[1][m]=opendap&pg[1][uv]=V123456-EDSC!V987654-EDSC&pg[2][v]=f') + expect(encodeUrlQuery(props)).toEqual('/path/here?p=!collectionId1!collectionId2&pg[1][v]=f&pg[1][m]=opendap&pg[1][uv]=V123456-EDSC!V987654-EDSC&pg[1][cd]=f&pg[2][v]=f') }) test('correctly encodes enable temporal subsetting', () => { @@ -590,7 +596,7 @@ describe('url#encodeUrlQuery', () => { } } - expect(encodeUrlQuery(props)).toEqual('/path/here?p=!collectionId1!collectionId2&pg[1][v]=f&pg[1][m]=harmony&pg[1][ets]=f&pg[1][ess]=f&pg[2][v]=f') + expect(encodeUrlQuery(props)).toEqual('/path/here?p=!collectionId1!collectionId2&pg[1][v]=f&pg[1][m]=harmony&pg[1][cd]=f&pg[1][ets]=f&pg[1][ess]=f&pg[2][v]=f') }) test('correctly encodes enable spatial subsetting', () => { @@ -610,7 +616,8 @@ describe('url#encodeUrlQuery', () => { accessMethods: { harmony: { enableTemporalSubsetting: false, - enableSpatialSubsetting: false + enableSpatialSubsetting: false, + enableConcatenateDownload: false } }, selectedAccessMethod: 'harmony' @@ -621,6 +628,106 @@ describe('url#encodeUrlQuery', () => { } } - expect(encodeUrlQuery(props)).toEqual('/path/here?p=!collectionId1!collectionId2&pg[1][v]=f&pg[1][m]=harmony&pg[1][ets]=f&pg[1][ess]=f&pg[2][v]=f') + expect(encodeUrlQuery(props)).toEqual('/path/here?p=!collectionId1!collectionId2&pg[1][v]=f&pg[1][m]=harmony&pg[1][cd]=f&pg[1][ets]=f&pg[1][ess]=f&pg[2][v]=f') + }) +}) + +describe('enable concatenate download flag', () => { + test('decodes enable concatenate download correctly when false', () => { + const expectedResult = { + ...emptyDecodedResult, + focusedCollection: '', + project: { + collections: { + allIds: ['collectionId1', 'collectionId2'], + byId: { + collectionId1: { + granules: {}, + isVisible: true, + accessMethods: { + harmony: { + enableSpatialSubsetting: true, + enableConcatenateDownload: false, + enableTemporalSubsetting: false, + selectedOutputFormat: undefined, + selectedOutputProjection: undefined, + selectedVariables: undefined + } + }, + selectedAccessMethod: 'harmony' + }, + collectionId2: { + granules: {}, + isVisible: true, + selectedAccessMethod: undefined + } + } + } + }, + query: { + collection: { + ...emptyDecodedResult.query.collection, + byId: { + collectionId1: { + granules: {} + }, + collectionId2: { + granules: {} + } + } + } + } + } + + expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][cd]=f&pg[1][ets]=f')).toEqual(expectedResult) + }) + + test('decodes enable concatenate download correctly when true', () => { + const expectedResult = { + ...emptyDecodedResult, + focusedCollection: '', + project: { + collections: { + allIds: ['collectionId1', 'collectionId2'], + byId: { + collectionId1: { + granules: {}, + isVisible: true, + accessMethods: { + harmony: { + enableSpatialSubsetting: true, + enableConcatenateDownload: true, + enableTemporalSubsetting: false, + selectedOutputFormat: undefined, + selectedOutputProjection: undefined, + selectedVariables: undefined + } + }, + selectedAccessMethod: 'harmony' + }, + collectionId2: { + granules: {}, + isVisible: true, + selectedAccessMethod: undefined + } + } + } + }, + query: { + collection: { + ...emptyDecodedResult.query.collection, + byId: { + collectionId1: { + granules: {} + }, + collectionId2: { + granules: {} + } + } + } + } + } + + expect(decodeUrlParams('?p=!collectionId1!collectionId2&pg[1][m]=harmony&pg[1][ets]=f&pg[1][cd]=t')).toEqual(expectedResult) }) }) diff --git a/static/src/js/util/url/collectionsEncoders.js b/static/src/js/util/url/collectionsEncoders.js index fc239f5ddf..2453ca428c 100644 --- a/static/src/js/util/url/collectionsEncoders.js +++ b/static/src/js/util/url/collectionsEncoders.js @@ -62,6 +62,24 @@ const decodedGranules = (key, granules) => { return result } +const encodeConcatenateDownload = (projectCollection) => { + if (!projectCollection) return null + + const { + accessMethods, + selectedAccessMethod + } = projectCollection + + if (!accessMethods || !selectedAccessMethod) return null + + const selectedMethod = accessMethods[selectedAccessMethod] + const { + enableConcatenateDownload + } = selectedMethod + + return enableConcatenateDownload ? 't' : 'f' +} + const encodeSelectedVariables = (projectCollection) => { if (!projectCollection) return null @@ -191,6 +209,12 @@ const decodedSelectedAccessMethod = (pgParam) => { return accessMethod } +const decodedConcatenateDownload = (pgParam) => { + const { cd: enableConcatenateDownload } = pgParam + + return enableConcatenateDownload === 't' +} + const decodedOutputFormat = (pgParam) => { const { of: outputFormat } = pgParam @@ -336,6 +360,9 @@ export const encodeCollections = (props) => { // Encode selected variables pg.uv = encodeSelectedVariables(projectCollection) + // Encode concatenation selection + pg.cd = encodeConcatenateDownload(projectCollection) + // Encode selected output format pg.of = encodeOutputFormat(projectCollection) @@ -402,6 +429,7 @@ export const decodeCollections = (params) => { // Project let addedGranuleIds = [] let addedIsOpenSearch + let enableConcatenateDownload let enableTemporalSubsetting let enableSpatialSubsetting let isVisible = true @@ -477,10 +505,14 @@ export const decodeCollections = (params) => { // Decode output projection selectedOutputProjection = decodedOutputProjection(pCollection) - // Decode temporal subsetting for harmony collections + // Decode harmony subsettings on collections if (selectedAccessMethod && selectedAccessMethod.startsWith('harmony')) { + // Decode Temporal Subsetting enableTemporalSubsetting = decodedTemporalSubsetting(pCollection) + // Decode Spatial Subsetting enableSpatialSubsetting = decodedSpatialSubsetting(pCollection) + // Decode concatenate download + enableConcatenateDownload = decodedConcatenateDownload(pCollection) } // Determine if the collection is a CWIC collection @@ -512,9 +544,11 @@ export const decodeCollections = (params) => { || selectedOutputProjection || enableTemporalSubsetting !== undefined || enableSpatialSubsetting !== undefined + || enableConcatenateDownload !== undefined ) { projectById[collectionId].accessMethods = { [selectedAccessMethod]: { + enableConcatenateDownload, enableTemporalSubsetting, enableSpatialSubsetting, selectedOutputFormat,