From e67ad5f8530683f1391984c376f9deb08cd7a7a6 Mon Sep 17 00:00:00 2001 From: Maya DeBellis Date: Fri, 21 Aug 2020 14:05:36 -0700 Subject: [PATCH] Release 2.3! (#67) * Dev docs (#64) * update dev docs for each extension * submit_jobs minor cleanup and update CAS profile ticket attribute name * update labextension install and Che stack config * fix missing refs in submit jobs handler, return basic joblist if DPS doesn't give job details * fix dps panel, dps UI widget for jlab2 * add installation list and dependencies to README * readme type, put maap-py library installation note before serverextension install commands * dynamically get username from profile in dps info * allow edsc to open on localhost * Update edsc_extension to work with new edsc instance (#66) * add url parsers from earthdata src code * add attribution comments * clean up * create cmr query url from edsc code, pass to maap-py * start granule/collection search * exclude working! showing correct params. list comment w search * collection and granule search working * cleanup, adding sources * use generalized helper to get profile for registration * fix some UI formatting * fix merge conflicts on readme and dps_info jobinfo from master Co-authored-by: bsatoriu Co-authored-by: Sujen Shah Co-authored-by: echyam Co-authored-by: mayadebellis --- dps_info/src/jobinfo.ts | 11 +- edsc_extension/edsc_extension/handlers.py | 21 +- edsc_extension/package.json | 3 +- edsc_extension/src/buildCmrQuery.js | 159 ++++ edsc_extension/src/encodersDecoders.js | 983 ++++++++++++++++++++++ edsc_extension/src/globals.d.ts | 8 +- edsc_extension/src/index.ts | 87 +- edsc_extension/src/searchKeys.js | 95 +++ edsc_extension/src/urlParser.js | 162 ++++ edsc_extension/src/widgets.ts | 3 +- submit_jobs/src/activate.ts | 6 +- submit_jobs/src/funcs.ts | 6 +- 12 files changed, 1480 insertions(+), 64 deletions(-) create mode 100644 edsc_extension/src/buildCmrQuery.js create mode 100644 edsc_extension/src/encodersDecoders.js create mode 100644 edsc_extension/src/searchKeys.js create mode 100644 edsc_extension/src/urlParser.js diff --git a/dps_info/src/jobinfo.ts b/dps_info/src/jobinfo.ts index d4ad57c1..7b180a8d 100644 --- a/dps_info/src/jobinfo.ts +++ b/dps_info/src/jobinfo.ts @@ -89,7 +89,7 @@ export class JobTable extends Widget { display.readOnly = true; display.cols = 30; display.innerHTML = disp; - display.setAttribute('style', 'margin: 0px; height:17%; width: 98%; border: none; resize: none'); + display.setAttribute('style', 'margin: 0px; height:17%; width: 105%; border: none; resize: none; font-size: 11px'); display.className = 'jp-JSONEditor-host'; div2.appendChild(display); } @@ -224,7 +224,7 @@ export class JobTable extends Widget { if (json_response['status_code'] === 200) { INotification.success("Get user job result success."); - this._results = json_response['results']; + this._results = json_response['result']; } else { console.log('get user job result != 200'); INotification.error("Get user job result failed."); @@ -239,7 +239,6 @@ export class JobTable extends Widget { } _updateJobMetrics() { - // todo let outerDiv = (document.getElementById('jobs-div') as HTMLDivElement); // section header formatting if (outerDiv === null) { @@ -292,7 +291,7 @@ export class JobTable extends Widget { if (json_response['status_code'] === 200) { INotification.success("Get user job metrics success."); - this._results = json_response['results']; + this._metrics = json_response['result']; } else { console.log('get user job result != 200'); INotification.error("Get user job metrics failed."); @@ -914,7 +913,7 @@ export class JobWidget extends Widget { if (json_response['status_code'] === 200) { INotification.success("Get user job result success."); - this._results = json_response['results']; + this._results = json_response['result']; } else { console.log('get user job result != 200'); INotification.error("Get user job result failed."); @@ -989,7 +988,7 @@ export class JobWidget extends Widget { if (json_response['status_code'] === 200) { INotification.success("Get user job metrics success."); - this._metrics = json_response['results']; + this._metrics = json_response['result']; } else { console.log('get user job metrics != 200'); INotification.error("Get user job metrics failed."); diff --git a/edsc_extension/edsc_extension/handlers.py b/edsc_extension/edsc_extension/handlers.py index de675c0d..e0e6c306 100644 --- a/edsc_extension/edsc_extension/handlers.py +++ b/edsc_extension/edsc_extension/handlers.py @@ -3,17 +3,9 @@ import nbformat from notebook.base.handlers import IPythonHandler import subprocess -sys.path.append('./edsc_extension/maap-py') -print(subprocess.check_output('pwd',shell=True)) -#from maap import maap.MAAP as MAAP import maap from maap.maap import MAAP -# In local Config -#PATH_TO_MAAP_CFG = './maap-py/maap.cfg' - -# In Docker Image Che Config -PATH_TO_MAAP_CFG = '/edsc_extension/maap-py/maap.cfg' class GetGranulesHandler(IPythonHandler): def printUrls(self, granules): @@ -27,11 +19,11 @@ def printUrls(self, granules): def get(self): maap = MAAP() - json_obj = self.get_argument('json_obj', '') + cmr_query = self.get_argument('cmr_query', '') limit = str(self.get_argument('limit', '')) - print("json obj", json_obj) + print("cmr_query", cmr_query) - query_string = maap.getCallFromEarthdataQuery(json_obj, limit=limit) + query_string = maap.getCallFromCmrUri(cmr_query, limit=limit) granules = eval(query_string) query_result = self.printUrls(granules) try: @@ -44,11 +36,12 @@ def get(self): class GetQueryHandler(IPythonHandler): def get(self): maap = MAAP() - json_obj = self.get_argument('json_obj', '') + cmr_query = self.get_argument('cmr_query', '') limit = str(self.get_argument('limit', '')) - print("json obj", json_obj) + query_type = self.get_argument('query_type', 'granule') + print("cmr_query", cmr_query) - query_string = maap.getCallFromEarthdataQuery(json_obj, limit=limit) + query_string = maap.getCallFromCmrUri(cmr_query, limit=limit, search=query_type) print("Response is: ", query_string) self.finish({"query_string": query_string}) diff --git a/edsc_extension/package.json b/edsc_extension/package.json index cb31807a..c79d17a1 100644 --- a/edsc_extension/package.json +++ b/edsc_extension/package.json @@ -40,7 +40,8 @@ "@types/jquery": "^3.5.0", "@types/node": "^14.0.14", "jquery": "^3.5.1", - "jupyterlab_toastify": "^3.0.0" + "jupyterlab_toastify": "^3.0.0", + "qs": "^6.9.4" }, "devDependencies": { "rimraf": "^2.6.1", diff --git a/edsc_extension/src/buildCmrQuery.js b/edsc_extension/src/buildCmrQuery.js new file mode 100644 index 00000000..02dc7c0a --- /dev/null +++ b/edsc_extension/src/buildCmrQuery.js @@ -0,0 +1,159 @@ +import { stringify } from 'qs'; +import { granuleParams, collectionParams } from "./globals"; + +export const buildCmrQuery = (urlParams, nonIndexedKeys, permittedCmrKeys, granule=true) => { + return buildParams({ + body: camelCaseKeysToUnderscore(urlParams), + nonIndexedKeys, + permittedCmrKeys, + granule + }); +} + +/** + * Apapted from source: https://github.com/nasa/earthdata-search/blob/f09ff3bfd40420322f005654bc349374aab1fe57/serverless/src/util/cmr/buildParams.js + * Builds a URL used to perform a search request + * @param {object} paramObj Parameters needed to build a search request URL + */ +export const buildParams = (paramObj) => { + const { + body, + nonIndexedKeys, + permittedCmrKeys, + granule, + stringifyResult = true + } = paramObj; + + + const obj = pick(body, permittedCmrKeys) + granule ? granuleParams = obj : collectionParams = obj; + + // console.log("unfiltered", body); + // console.log("filtered", obj) + + // For JSON requests we want dont want to stringify the params returned + if (stringifyResult) { + // Transform the query string hash to an encoded url string + const queryParams = prepKeysForCmr(obj, nonIndexedKeys) + return queryParams + } + + return obj +} + +/** + * Adapted from source https://github.com/nasa/earthdata-search/blob/f09ff3bfd40420322f005654bc349374aab1fe57/serverless/src/util/pick.js + * Select only desired keys from a provided object. + * @param {object} providedObj - An object containing any keys. + * @param {array} keys - An array of strings that represent the keys to be picked. + * @return {obj} An object containing only the desired keys. + */ +export const pick = (providedObj = {}, keys) => { + let obj = null + + // if `null` is provided the default parameter will not be + // set so we'll handle it manually + if (providedObj == null) { + obj = {} + } else { + obj = providedObj + } + + let filteredObj = {}; + keys.forEach((key) => { + let val; + if (key === 'exclude') { + val = getObject(obj, "excluded_granule_ids"); + } else { + val = getObject(obj, key); + } + if (val) { + filteredObj[key] = val; + } + }) + return filteredObj +} + +/* +* Adapted from +* https://stackoverflow.com/questions/15523514/find-by-key-deep-in-a-nested-array +* */ +function getObject(theObject, key) { + var result = null; + if(theObject instanceof Array) { + for(var i = 0; i < theObject.length; i++) { + result = getObject(theObject[i], key); + if (result) { + break; + } + } + } + else + { + for(var prop in theObject) { + if(prop == key) { + if(theObject[prop]) { + return theObject[prop]; + } + } + if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) { + result = getObject(theObject[prop], key); + if (result) { + break; + } + } + } + } + return result; +} + +/** + * Adapted from source https://github.com/nasa/earthdata-search/blob/f09ff3bfd40420322f005654bc349374aab1fe57/sharedUtils/prepKeysForCmr.js + * Create a query string containing both indexed and non-indexed keys. + * @param {object} queryParams - An object containing all queryParams. + * @param {array} nonIndexedKeys - An array of strings that represent the keys which should not be indexed. + * @return {string} A query string containing both indexed and non-indexed keys. + */ +export const prepKeysForCmr = (queryParams, nonIndexedKeys = []) => { + const nonIndexedAttrs = {} + const indexedAttrs = { ...queryParams } + + nonIndexedKeys.forEach((key) => { + nonIndexedAttrs[key] = indexedAttrs[key] + delete indexedAttrs[key] + }) + + return [ + stringify(indexedAttrs), + stringify(nonIndexedAttrs, { indices: false, arrayFormat: 'brackets' }) + ].filter(Boolean).join('&') +} + +/* +* Source: https://stackoverflow.com/questions/30970286/convert-javascript-object-camelcase-keys-to-underscore-case +* */ +function camelCaseKeysToUnderscore(obj){ + if (typeof(obj) != "object") return obj; + + for(let oldName in obj){ + + // Camel to underscore + let newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();}); + + // Only process if names are different + if (newName != oldName) { + // Check for the old property name to avoid a ReferenceError in strict mode. + if (obj.hasOwnProperty(oldName)) { + obj[newName] = obj[oldName]; + delete obj[oldName]; + } + } + + // Recursion + if (typeof(obj[newName]) == "object") { + obj[newName] = camelCaseKeysToUnderscore(obj[newName]); + } + + } + return obj; +} \ No newline at end of file diff --git a/edsc_extension/src/encodersDecoders.js b/edsc_extension/src/encodersDecoders.js new file mode 100644 index 00000000..268be925 --- /dev/null +++ b/edsc_extension/src/encodersDecoders.js @@ -0,0 +1,983 @@ +/** + * + * All encoders and decoders copied from earthdata search source code. + * They can all be found in this directory: https://github.com/nasa/earthdata-search/tree/master/static/src/js/util/url + * + * June 30, 2020 - https://github.com/nasa/earthdata-search/releases/tag/v1.123.14 + * + * */ + + +/** + * Decodes a string parameter (returns the same value) + * @param {string} string + */ +export const decodeString = string => string + + +/** + * Encodes a string parameter (returns the same value) + * @param {string} string + */ +export const encodeString = string => string || '' + +/** + * Encodes a Feature Facet object into a string + * @param {object} features Feature Facet object + * @return {string} A `!` delimited string of the Feature Facet values + */ +export const encodeFeatures = (features) => { + if (!features) return '' + + const { + customizable, + mapImagery, + nearRealTime + } = features + + const encoded = [] + + if (mapImagery) encoded.push('Map Imagery') + if (nearRealTime) encoded.push('Near Real Time') + if (customizable) encoded.push('Customizable') + + const encodedString = encoded.join('!') + + if (encodedString === '') return '' + + return encodedString +} + + +/** + * Decodes a Feature Facet parameter string into an object + * @param {string} string A `!` delimited string of the Feature Facet values + * @return {object} Feature Facet object + */ +export const decodeFeatures = (string) => { + const defaultFeatures = { + mapImagery: false, + nearRealTime: false, + customizable: false + } + + if (!string) { + return defaultFeatures + } + + const decodedValues = string.split('!') + + const decodedFeatures = { + mapImagery: decodedValues.indexOf('Map Imagery') !== -1, + nearRealTime: decodedValues.indexOf('Near Real Time') !== -1, + customizable: decodedValues.indexOf('Customizable') !== -1 + } + + return { + ...decodedFeatures + } +} + +/** + * Encodes a Facet object into a string + * @param {object} facets Facet object + * @return {string} A `!` delimited string of the facet values + */ +export const encodeFacets = (facets) => { + if (!facets) return '' + + const encoded = [] + + facets.forEach((facet) => { + encoded.push(facet) + }) + + return encoded.join('!') +} + + +/** + * Decodes a Facet parameter string into an object + * @param {string} string A `!` delimited string of the facet values + * @return {object} Facet object + */ +export const decodeFacets = (string) => { + if (!string) { + return undefined + } + + const decodedValues = string.split('!') + + return decodedValues +} +const projections = { + arctic: 'epsg3413', + geographic: 'epsg4326', + antarctic: 'epsg3031' +} + +const projectionList = [ + projections.arctic, + projections.geographic, + projections.antarctic +] + +/** + * Encodes a Map object into a string + * @param {object} query Map object with query and state + * @return {string} A `!` delimited string of the map values + */ +export const encodeMap = (map) => { + if (!map) return '' + + const { + base, + latitude, + longitude, + overlays, + projection, + zoom + } = map + + const encodedProjection = projectionList.indexOf(projection) + + let encodedBase + if (base.blueMarble) encodedBase = 0 + if (base.trueColor) encodedBase = 1 + if (base.landWaterMap) encodedBase = 2 + + const encodedOverlays = [] + if (overlays.referenceFeatures) encodedOverlays.push(0) + if (overlays.coastlines) encodedOverlays.push(1) + if (overlays.referenceLabels) encodedOverlays.push(2) + + const encodedString = [ + latitude, + longitude, + zoom, + encodedProjection, + encodedBase, + encodedOverlays.join(',') + ].join('!') + + if (encodedString === '0!0!2!1!0!0,2') return '' + + return encodedString +} + + +/** + * Decodes a map parameter string into an object + * @param {string} string A `!` delimited string of the map values + * @return {object} Map object with query and state + */ +export const decodeMap = (string) => { + if (!string) { + return {} + } + + const [latitude, longitude, zoom, projection, base, overlays] = string.split('!') + + const decodedLatitude = parseFloat(latitude) + const decodedLongitude = parseFloat(longitude) + const decodedZoom = parseFloat(zoom) + + const decodedProjection = projectionList[projection] + + const decodedBase = { + blueMarble: base === '0', + trueColor: base === '1', + landWaterMap: base === '2' + } + + const decodedOverlays = { + referenceFeatures: overlays.split(',').indexOf('0') !== -1, + coastlines: overlays.split(',').indexOf('1') !== -1, + referenceLabels: overlays.split(',').indexOf('2') !== -1 + } + + const map = { + base: decodedBase, + latitude: decodedLatitude, + longitude: decodedLongitude, + overlays: decodedOverlays, + projection: decodedProjection, + zoom: decodedZoom + } + + return { + ...map + } +} + +/** + * Lookup a object key given a value + * @param {string} object JavaScript Object with key-value pairs + * @param {string} value A value in the object + * @return {string} A key in the object + */ +const getObjectKeyByValue = (object, value) => Object.keys(object) + .find(key => object[key] === value) + +/** + * Mapping of Science Keyword keys to encoded values + */ +const scienceKeywordMapping = { + topic: 'fst', + term: 'fsm', + variable_level_1: 'fs1', + variable_level_2: 'fs2', + variable_level_3: 'fs3', + detailed_variable: 'fsd' +} + +/** + * Encodes a Science Keyword Facet object into a flat object with encoded keys + * @param {object} scienceKeywords Science Keyword Facet object + * @return {object} A flat object with encoded Science Keyword keys + */ +export const encodeScienceKeywords = (scienceKeywords) => { + if (!scienceKeywords) return '' + if (Object.keys(scienceKeywords).length === 0) return '' + + const encoded = {} + scienceKeywords.forEach((keyword, index) => { + Object.keys(keyword).forEach((key) => { + encoded[`${scienceKeywordMapping[key]}${index}`] = keyword[key] + }) + }) + + return encoded +} + + +/** + * Decodes a parameter object into a Science Keyword object + * @param {object} params URL parameter object from parsing the URL parameter string + * @return {object} Science Keyword Facet object + */ +export const decodeScienceKeywords = (params) => { + if (Object.keys(params).length === 0) return undefined + + const decoded = [] + Object.keys(params).forEach((encodedKey) => { + // All of the science keyword facets have an index as the last character of the key + // Strip off the last character and check the mapping if it exists + const key = encodedKey.slice(0, -1) + const index = encodedKey.slice(-1) + + const decodedKey = getObjectKeyByValue(scienceKeywordMapping, key) + if (decodedKey) { + // Update the decoded index with value + if (decoded[index] === undefined) decoded[index] = {} + decoded[index][decodedKey] = params[encodedKey] + } + }) + + if (decoded.length > 0) return decoded + + return undefined +} + +/** + * Encodes a Temporal object into a string + * @param {object} temporal Temporal object + * @return {string} A `,` delimited string of the temporal values + */ +export const encodeTemporal = (temporal) => { + if (!temporal) return undefined + + const { + endDate, + startDate, + recurringDayStart, + recurringDayEnd, + isRecurring + } = temporal + + const valuesToEncode = [ + startDate, + endDate + ] + + if (isRecurring) { + valuesToEncode.push(...[recurringDayStart, recurringDayEnd]) + } + + const encodedString = valuesToEncode.filter(Boolean).join(',') + + // TODO: Strip empty elements then join + if (encodedString === '') return undefined + + return encodedString +} + +/** + * Mapping of timeline zoom levels. The Timeline (sometimes) and URL use numbers, CMR uses words + */ +export const timelineIntervals = { + minute: '2', + hour: '3', + day: '4', + month: '5', + year: '6' +} + +/** + * Decodes a Temporal parameter string into an object + * @param {string} string A `,` delimited string of the temporal values + * @return {object} Temporal object + */ +export const decodeTemporal = (string) => { + if (!string) { + return {} + } + + const [ + startDate, + endDate, + recurringDayStart = '', + recurringDayEnd = '' + ] = string.split(',') + + const isRecurring = !!(recurringDayStart && recurringDayEnd) + + const temporal = { + endDate, + startDate, + recurringDayStart, + recurringDayEnd, + isRecurring + } + + return { + ...temporal + } +} + +/** + * Encodes a Timeline object into an encoded object + * @param {object} timelineQuery Timeline query object + * @param {string} pathname Pathname string from react-router + * @return {string} A `!` delimited string of the timeline values + */ +export const encodeTimeline = (timelineQuery, pathname) => { + if (pathname === '/search') return '' + if (!timelineQuery) return '' + + const { + center, + interval, + start, + end + } = timelineQuery + + if (!center && !start && !end) return '' + + const encodedStart = start || '' + const encodedEnd = end || '' + + const encodedString = [center, timelineIntervals[interval], encodedStart, encodedEnd].join('!') + // if there is no center, return an empty string + if (encodedString[0] === '!') return '' + + return { + tl: encodedString + } +} + + +/** + * Decodes a parameter object into a Timeline object + * @param {object} params URL parameter object from parsing the URL parameter string + * @return {object} Timeline object with query and state + */ +export const decodeTimeline = (params) => { + const { tl: timeline } = params + + if (!timeline) return undefined + + const [center, intervalNum, start, end] = timeline.split('!') + + const interval = getObjectKeyByValue(timelineIntervals, intervalNum) + const query = { + center: parseInt(center, 10) || undefined, + end: parseInt(end, 10) || undefined, + interval, + start: parseInt(start, 10) || undefined + } + + return query +} + +/** + * Encode a list of Granule IDs + * @param {boolean} isCwic Are the granules CWIC + * @param {array} granuleIds List of granule IDs + */ +const encodeGranules = (isCwic, granuleIds) => { + // On page log, isCwic hasn't been determined yet + // temporary fix, if the granule doesn't start with G, it is CWIC + const [firstGranuleId] = granuleIds + + if (isCwic || isNumber(firstGranuleId)) { + return granuleIds.join('!') + } + + // CMR Granule Ids + // G12345-PROVIDER + const provider = granuleIds[0].split('-')[1] + const formattedGranuleIds = granuleIds.map(granuleId => granuleId.split('G')[1].split('-')[0]) + + return `${formattedGranuleIds.join('!')}!${provider}` +} + +/** + * Decode a string of Granule IDs + * @param {string} excludedGranules Encoded Granule IDs + */ +const decodedGranules = (key, granules) => { + const keys = Object.keys(granules) + + let result = { + isCwic: false, + granuleIds: [] + } + + if (keys.indexOf(key) !== -1) { + const { [key]: decodedGranules } = granules + const granulesList = decodedGranules.split('!') + const provider = granulesList.pop() + const granuleIds = granulesList.map(granuleId => `G${granuleId}-${provider}`) + + result = { + isCwic: false, + granuleIds + } + } + if (keys.indexOf(`c${key}`) !== -1) { + const { [`c${key}`]: decodedGranules } = granules + const granuleIds = decodedGranules.split('!') + + result = { + isCwic: true, + granuleIds + } + } + return result +} + +const encodeSelectedVariables = (projectCollection) => { + if (!projectCollection) return null + + const { + accessMethods, + selectedAccessMethod + } = projectCollection + + if (!accessMethods || !selectedAccessMethod) return null + + const selectedMethod = accessMethods[selectedAccessMethod] + const { + selectedVariables + } = selectedMethod + + if (!selectedVariables) return null + + return selectedVariables.join('!') +} + +const encodeOutputFormat = (projectCollection) => { + if (!projectCollection) return null + + const { + accessMethods, + selectedAccessMethod + } = projectCollection + + if (!accessMethods || !selectedAccessMethod) return null + + const selectedMethod = accessMethods[selectedAccessMethod] + const { + selectedOutputFormat + } = selectedMethod + + if (!selectedOutputFormat) return null + + return selectedOutputFormat +} + +const encodeAddedGranules = (isCwic, projectCollection) => { + if (!projectCollection) return null + + const { + addedGranuleIds = [] + } = projectCollection + + if (!addedGranuleIds.length) return null + + return encodeGranules(isCwic, addedGranuleIds) +} + +const encodeRemovedGranules = (isCwic, projectCollection) => { + if (!projectCollection) return null + + const { + removedGranuleIds = [] + } = projectCollection + + if (!removedGranuleIds.length) return null + + return encodeGranules(isCwic, removedGranuleIds) +} + +const decodedSelectedVariables = (pgParam) => { + const { uv: variableIds } = pgParam + + if (!variableIds) return undefined + + return variableIds.split('!') +} + +const decodedOutputFormat = (pgParam) => { + const { of: outputFormat } = pgParam + + return outputFormat +} + +/** + * Encodes a Collections object into an object + * @param {object} collections Collections object + * @param {string} focusedCollection Focused Collection ID + * @return {string} An object with encoded Collections + */ +export const encodeCollections = (props) => { + const { + collections = {}, + focusedCollection, + project = {} + } = props + + const { byId } = collections + const { + byId: projectById = {}, + collectionIds: projectIds = [] + } = project + + // pParameter - focusedCollection!projectCollection1!projectCollection2 + const pParameter = [ + focusedCollection, + ...projectIds + ].join('!') + + // If there isn't a focusedCollection or any projectIds, we don't see to continue + if (pParameter === '') return '' + + // pgParameter - excluded granules and granule filters based on pParameter collections + const pgParameter = [] + if (byId) { + pParameter.split('!').forEach((collectionId, index) => { + let pg = {} + + // if the focusedCollection is also in projectIds, don't encode the focusedCollection + if (index === 0 && projectIds.indexOf(focusedCollection) !== -1) { + pgParameter[index] = pg + return + } + + const collection = byId[collectionId] + if (!collection) { + pgParameter[index] = pg + return + } + + const projectCollection = projectById[collectionId] + + // excludedGranules + let encodedExcludedGranules + const { + excludedGranuleIds = [], + granules, + granuleFilters, + isVisible, + isCwic + } = collection + + const excludedKey = isCwic ? 'cx' : 'x' + + if (granules && excludedGranuleIds.length > 0) { + encodedExcludedGranules = encodeGranules(isCwic, excludedGranuleIds) + } + + if (encodedExcludedGranules) pg[excludedKey] = encodedExcludedGranules + + let encodedAddedGranules + let encodedRemovedGranules + const addedKey = isCwic ? 'ca' : 'a' + const removedKey = isCwic ? 'cr' : 'r' + + // Encode granules added to the current project + if ( + projectCollection + && projectCollection.addedGranuleIds + && projectCollection.addedGranuleIds.length > 0 + ) { + encodedAddedGranules = encodeAddedGranules(isCwic, projectCollection) + } + + // Encode granules removed from the current project + if ( + projectCollection + && projectCollection.removedGranuleIds + && projectCollection.removedGranuleIds.length > 0 + ) { + encodedRemovedGranules = encodeRemovedGranules(isCwic, projectCollection) + } + + if (encodedAddedGranules) pg[addedKey] = encodedAddedGranules + if (encodedRemovedGranules) pg[removedKey] = encodedRemovedGranules + + // Collection visible, don't encode the focusedCollection + if (index !== 0 && isVisible) pg.v = 't' + + // Add the granule encoded granule filters + if (granuleFilters) { + pg = { ...pg, ...encodeGranuleFilters(granuleFilters) } + } + + // Encode selected variables + pg.uv = encodeSelectedVariables(projectCollection) + + // Encode selected output format + pg.of = encodeOutputFormat(projectCollection) + + pgParameter[index] = pg + }) + } + + const encoded = { + p: pParameter, + pg: pgParameter + } + + return encoded +} + + +/** + * Decodes a parameter object into a Collections object + * @param {object} params URL parameter object from parsing the URL parameter string + * @return {object} Collections object + */ +export const decodeCollections = (params) => { + if (Object.keys(params).length === 0) return {} + + const { p, pg } = params + if (!p && !pg) return {} + + let focusedCollection = '' + let collections + let project + const allIds = [] + const byId = {} + const projectIds = [] + const projectById = {} + + p.split('!').forEach((collectionId, index) => { + // If there is no collectionId, move on to the next index + // i.e. there is no focusedCollection + if (collectionId === '') return + + // Add collectionId to correct allIds and projectIds + if (allIds.indexOf(collectionId) === -1) allIds.push(collectionId) + if (index > 0) projectIds.push(collectionId) + + // Set the focusedCollection + if (index === 0) focusedCollection = collectionId + + let excludedGranuleIds = [] + let addedGranuleIds = [] + let removedGranuleIds = [] + let granuleFilters = {} + let selectedOutputFormat + let isCwic + let excludedIsCwic + let addedIsCwic + let removedIsCwic + let isVisible = false + + let variableIds + if (pg && pg[index]) { + // Excluded Granules + ({ isCwic: excludedIsCwic, granuleIds: excludedGranuleIds } = decodedGranules('x', pg[index])); + + ({ isCwic: addedIsCwic, granuleIds: addedGranuleIds = [] } = decodedGranules('a', pg[index])); + + ({ isCwic: removedIsCwic, granuleIds: removedGranuleIds = [] } = decodedGranules('r', pg[index])) + + isCwic = excludedIsCwic || addedIsCwic || removedIsCwic + + // Collection visible + const { v: visible = '' } = pg[index] + if (visible === 't') isVisible = true + + // Decode selected variables + variableIds = decodedSelectedVariables(pg[index]) + + // Decode granule filters + granuleFilters = decodeGranuleFilters(pg[index]) + + // Decode output format + selectedOutputFormat = decodedOutputFormat(pg[index]) + } + + // Populate the collection object for the redux store + byId[collectionId] = { + excludedGranuleIds, + granules: {}, + granuleFilters, + isCwic, + isVisible, + metadata: {} + } + + if (index > 0) { + projectById[collectionId] = {} + } + + if (variableIds || selectedOutputFormat) { + projectById[collectionId] = { + accessMethods: { + opendap: { + selectedVariables: variableIds, + selectedOutputFormat + } + } + } + } + + if (addedGranuleIds.length && projectById[collectionId]) { + projectById[collectionId].addedGranuleIds = addedGranuleIds + } + + if (removedGranuleIds.length && projectById[collectionId]) { + projectById[collectionId].removedGranuleIds = removedGranuleIds + } + }) + + // if no decoded collections information exists, return undfined for collections + if (pg || projectIds.length > 0) { + collections = { + allIds, + byId + } + + project = { + byId: projectById, + collectionIds: projectIds + } + } + + return { + collections, + focusedCollection, + project + } +} + +/** + * Encodes a granule filters object into an object. + * @param {Object} granuleFilters - The granule filters object. + * @return {String} An object with encoded granule filters. + */ +export const encodeGranuleFilters = (granuleFilters) => { + const pg = {} + if (granuleFilters.temporal) pg.qt = encodeTemporal(granuleFilters.temporal) + if (granuleFilters.dayNightFlag) pg.dnf = granuleFilters.dayNightFlag + if (granuleFilters.browseOnly) pg.bo = granuleFilters.browseOnly + if (granuleFilters.onlineOnly) pg.oo = granuleFilters.onlineOnly + if (granuleFilters.cloudCover) pg.cc = granuleFilters.cloudCover + if (granuleFilters.orbitNumber) pg.on = granuleFilters.orbitNumber + if (granuleFilters.equatorCrossingLongitude) pg.ecl = granuleFilters.equatorCrossingLongitude + if (granuleFilters.readableGranuleName) pg.id = granuleFilters.readableGranuleName.join('!') + if (granuleFilters.equatorCrossingDate) { + pg.ecd = encodeTemporal(granuleFilters.equatorCrossingDate) + } + if (granuleFilters.sortKey) pg.gsk = granuleFilters.sortKey + + return pg +} + +/** + * Decodes part of the decoded ?pg url parameter into a granule filters object + * @param {Object} params - URL parameter object from parsing the URL parameter string + * @return {Object} A granule filters object + */ +export const decodeGranuleFilters = (params = {}) => { + const granuleFilters = {} + if (params.qt) granuleFilters.temporal = decodeTemporal(params.qt) + if (params.dnf) granuleFilters.dayNightFlag = params.dnf + if (params.bo) granuleFilters.browseOnly = params.bo === 'true' + if (params.oo) granuleFilters.onlineOnly = params.oo === 'true' + if (params.cc) granuleFilters.cloudCover = params.cc + if (params.on) granuleFilters.orbitNumber = params.on + if (params.ecl) granuleFilters.equatorCrossingLongitude = params.ecl + if (params.id) granuleFilters.readableGranuleName = params.id.split('!') + if (params.ecd) granuleFilters.equatorCrossingDate = decodeTemporal(params.ecd) + if (params.gsk) granuleFilters.sortKey = params.gsk + + return granuleFilters +} + +/** + * Returns true the string contains only number characters and false if there are any non-number characters + * @return {boolean} + */ +const reg = new RegExp(/^\d+$/) +export const isNumber = string => reg.test(string) + +export const encodeGridCoords = (gridCoords) => { + if (!gridCoords) return '' + + const encodedCoords = gridCoords + .trim() + .replace(/,/g, ':') + .replace(/\s+/g, ',') + .replace(/(^|,)(\d+)($|:)/g, '$1$2-$2$3') + .replace(/(^|:)(\d+)($|,)/g, '$1$2-$2$3') + + return encodedCoords +} + +export const decodeGridCoords = (string) => { + if (!string) return undefined + + const decodedString = string + .replace(/,/g, ' ') + .replace(/:/g, ',') + .replace(/(\d+)-(\d+)/g, (m, m0, m1) => { + if (m0 === m1) return m0 + return m + }) + + return decodedString +} + +/** + * Encodes hasGranulesOrCwic + * @param {object} hasGranulesOrCwic hasGranulesOrCwic value from redux store + * @return {string} Encoded value for hasGranulesOrCwic + */ +export const encodeHasGranulesOrCwic = (hasGranulesOrCwic) => { + // When we have undefined in the store, the encoded value is true (ac=true) + if (!hasGranulesOrCwic) return true + + // When we have true in the store, we don't encode the value + return '' +} + + +/** + * Decodes hasGranulesOrCwic + * @param {string} value Encoded value for hasGranulesOrCwic + * @return {object} Decoded hasGranulesOrCwic value + */ +export const decodeHasGranulesOrCwic = (value) => { + // When we see true in the url, we do not store hasGranulesOrCwic in the store + if (value === 'true') return undefined + + // If we do not see the ac param in the store, we save hasGranulesOrCwic=true in the store + return true +} + +/** + * Encodes the Advanced Search params into an object + * @param {Object} advancedSearch advancedSearch object from the store + */ +export const encodeAdvancedSearch = (advancedSearch) => { + if (!advancedSearch) return '' + + const { regionSearch } = advancedSearch + + if (!regionSearch) return '' + + const { selectedRegion } = regionSearch + + if (!selectedRegion) return '' + + return { + sr: { + ...selectedRegion + } + } +} + +/** + * Decodes a parameter object into an advancedSearch object + * @param {Object} params URL parameter object from parsing the URL parameter string + */ +export const decodeAdvancedSearch = (params) => { + if (Object.keys(params).length === 0) return undefined + + const { sr } = params + if (!sr) return undefined + + const advancedSearch = { + regionSearch: { + selectedRegion: { + ...sr + } + } + } + + return advancedSearch +} + +import { isEmpty } from 'lodash' + +/** + * Encodes the Autocomplete Selected params into an object + * @param {Object} selected autocomplete selected object from the store + */ +export const encodeAutocomplete = (selected) => { + if (!selected || selected.length === 0) return '' + + const param = {} + selected.forEach(({ type, fields }) => { + if (Object.keys(param).includes(type)) { + param[type].push(fields) + } else { + param[type] = [fields] + } + }) + + return param +} + +/** + * Decodes a parameter object into an Autocomplete Selected array + * @param {Object} params URL parameter object from parsing the URL parameter string + */ +export const decodeAutocomplete = (params) => { + if (!params || isEmpty(params)) return undefined + + const values = [] + + Object.keys(params).forEach((key) => { + const items = params[key] + Object.keys(items).forEach((index) => { + // Pull out the colon delimited value + const fields = items[index] + + // Split the fields and pop the last element (which represents the leaf node) + const value = fields.split(':').slice(-1) + + // slice returns an array, select the element + const [selectedValue] = value + + values.push({ type: key, fields: items[index], value: selectedValue }) + }) + }) + + return values +} \ No newline at end of file diff --git a/edsc_extension/src/globals.d.ts b/edsc_extension/src/globals.d.ts index f4160c77..eda201e1 100644 --- a/edsc_extension/src/globals.d.ts +++ b/edsc_extension/src/globals.d.ts @@ -1,2 +1,8 @@ export var limit: any; -export var params: any; +export var granuleParams: any; +export var collectionParams: any; +export var granuleQuery: any; +export var collectionQuery: any; +export var edscUrl: any; + + diff --git a/edsc_extension/src/index.ts b/edsc_extension/src/index.ts index 641b5ee5..b4f1a3b8 100644 --- a/edsc_extension/src/index.ts +++ b/edsc_extension/src/index.ts @@ -22,6 +22,12 @@ import '../style/index.css'; import { IFrameWidget } from './widgets'; import { setResultsLimit, displaySearchParams } from './popups' import globals = require("./globals"); +import { decodeUrlParams } from "./urlParser"; +import { buildCmrQuery } from "./buildCmrQuery"; +import { granulePermittedCmrKeys, + granuleNonIndexedKeys, + collectionPermittedCmrKeys, + collectionNonIndexedKeys } from "./searchKeys"; let SEARCH_CLIENT_URL = ''; if (document.location.hostname === 'localhost') { @@ -29,7 +35,7 @@ if (document.location.hostname === 'localhost') { } else { SEARCH_CLIENT_URL = document.location.origin + ':3052/search'; } -console.log(SEARCH_CLIENT_URL); +console.log("EDSC instance is", SEARCH_CLIENT_URL); /////////////////////////////////////////////////////////////// // @@ -59,49 +65,58 @@ function activate(app: JupyterFrontEnd, let instanceTracker = new WidgetTracker({ namespace }); - // - // Listen for messages being sent by the iframe - this will be all of the parameter - // objects from the EDSC instance + // Listen for messages being sent by the iframe - parse the url and set as parameters for search // window.addEventListener("message", (event: MessageEvent) => { - globals.params = event.data; - console.log("at event listen: ", event.data); + // if the message sent is the edsc url + if (typeof event.data === "string"){ + globals.edscUrl = event.data; + const queryString = '?' + event.data.split('?')[1]; + const decodedUrlObj = decodeUrlParams(queryString); + globals.granuleQuery = "https://fake.com/?" + buildCmrQuery(decodedUrlObj, granulePermittedCmrKeys, granuleNonIndexedKeys, ); + globals.collectionQuery = "https://fake.com/?" + buildCmrQuery(decodedUrlObj, collectionPermittedCmrKeys, collectionNonIndexedKeys, false); + // console.log("Granule", globals.granuleQuery); + // console.log("Collection", globals.collectionQuery); + } }); + // // Get the current cell selected in a notebook // function getCurrent(args: ReadonlyJSONObject): NotebookPanel | null { - console.log(args); const widget = tracker.currentWidget; const activate = args['activate'] !== false; if (activate && widget) { app.shell.activateById(widget.id); } - return widget; } // PASTE SEARCH INTO A NOTEBOOK - function pasteSearch(args: any, result_type: any) { + function pasteSearch(args: any, result_type: any, query_type='granule') { const current = getCurrent(args); - console.log(result_type); // If no search is selected, send an error - if (Object.keys(globals.params).length == 0) { + if (Object.keys(globals.granuleParams).length == 0) { INotification.error("Error: No Search Selected."); return; } - // Paste Search Query if (result_type == "query") { var getUrl = new URL(PageConfig.getBaseUrl() + 'edsc/getQuery'); - getUrl.searchParams.append("json_obj", JSON.stringify(globals.params)); + if (query_type === 'granule') { + getUrl.searchParams.append("cmr_query", globals.granuleQuery); + getUrl.searchParams.append("query_type", 'granule'); + } else { + getUrl.searchParams.append("cmr_query", globals.collectionQuery); + getUrl.searchParams.append("query_type", 'collection'); + } getUrl.searchParams.append("limit", globals.limit); // Make call to back end @@ -111,18 +126,16 @@ function activate(app: JupyterFrontEnd, xhr.onload = function() { if (xhr.status == 200) { let response: any = $.parseJSON(xhr.response); - console.log(response); response_text = response.query_string; if (response_text == "") { response_text = "No results found."; } - console.log(response_text); if (current) { NotebookActions.insertBelow(current.content); NotebookActions.paste(current.content); current.content.mode = 'edit'; - current.content.activeCell.model.value.text = response_text; - console.log("inserted text"); + const insert_text = "# generated from this EDSC search: " + globals.edscUrl + "\n" + response_text; + current.content.activeCell.model.value.text = insert_text; } } else { @@ -132,7 +145,7 @@ function activate(app: JupyterFrontEnd, }; xhr.onerror = function() { - console.log("Error making call to get query"); + console.error("Error making call to get query"); }; xhr.open("GET", getUrl.href, true); @@ -143,13 +156,11 @@ function activate(app: JupyterFrontEnd, } else { var getUrl = new URL(PageConfig.getBaseUrl() + 'edsc/getGranules'); - getUrl.searchParams.append("json_obj", JSON.stringify(globals.params)); + getUrl.searchParams.append("cmr_query", globals.granuleQuery); getUrl.searchParams.append("limit", globals.limit); - // Make call to back end var xhr = new XMLHttpRequest(); - let url_response:any = []; xhr.onload = function() { @@ -160,13 +171,12 @@ function activate(app: JupyterFrontEnd, response_text = "No results found."; } url_response = response_text; - console.log(response_text); if (current) { NotebookActions.insertBelow(current.content); NotebookActions.paste(current.content); current.content.mode = 'edit'; - current.content.activeCell.model.value.text = url_response; - console.log("inserted text"); + const insert_text = "# generated from this EDSC search: " + globals.edscUrl + "\n" + url_response; + current.content.activeCell.model.value.text = insert_text; } } else { @@ -195,9 +205,6 @@ function activate(app: JupyterFrontEnd, label: 'Open EarthData Search', isEnabled: () => true, execute: args => { - - console.log(widget); - // Only allow user to have one EDSC window if (widget == undefined) { widget = new IFrameWidget(SEARCH_CLIENT_URL); @@ -210,7 +217,6 @@ function activate(app: JupyterFrontEnd, } if (!instanceTracker.has(widget)) { - console.log("in has widget"); // Track the state of the widget for later restoration instanceTracker.add(widget); } @@ -228,19 +234,29 @@ function activate(app: JupyterFrontEnd, }); palette.addItem({command: display_params_command, category: 'Search'}); - const paste_query_command = 'search:pasteQuery'; - app.commands.addCommand(paste_query_command, { - label: 'Paste Search Query', + const paste_collection_query_command = 'search:pasteCollectionQuery'; + app.commands.addCommand(paste_collection_query_command, { + label: 'Paste Collection Search Query', + isEnabled: () => true, + execute: args => { + pasteSearch(args, "query", "collection") + } + }); + palette.addItem({command: paste_collection_query_command, category: 'Search'}); + + const paste_granule_query_command = 'search:pasteGranuleQuery'; + app.commands.addCommand(paste_granule_query_command, { + label: 'Paste Granule Search Query', isEnabled: () => true, execute: args => { - pasteSearch(args, "query") + pasteSearch(args, "query", "granule") } }); - palette.addItem({command: paste_query_command, category: 'Search'}); + palette.addItem({command: paste_granule_query_command, category: 'Search'}); const paste_results_command = 'search:pasteResults'; app.commands.addCommand(paste_results_command, { - label: 'Paste Search Results', + label: 'Paste Granule Search Results', isEnabled: () => true, execute: args => { pasteSearch(args, "results") @@ -266,7 +282,8 @@ function activate(app: JupyterFrontEnd, [ open_command, display_params_command, - paste_query_command, + paste_collection_query_command, + paste_granule_query_command, paste_results_command, set_limit_command ].forEach(command => { diff --git a/edsc_extension/src/searchKeys.js b/edsc_extension/src/searchKeys.js new file mode 100644 index 00000000..a90ac9d4 --- /dev/null +++ b/edsc_extension/src/searchKeys.js @@ -0,0 +1,95 @@ +/** + * Whitelist parameters supplied by the request + * Modified from https://github.com/nasa/earthdata-search/blob/f09ff3bfd40420322f005654bc349374aab1fe57/static/src/js/util/request/granuleRequest.js + */ +export const granulePermittedCmrKeys = [ + 'concept_id', + 'bounding_box', + 'circle', + 'browse_only', + 'cloud_cover', + 'day_night_flag', + 'echo_collection_id', + 'equator_crossing_date', + 'equator_crossing_longitude', + 'exclude', + 'line', + 'online_only', + 'options', + 'orbit_number', + 'page_num', + 'page_size', + 'point', + 'polygon', + 'readable_granule_name', + 'sort_key', + 'temporal', + 'two_d_coordinate_system' +] + +export const granuleNonIndexedKeys = [ + 'concept_id', + 'exclude', + 'readable_granule_name', + 'sort_key' +] + +// Whitelist parameters supplied by the request +export const collectionPermittedCmrKeys = [ + 'bounding_box', + 'circle', + 'collection_data_type', + 'concept_id', + 'data_center_h', + 'data_center', + 'echo_collection_id', + 'facets_size', + 'format', + 'granule_data_format', + 'granule_data_format_h', + 'has_granules_or_cwic', + 'has_granules', + 'include_facets', + 'include_granule_counts', + 'include_has_granules', + 'include_tags', + 'instrument', + 'instrument_h', + 'keyword', + 'line', + 'options', + 'page_num', + 'page_size', + 'platform', + 'platform_h', + 'point', + 'polygon', + 'processing_level_id_h', + 'project_h', + 'project', + 'provider', + 'science_keywords_h', + 'sort_key', + 'spatial_keyword', + 'tag_key', + 'temporal', + 'two_d_coordinate_system' +] + +export const collectionNonIndexedKeys = [ + 'collection_data_type', + 'concept_id', + 'data_center_h', + 'granule_data_format', + 'granule_data_format_h', + 'instrument', + 'instrument_h', + 'platform', + 'platform_h', + 'processing_level_id_h', + 'project_h', + 'provider', + 'sort_key', + 'spatial_keyword', + 'tag_key' +] \ No newline at end of file diff --git a/edsc_extension/src/urlParser.js b/edsc_extension/src/urlParser.js new file mode 100644 index 00000000..3128f702 --- /dev/null +++ b/edsc_extension/src/urlParser.js @@ -0,0 +1,162 @@ +/** + * + * Url Decoder copied from earthdata search source code + * https://github.com/nasa/earthdata-search/blob/master/static/src/js/util/url/url.js#L78 + * + * June 30, 2020 - https://github.com/nasa/earthdata-search/releases/tag/v1.123.14 + * + */ + + +import qs from 'qs' + +import { decodeFeatures, encodeFeatures } from './encodersDecoders' +import { decodeFacets, encodeFacets } from './encodersDecoders' +import { decodeMap, encodeMap } from './encodersDecoders' +import { decodeScienceKeywords, encodeScienceKeywords } from './encodersDecoders' +import { decodeString, encodeString } from './encodersDecoders' +import { decodeTemporal, encodeTemporal } from './encodersDecoders' +import { decodeTimeline, encodeTimeline } from './encodersDecoders' +import { decodeCollections, encodeCollections } from './encodersDecoders' +import { decodeGridCoords, encodeGridCoords } from './encodersDecoders' +import { decodeHasGranulesOrCwic, encodeHasGranulesOrCwic } from './encodersDecoders' +import { encodeAdvancedSearch, decodeAdvancedSearch } from './encodersDecoders' +import { encodeAutocomplete, decodeAutocomplete } from './encodersDecoders' + +/** + * Mapping of URL Shortened Keys to their redux store keys + */ +const urlDefs = { + focusedGranule: { shortKey: 'g', encode: encodeString, decode: decodeString }, + keywordSearch: { shortKey: 'q', encode: encodeString, decode: decodeString }, + pointSearch: { shortKey: 'sp', encode: encodeString, decode: decodeString }, + boundingBoxSearch: { shortKey: 'sb', encode: encodeString, decode: decodeString }, + polygonSearch: { shortKey: 'polygon', encode: encodeString, decode: decodeString }, + lineSearch: { shortKey: 'line', encode: encodeString, decode: decodeString }, + circleSearch: { shortKey: 'circle', encode: encodeString, decode: decodeString }, + map: { shortKey: 'm', encode: encodeMap, decode: decodeMap }, + temporalSearch: { shortKey: 'qt', encode: encodeTemporal, decode: decodeTemporal }, + overrideTemporalSearch: { shortKey: 'ot', encode: encodeTemporal, decode: decodeTemporal }, + featureFacets: { shortKey: 'ff', encode: encodeFeatures, decode: decodeFeatures }, + platformFacets: { shortKey: 'fp', encode: encodeFacets, decode: decodeFacets }, + instrumentFacets: { shortKey: 'fi', encode: encodeFacets, decode: decodeFacets }, + organizationFacets: { shortKey: 'fdc', encode: encodeFacets, decode: decodeFacets }, + projectFacets: { shortKey: 'fpj', encode: encodeFacets, decode: decodeFacets }, + processingLevelFacets: { shortKey: 'fl', encode: encodeFacets, decode: decodeFacets }, + granuleDataFormatFacets: { shortKey: 'gdf', encode: encodeFacets, decode: decodeFacets }, + gridName: { shortKey: 's2n', encode: encodeString, decode: decodeString }, + gridCoords: { shortKey: 's2c', encode: encodeGridCoords, decode: decodeGridCoords }, + shapefileId: { shortKey: 'sf', encode: encodeString, decode: decodeString }, + tagKey: { shortKey: 'tag_key', encode: encodeString, decode: decodeString }, + hasGranulesOrCwic: { shortKey: 'ac', encode: encodeHasGranulesOrCwic, decode: decodeHasGranulesOrCwic }, + autocompleteSelected: { shortKey: 'as', encode: encodeAutocomplete, decode: decodeAutocomplete } +} + +/** + * Helper method to decode a given paramName from URL parameters base on urlDefs keys + * @param {object} params Object of encoded URL parameters + * @param {string} paramName Param to decode + */ +const decodeHelp = (params, paramName) => { + const value = params[urlDefs[paramName].shortKey] + return urlDefs[paramName].decode(value) +} + +/** + * Given a URL param string, returns an object that matches the redux store + * @param {string} paramString a URL encoded parameter string + * @return {object} An object of values that match the redux store + */ +export const decodeUrlParams = (paramString) => { + // decode the paramString + const params = qs.parse(paramString, { ignoreQueryPrefix: true, parseArrays: false }) + + // build the param object based on the structure in the redux store + // e.g. map is store separately from query + const focusedGranule = decodeHelp(params, 'focusedGranule') + + const map = decodeHelp(params, 'map') + + const spatial = {} + spatial.point = decodeHelp(params, 'pointSearch') + spatial.boundingBox = decodeHelp(params, 'boundingBoxSearch') + spatial.polygon = decodeHelp(params, 'polygonSearch') + spatial.line = decodeHelp(params, 'lineSearch') + spatial.circle = decodeHelp(params, 'circleSearch') + + const collectionQuery = { pageNum: 1 } + const granuleQuery = { pageNum: 1 } + collectionQuery.spatial = spatial + collectionQuery.keyword = decodeHelp(params, 'keywordSearch') + collectionQuery.temporal = decodeHelp(params, 'temporalSearch') + collectionQuery.overrideTemporal = decodeHelp(params, 'overrideTemporalSearch') + collectionQuery.gridName = decodeHelp(params, 'gridName') + collectionQuery.tagKey = decodeHelp(params, 'tagKey') + collectionQuery.hasGranulesOrCwic = decodeHelp(params, 'hasGranulesOrCwic') + granuleQuery.gridCoords = decodeHelp(params, 'gridCoords') + + const query = { + collection: collectionQuery, + granule: granuleQuery + } + + const timeline = decodeTimeline(params) + + const featureFacets = decodeHelp(params, 'featureFacets') + const granuleDataFormats = decodeHelp(params, 'granuleDataFormatFacets') + const instruments = decodeHelp(params, 'instrumentFacets') + const organizations = decodeHelp(params, 'organizationFacets') + const platforms = decodeHelp(params, 'platformFacets') + const processingLevels = decodeHelp(params, 'processingLevelFacets') + const projects = decodeHelp(params, 'projectFacets') + const scienceKeywords = decodeScienceKeywords(params) + + const { + collections, + focusedCollection, + project + } = decodeCollections(params) + + const cmrFacets = { + data_center_h: organizations, + instrument_h: instruments, + granule_data_format_h: granuleDataFormats, + platform_h: platforms, + processing_level_id_h: processingLevels, + project_h: projects, + science_keywords_h: scienceKeywords + } + + const shapefile = { + shapefileId: decodeHelp(params, 'shapefileId') + } + + const advancedSearch = decodeAdvancedSearch(params) + + const autocompleteSelected = decodeHelp(params, 'autocompleteSelected') + + // Fetch collections in the project + const { collectionIds = [] } = project || {} + + // Create a unique list of collections to fetch and remove any empty values [.filter(Boolean)] + const uniqueCollectionList = [...new Set([ + ...collectionIds, + focusedCollection + ])].filter(Boolean) + // console.log(project, uniqueCollectionList); + + return { + advancedSearch, + autocompleteSelected, + cmrFacets, + collections, + featureFacets, + focusedCollection, + focusedGranule, + map, + conceptId: uniqueCollectionList, + query, + shapefile, + timeline + } +} diff --git a/edsc_extension/src/widgets.ts b/edsc_extension/src/widgets.ts index 9663f656..c9c538e9 100644 --- a/edsc_extension/src/widgets.ts +++ b/edsc_extension/src/widgets.ts @@ -64,7 +64,8 @@ class ParamsPopupWidget extends Widget { let body = document.createElement('div'); body.style.display = 'flex'; body.style.flexDirection = 'column'; - body.innerHTML = "
" + JSON.stringify(globals.params, null, " ") + "

" + body.innerHTML = "
Granule search: " + JSON.stringify(globals.granuleParams, null, " ") + "

" + + "
Collection search: " + JSON.stringify(globals.collectionParams, null, " ") + "

" + "
Results Limit: " + globals.limit + "
"; super({ node: body }); diff --git a/submit_jobs/src/activate.ts b/submit_jobs/src/activate.ts index f2740e99..18f0858b 100644 --- a/submit_jobs/src/activate.ts +++ b/submit_jobs/src/activate.ts @@ -69,11 +69,7 @@ export function activateRegisterAlgorithm( let path = PageConfig.getOption('serverRoot') + '/' + item.path; console.log(path); - state.fetch(profileId).then((profile) => { - let profileObj = JSON.parse(JSON.stringify(profile)); - let uname:string = profileObj.preferred_username; - let ticket:string = profileObj.proxyGrantingTicket; - + getUsernameToken(state,profileId,function(uname:string,ticket:string) { // send request to defaultvalueshandler let getValuesFn = function(resp:Object) { console.log('getValuesFn'); diff --git a/submit_jobs/src/funcs.ts b/submit_jobs/src/funcs.ts index ce580725..bbc9de61 100644 --- a/submit_jobs/src/funcs.ts +++ b/submit_jobs/src/funcs.ts @@ -96,7 +96,11 @@ export function inputRequest(endpt:string,title:string,inputs:{[k:string]:string // console.log(fn); if (fn == undefined) { console.log('fn undefined'); - popupResultText(json_response['result'],title); + if (endpt === 'listJobs') { + popupResultText(Object.keys(json_response['jobs']).join('
'),title); + } else { + popupResultText(json_response['result'],title); + } } else { console.log('fn defined'); fn(json_response);