diff --git a/src/actions/vis-state-actions.d.ts b/src/actions/vis-state-actions.d.ts index 3f41289a5a..3438111cf6 100644 --- a/src/actions/vis-state-actions.d.ts +++ b/src/actions/vis-state-actions.d.ts @@ -243,6 +243,15 @@ export function updateVisData( config: AddDataToMapPayload['config'] ): Merge; +export type RenameDatasetUpdaterAction = { + dataId: string; + label: string; +}; +export function renameDataset( + dataId: string, + label: string +): Merge; + export type ToggleFilterAnimationUpdaterAction = { idx; }; diff --git a/src/actions/vis-state-actions.js b/src/actions/vis-state-actions.js index 069e011340..adee05d2af 100644 --- a/src/actions/vis-state-actions.js +++ b/src/actions/vis-state-actions.js @@ -405,6 +405,23 @@ export function updateVisData(datasets, options, config) { }; } +/** + * Rename an existing dataset in `visState` + * @memberof visStateActions + * @param dataId - ***required** Id of the dataset to update + * @param label - ***required** New name for the dataset + * @returns action + * @type {typeof import('./vis-state-actions').renameDataset} + * @public + */ +export function renameDataset(dataId, label) { + return { + type: ActionTypes.RENAME_DATASET, + dataId, + label + }; +} + /** * Start and end filter animation * @memberof visStateActions diff --git a/src/constants/action-types.d.ts b/src/constants/action-types.d.ts index 4a91d33b49..e8b8232e1c 100644 --- a/src/constants/action-types.d.ts +++ b/src/constants/action-types.d.ts @@ -28,6 +28,7 @@ export type ActionType = { SHOW_DATASET_TABLE: string; UPDATE_LAYER_BLENDING: string; UPDATE_VIS_DATA: string; + RENAME_DATASET: string; TOGGLE_FILTER_ANIMATION: string; UPDATE_FILTER_ANIMATION_SPEED: string; PLAY_ANIMATION: string; diff --git a/src/constants/action-types.js b/src/constants/action-types.js index 012ef893d2..d4385403cf 100644 --- a/src/constants/action-types.js +++ b/src/constants/action-types.js @@ -80,6 +80,7 @@ const ActionTypes = { SHOW_DATASET_TABLE: `${ACTION_PREFIX}SHOW_DATASET_TABLE`, UPDATE_LAYER_BLENDING: `${ACTION_PREFIX}UPDATE_LAYER_BLENDING`, UPDATE_VIS_DATA: `${ACTION_PREFIX}UPDATE_VIS_DATA`, + RENAME_DATASET: `${ACTION_PREFIX}RENAME_DATASET`, TOGGLE_FILTER_ANIMATION: `${ACTION_PREFIX}TOGGLE_FILTER_ANIMATION`, UPDATE_FILTER_ANIMATION_SPEED: `${ACTION_PREFIX}UPDATE_FILTER_ANIMATION_SPEED`, PLAY_ANIMATION: `${ACTION_PREFIX}PLAY_ANIMATION`, diff --git a/src/processors/data-processor.js b/src/processors/data-processor.js index 63e1c6cdb7..80b769d4dd 100644 --- a/src/processors/data-processor.js +++ b/src/processors/data-processor.js @@ -406,6 +406,7 @@ export function analyzerTypeToFieldType(aType) { /** * Process data where each row is an object, output can be passed to [`addDataToMap`](../actions/actions.md#adddatatomap) + * NOTE: This function may mutate input. * @param rawData an array of row object, each object should have the same number of keys * @returns dataset containing `fields` and `rows` * @type {typeof import('./data-processor').processRowObject} @@ -443,7 +444,8 @@ export function processRowObject(rawData) { /** * Process GeoJSON [`FeatureCollection`](http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection), * output a data object with `{fields: [], rows: []}`. - * The data object can be wrapped in a `dataset` and pass to [`addDataToMap`](../actions/actions.md#adddatatomap) + * The data object can be wrapped in a `dataset` and passed to [`addDataToMap`](../actions/actions.md#adddatatomap) + * NOTE: This function may mutate input. * * @param rawData raw geojson feature collection * @returns dataset containing `fields` and `rows` diff --git a/src/reducers/vis-state-updaters.d.ts b/src/reducers/vis-state-updaters.d.ts index c50d5687a7..64b1b70954 100644 --- a/src/reducers/vis-state-updaters.d.ts +++ b/src/reducers/vis-state-updaters.d.ts @@ -430,6 +430,10 @@ export function updateVisDataUpdater( state: VisState, action: VisStateActions.UpdateVisDataUpdaterAction ): VisState; +export function renameDatasetUpdater( + state: VisState, + action: VisStateActions.RenameDatasetUpdaterAction +): VisState; export function toggleFilterAnimationUpdater( state: VisState, action: VisStateActions.ToggleFilterAnimationUpdaterAction diff --git a/src/reducers/vis-state-updaters.js b/src/reducers/vis-state-updaters.js index adf8addfbd..27bee407ce 100644 --- a/src/reducers/vis-state-updaters.js +++ b/src/reducers/vis-state-updaters.js @@ -1305,6 +1305,31 @@ export const updateVisDataUpdater = (state, action) => { }; /* eslint-enable max-statements */ +/** + * Rename an existing dataset in `visState` + * @memberof visStateUpdaters + * @type {typeof import('./vis-state-updaters').renameDatasetUpdater} + * @public + */ +export function renameDatasetUpdater(state, action) { + const {dataId, label} = action; + const {datasets} = state; + const existing = datasets[dataId]; + return existing + ? { + ...state, + datasets: { + ...datasets, + [dataId]: { + ...existing, + label + } + } + } + : // No-op if the dataset doesn't exist + state; +} + /** * When a user clicks on the specific map closing icon * the application will close the selected map diff --git a/src/reducers/vis-state.js b/src/reducers/vis-state.js index ffc27f4356..59ae8c2c1f 100644 --- a/src/reducers/vis-state.js +++ b/src/reducers/vis-state.js @@ -105,6 +105,8 @@ const actionHandler = { [ActionTypes.UPDATE_VIS_DATA]: visStateUpdaters.updateVisDataUpdater, + [ActionTypes.RENAME_DATASET]: visStateUpdaters.renameDatasetUpdater, + [ActionTypes.SET_FEATURES]: visStateUpdaters.setFeaturesUpdater, [ActionTypes.DELETE_FEATURE]: visStateUpdaters.deleteFeatureUpdater, diff --git a/test/node/reducers/vis-state-test.js b/test/node/reducers/vis-state-test.js index 5b48df31af..7893c803b9 100644 --- a/test/node/reducers/vis-state-test.js +++ b/test/node/reducers/vis-state-test.js @@ -51,7 +51,7 @@ import { fields as geojsonFields } from 'test/fixtures/geojson'; -import tripGeojson, {timeStampDomain} from 'test/fixtures/trip-geojson'; +import tripGeojson, {timeStampDomain, tripDataInfo} from 'test/fixtures/trip-geojson'; import {mockPolygonFeature, mockPolygonFeature2, mockPolygonData} from 'test/fixtures/polygon'; // test helpers @@ -1392,7 +1392,7 @@ test('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> geojson data', t => { const expectedLayerData = { data: geojsonData.features.map((f, i) => ({ ...f, - properties: {...f.properties, index: i} + properties: {...f.properties, TRIPS: f.properties.TRIPS || null, index: i} })) }; @@ -1410,7 +1410,7 @@ test('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> geojson data', t => { t.deepEqual( initialState.layerData[0].data, expectedLayerData.data, - 'should save geojson to datasets' + 'should save geojson to layer data' ); t.end(); @@ -1963,6 +1963,23 @@ test('#visStateReducer -> setFilter.dynamicDomain & cpu', t => { t.end(); }); +test('#visStateReducer -> RENAME_DATASET', t => { + const initialState = StateWTripGeojson.visState; + + t.equal( + initialState.datasets[tripDataInfo.id].label, + tripDataInfo.label, + 'Initial label as expected' + ); + + const newLabel = 'New label!!!11'; + const updated = reducer(initialState, VisStateActions.renameDataset(tripDataInfo.id, newLabel)); + + t.equal(updated.datasets[tripDataInfo.id].label, newLabel, 'Updated label as expected'); + + t.end(); +}); + test('#visStateReducer -> SET_FILTER.name', t => { const oldState = StateWFilters.visState; const oldFilter0 = oldState.filters[0]; diff --git a/test/node/utils/data-processor-test.js b/test/node/utils/data-processor-test.js index a95413eb04..c3262176d0 100644 --- a/test/node/utils/data-processor-test.js +++ b/test/node/utils/data-processor-test.js @@ -22,6 +22,7 @@ import test from 'tape'; import sinon from 'sinon'; import {console as Console} from 'global/window'; import {DATA_TYPES} from 'type-analyzer'; +import cloneDeep from 'lodash.clonedeep'; import testData, { dataWithNulls, @@ -118,7 +119,7 @@ test('Processor -> processCsvData', t => { t.throws(() => processCsvData(''), 'should throw if csv is empty'); // load sample dataset csv as text - const {fields, rows} = processCsvData(testData); + const {fields, rows} = processCsvData(cloneDeep(testData)); t.equal(rows.length, testAllData.length, `should return ${testAllData.length} rows`); @@ -188,7 +189,7 @@ test('Processor -> processCsv.wkt', t => { }); test('Processor => processGeojson', t => { - const {fields, rows} = processGeojson(geojsonData); + const {fields, rows} = processGeojson(cloneDeep(geojsonData)); t.equal(fields.length, geojsonFields.length, 'should have same field length'); fields.forEach((f, i) => { @@ -203,7 +204,7 @@ test('Processor => processGeojson', t => { }); test('Processor => processGeojson: with style property', t => { - const {fields, rows} = processGeojson(geoJsonWithStyle); + const {fields, rows} = processGeojson(cloneDeep(geoJsonWithStyle)); t.deepEqual(fields, geoStyleFields, 'should preserve objects in geojson properties'); t.deepEqual(rows, geoStyleRows, 'should preserve objects in geojson properties');