Commit b8f91478 authored by Michael Lunøe's avatar Michael Lunøe Committed by Jose Ivan Vargas

Feat(Instance Statistics): add general chart comp

Add general component for instance statistics
count charts to be reused, so the user can both
see issues and merge request data as well as
pipeline statistics. This will also be useful for
other charts on the same page.

Refs:
https://gitlab.com/gitlab-org/gitlab/-/issues/267538
https://gitlab.com/gitlab-org/gitlab/-/issues/246491
https://gitlab.com/gitlab-org/gitlab/-/issues/268224
https://gitlab.com/gitlab-org/gitlab/-/issues/267997
https://gitlab.com/gitlab-org/gitlab/-/issues/233854
parent 1bb87641
<script> <script>
import { s__ } from '~/locale';
import InstanceCounts from './instance_counts.vue'; import InstanceCounts from './instance_counts.vue';
import PipelinesChart from './pipelines_chart.vue'; import InstanceStatisticsCountChart from './instance_statistics_count_chart.vue';
import UsersChart from './users_chart.vue'; import UsersChart from './users_chart.vue';
import pipelinesStatsQuery from '../graphql/queries/pipeline_stats.query.graphql';
import issuesAndMergeRequestsQuery from '../graphql/queries/issues_and_merge_requests.query.graphql';
import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants'; import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
const PIPELINES_KEY_TO_NAME_MAP = {
total: s__('InstanceAnalytics|Total'),
succeeded: s__('InstanceAnalytics|Succeeded'),
failed: s__('InstanceAnalytics|Failed'),
canceled: s__('InstanceAnalytics|Canceled'),
skipped: s__('InstanceAnalytics|Skipped'),
};
const ISSUES_AND_MERGE_REQUESTS_KEY_TO_NAME_MAP = {
issues: s__('InstanceAnalytics|Issues'),
mergeRequests: s__('InstanceAnalytics|Merge Requests'),
};
const loadPipelineChartError = s__(
'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.',
);
const loadIssuesAndMergeRequestsChartError = s__(
'InstanceAnalytics|Could not load the issues and merge requests chart. Please refresh the page to try again.',
);
const noDataMessage = s__('InstanceAnalytics|There is no data available.');
export default { export default {
name: 'InstanceStatisticsApp', name: 'InstanceStatisticsApp',
components: { components: {
InstanceCounts, InstanceCounts,
PipelinesChart, InstanceStatisticsCountChart,
UsersChart, UsersChart,
}, },
TOTAL_DAYS_TO_SHOW, TOTAL_DAYS_TO_SHOW,
START_DATE, START_DATE,
TODAY, TODAY,
configs: [
{
keyToNameMap: PIPELINES_KEY_TO_NAME_MAP,
prefix: 'pipelines',
loadChartError: loadPipelineChartError,
noDataMessage,
chartTitle: s__('InstanceAnalytics|Pipelines'),
yAxisTitle: s__('InstanceAnalytics|Items'),
xAxisTitle: s__('InstanceAnalytics|Month'),
query: pipelinesStatsQuery,
},
{
keyToNameMap: ISSUES_AND_MERGE_REQUESTS_KEY_TO_NAME_MAP,
prefix: 'issuesAndMergeRequests',
loadChartError: loadIssuesAndMergeRequestsChartError,
noDataMessage,
chartTitle: s__('InstanceAnalytics|Issues & Merge Requests'),
yAxisTitle: s__('InstanceAnalytics|Items'),
xAxisTitle: s__('InstanceAnalytics|Month'),
query: issuesAndMergeRequestsQuery,
},
],
}; };
</script> </script>
...@@ -25,6 +69,17 @@ export default { ...@@ -25,6 +69,17 @@ export default {
:end-date="$options.TODAY" :end-date="$options.TODAY"
:total-data-points="$options.TOTAL_DAYS_TO_SHOW" :total-data-points="$options.TOTAL_DAYS_TO_SHOW"
/> />
<pipelines-chart /> <instance-statistics-count-chart
v-for="chartOptions in $options.configs"
:key="chartOptions.chartTitle"
:prefix="chartOptions.prefix"
:key-to-name-map="chartOptions.keyToNameMap"
:query="chartOptions.query"
:x-axis-title="chartOptions.xAxisTitle"
:y-axis-title="chartOptions.yAxisTitle"
:load-chart-error-message="chartOptions.loadChartError"
:no-data-message="chartOptions.noDataMessage"
:chart-title="chartOptions.chartTitle"
/>
</div> </div>
</template> </template>
<script> <script>
import { GlLineChart } from '@gitlab/ui/dist/charts'; import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import { mapKeys, mapValues, pick, some, sum } from 'lodash'; import { mapValues, some, sum } from 'lodash';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { s__ } from '~/locale';
import { import {
differenceInMonths, differenceInMonths,
formatDateAsMonth, formatDateAsMonth,
getDayDifference, getDayDifference,
} from '~/lib/utils/datetime_utility'; } from '~/lib/utils/datetime_utility';
import { convertToTitleCase } from '~/lib/utils/text_utility';
import { getAverageByMonth, sortByDate, extractValues } from '../utils'; import { getAverageByMonth, sortByDate, extractValues } from '../utils';
import pipelineStatsQuery from '../graphql/queries/pipeline_stats.query.graphql';
import { TODAY, START_DATE } from '../constants'; import { TODAY, START_DATE } from '../constants';
const DATA_KEYS = [
'pipelinesTotal',
'pipelinesSucceeded',
'pipelinesFailed',
'pipelinesCanceled',
'pipelinesSkipped',
];
const PREFIX = 'pipelines';
export default { export default {
name: 'PipelinesChart', name: 'InstanceStatisticsCountChart',
components: { components: {
GlLineChart, GlLineChart,
GlAlert, GlAlert,
...@@ -31,19 +21,42 @@ export default { ...@@ -31,19 +21,42 @@ export default {
}, },
startDate: START_DATE, startDate: START_DATE,
endDate: TODAY, endDate: TODAY,
i18n: { dataKey: 'nodes',
loadPipelineChartError: s__( pageInfoKey: 'pageInfo',
'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.', firstKey: 'first',
), props: {
noDataMessage: s__('InstanceAnalytics|There is no data available.'), prefix: {
total: s__('InstanceAnalytics|Total'), type: String,
succeeded: s__('InstanceAnalytics|Succeeded'), required: true,
failed: s__('InstanceAnalytics|Failed'), },
canceled: s__('InstanceAnalytics|Canceled'), keyToNameMap: {
skipped: s__('InstanceAnalytics|Skipped'), type: Object,
chartTitle: s__('InstanceAnalytics|Pipelines'), required: true,
yAxisTitle: s__('InstanceAnalytics|Items'), },
xAxisTitle: s__('InstanceAnalytics|Month'), chartTitle: {
type: String,
required: true,
},
loadChartErrorMessage: {
type: String,
required: true,
},
noDataMessage: {
type: String,
required: true,
},
xAxisTitle: {
type: String,
required: true,
},
yAxisTitle: {
type: String,
required: true,
},
query: {
type: Object,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -53,19 +66,23 @@ export default { ...@@ -53,19 +66,23 @@ export default {
}, },
apollo: { apollo: {
pipelineStats: { pipelineStats: {
query: pipelineStatsQuery, query() {
return this.query;
},
variables() { variables() {
return { return this.nameKeys.reduce((memo, key) => {
firstTotal: this.totalDaysToShow, const firstKey = `${this.$options.firstKey}${convertToTitleCase(key)}`;
firstSucceeded: this.totalDaysToShow, return { ...memo, [firstKey]: this.totalDaysToShow };
firstFailed: this.totalDaysToShow, }, {});
firstCanceled: this.totalDaysToShow,
firstSkipped: this.totalDaysToShow,
};
}, },
update(data) { update(data) {
const allData = extractValues(data, DATA_KEYS, PREFIX, 'nodes'); const allData = extractValues(data, this.nameKeys, this.prefix, this.$options.dataKey);
const allPageInfo = extractValues(data, DATA_KEYS, PREFIX, 'pageInfo'); const allPageInfo = extractValues(
data,
this.nameKeys,
this.prefix,
this.$options.pageInfoKey,
);
return { return {
...mapValues(allData, sortByDate), ...mapValues(allData, sortByDate),
...@@ -83,6 +100,9 @@ export default { ...@@ -83,6 +100,9 @@ export default {
}, },
}, },
computed: { computed: {
nameKeys() {
return Object.keys(this.keyToNameMap);
},
isLoading() { isLoading() {
return this.$apollo.queries.pipelineStats.loading; return this.$apollo.queries.pipelineStats.loading;
}, },
...@@ -90,37 +110,35 @@ export default { ...@@ -90,37 +110,35 @@ export default {
return getDayDifference(this.$options.startDate, this.$options.endDate); return getDayDifference(this.$options.startDate, this.$options.endDate);
}, },
firstVariables() { firstVariables() {
const allData = pick(this.pipelineStats, [ const firstDataPoints = extractValues(
'nodesTotal', this.pipelineStats,
'nodesSucceeded', this.nameKeys,
'nodesFailed', this.$options.dataKey,
'nodesCanceled', '[0].recordedAt',
'nodesSkipped', { renameKey: this.$options.firstKey },
]); );
const allDayDiffs = mapValues(allData, data => {
const firstdataPoint = data[0]; return Object.keys(firstDataPoints).reduce((memo, name) => {
if (!firstdataPoint) { const recordedAt = firstDataPoints[name];
return 0; if (!recordedAt) {
return { ...memo, [name]: 0 };
} }
return Math.max( const numberOfDays = Math.max(
0, 0,
getDayDifference(this.$options.startDate, new Date(firstdataPoint.recordedAt)), getDayDifference(this.$options.startDate, new Date(recordedAt)),
); );
});
return mapKeys(allDayDiffs, (value, key) => key.replace('nodes', 'first')); return { ...memo, [name]: numberOfDays };
}, {});
}, },
cursorVariables() { cursorVariables() {
const pageInfoKeys = [ return extractValues(
'pageInfoTotal', this.pipelineStats,
'pageInfoSucceeded', this.nameKeys,
'pageInfoFailed', this.$options.pageInfoKey,
'pageInfoCanceled', 'endCursor',
'pageInfoSkipped', );
];
return extractValues(this.pipelineStats, pageInfoKeys, 'pageInfo', 'endCursor');
}, },
hasNextPage() { hasNextPage() {
return ( return (
...@@ -132,19 +150,13 @@ export default { ...@@ -132,19 +150,13 @@ export default {
return this.chartData.every(({ data }) => data.length === 0); return this.chartData.every(({ data }) => data.length === 0);
}, },
chartData() { chartData() {
const allData = pick(this.pipelineStats, [
'nodesTotal',
'nodesSucceeded',
'nodesFailed',
'nodesCanceled',
'nodesSkipped',
]);
const options = { shouldRound: true }; const options = { shouldRound: true };
return Object.keys(allData).map(key => {
const i18nName = key.slice('nodes'.length).toLowerCase(); return this.nameKeys.map(key => {
const dataKey = `${this.$options.dataKey}${convertToTitleCase(key)}`;
return { return {
name: this.$options.i18n[i18nName], name: this.keyToNameMap[key],
data: getAverageByMonth(allData[key], options), data: getAverageByMonth(this.pipelineStats?.[dataKey], options),
}; };
}); });
}, },
...@@ -155,11 +167,11 @@ export default { ...@@ -155,11 +167,11 @@ export default {
}; };
}, },
chartOptions() { chartOptions() {
const { endDate, startDate, i18n } = this.$options; const { endDate, startDate } = this.$options;
return { return {
xAxis: { xAxis: {
...this.range, ...this.range,
name: i18n.xAxisTitle, name: this.xAxisTitle,
type: 'time', type: 'time',
splitNumber: differenceInMonths(startDate, endDate) + 1, splitNumber: differenceInMonths(startDate, endDate) + 1,
axisLabel: { axisLabel: {
...@@ -171,7 +183,7 @@ export default { ...@@ -171,7 +183,7 @@ export default {
}, },
}, },
yAxis: { yAxis: {
name: i18n.yAxisTitle, name: this.yAxisTitle,
}, },
}; };
}, },
...@@ -202,13 +214,13 @@ export default { ...@@ -202,13 +214,13 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<h3>{{ $options.i18n.chartTitle }}</h3> <h3>{{ chartTitle }}</h3>
<gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3"> <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
{{ this.$options.i18n.loadPipelineChartError }} {{ loadChartErrorMessage }}
</gl-alert> </gl-alert>
<chart-skeleton-loader v-else-if="isLoading" /> <chart-skeleton-loader v-else-if="isLoading" />
<gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3"> <gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
{{ $options.i18n.noDataMessage }} {{ noDataMessage }}
</gl-alert> </gl-alert>
<gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" /> <gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" />
</div> </div>
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "./count.fragment.graphql"
query issuesAndMergeRequests(
$firstIssues: Int
$firstMergeRequests: Int
$endCursorIssues: String
$endCursorMergeRequests: String
) {
issuesAndMergeRequestsIssues: instanceStatisticsMeasurements(
identifier: ISSUES
first: $firstIssues
after: $endCursorIssues
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
issuesAndMergeRequestsMergeRequests: instanceStatisticsMeasurements(
identifier: MERGE_REQUESTS
first: $firstMergeRequests
after: $endCursorMergeRequests
) {
nodes {
...Count
}
pageInfo {
...PageInfo
}
}
}
import { masks } from 'dateformat'; import { masks } from 'dateformat';
import { mapKeys, mapValues, pick, sortBy } from 'lodash'; import { get, sortBy } from 'lodash';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import { convertToTitleCase } from '~/lib/utils/text_utility';
const { isoDate } = masks; const { isoDate } = masks;
...@@ -46,16 +47,25 @@ export function getAverageByMonth(items = [], options = {}) { ...@@ -46,16 +47,25 @@ export function getAverageByMonth(items = [], options = {}) {
* const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' }; * const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' };
* extractValues(data, ['fooBar'], 'foo', 'baz') => { bazBar: 'quis' } * extractValues(data, ['fooBar'], 'foo', 'baz') => { bazBar: 'quis' }
* @param {Object} data set to extract values from * @param {Object} data set to extract values from
* @param {Array} dataKeys keys describing where to look for values in the data set * @param {Array} nameKeys keys describing where to look for values in the data set
* @param {String} replaceKey name key to be replaced in the data set * @param {String} dataPrefix prefix to `nameKey` on where to get the data
* @param {String} nestedKey key nested in the data set to be extracted, * @param {String} nestedKey key nested in the data set to be extracted,
* this is also used to rename the newly created data set * this is also used to rename the newly created data set
* @param {Object} options
* @param {String} options.renameKey? optional rename key, if not provided nestedKey will be used
* @return {Object} the newly created data set with the extracted values * @return {Object} the newly created data set with the extracted values
*/ */
export function extractValues(data, dataKeys = [], replaceKey, nestedKey) { export function extractValues(data, nameKeys = [], dataPrefix, nestedKey, options = {}) {
return mapKeys(pick(mapValues(data, nestedKey), dataKeys), (value, key) => const { renameKey = nestedKey } = options;
key.replace(replaceKey, nestedKey),
); return nameKeys.reduce((memo, name) => {
const titelCaseName = convertToTitleCase(name);
const dataKey = `${dataPrefix}${titelCaseName}`;
const newKey = `${renameKey}${titelCaseName}`;
const itemData = get(data[dataKey], nestedKey);
return { ...memo, [newKey]: itemData };
}, {});
} }
/** /**
......
...@@ -14140,15 +14140,27 @@ msgstr "" ...@@ -14140,15 +14140,27 @@ msgstr ""
msgid "InstanceAnalytics|Canceled" msgid "InstanceAnalytics|Canceled"
msgstr "" msgstr ""
msgid "InstanceAnalytics|Could not load the issues and merge requests chart. Please refresh the page to try again."
msgstr ""
msgid "InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again." msgid "InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again."
msgstr "" msgstr ""
msgid "InstanceAnalytics|Failed" msgid "InstanceAnalytics|Failed"
msgstr "" msgstr ""
msgid "InstanceAnalytics|Issues"
msgstr ""
msgid "InstanceAnalytics|Issues & Merge Requests"
msgstr ""
msgid "InstanceAnalytics|Items" msgid "InstanceAnalytics|Items"
msgstr "" msgstr ""
msgid "InstanceAnalytics|Merge Requests"
msgstr ""
msgid "InstanceAnalytics|Month" msgid "InstanceAnalytics|Month"
msgstr "" msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PipelinesChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = ` exports[`InstanceStatisticsCountChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = `
Array [ Array [
Object { Object {
"data": Array [ "data": Array [
...@@ -90,7 +90,7 @@ Array [ ...@@ -90,7 +90,7 @@ Array [
] ]
`; `;
exports[`PipelinesChart with data passes the data to the line chart 1`] = ` exports[`InstanceStatisticsCountChart with data passes the data to the line chart 1`] = `
Array [ Array [
Object { Object {
"data": Array [ "data": Array [
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue'; import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue';
import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue'; import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue';
import PipelinesChart from '~/analytics/instance_statistics/components/pipelines_chart.vue'; import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue';
import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue'; import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
describe('InstanceStatisticsApp', () => { describe('InstanceStatisticsApp', () => {
...@@ -24,8 +24,11 @@ describe('InstanceStatisticsApp', () => { ...@@ -24,8 +24,11 @@ describe('InstanceStatisticsApp', () => {
expect(wrapper.find(InstanceCounts).exists()).toBe(true); expect(wrapper.find(InstanceCounts).exists()).toBe(true);
}); });
it('displays the pipelines chart component', () => { it('displays the instance statistics count chart component', () => {
expect(wrapper.find(PipelinesChart).exists()).toBe(true); const allCharts = wrapper.findAll(InstanceStatisticsCountChart);
expect(allCharts).toHaveLength(2);
expect(allCharts.at(0).exists()).toBe(true);
expect(allCharts.at(1).exists()).toBe(true);
}); });
it('displays the users chart component', () => { it('displays the users chart component', () => {
......
...@@ -3,7 +3,7 @@ import { GlLineChart } from '@gitlab/ui/dist/charts'; ...@@ -3,7 +3,7 @@ import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'jest/helpers/mock_apollo_helper'; import createMockApollo from 'jest/helpers/mock_apollo_helper';
import PipelinesChart from '~/analytics/instance_statistics/components/pipelines_chart.vue'; import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue';
import pipelinesStatsQuery from '~/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql'; import pipelinesStatsQuery from '~/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { mockCountsData1, mockCountsData2 } from '../mock_data'; import { mockCountsData1, mockCountsData2 } from '../mock_data';
...@@ -12,7 +12,17 @@ import { getApolloResponse } from '../apollo_mock_data'; ...@@ -12,7 +12,17 @@ import { getApolloResponse } from '../apollo_mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
describe('PipelinesChart', () => { const PIPELINES_KEY_TO_NAME_MAP = {
total: 'Total',
succeeded: 'Succeeded',
failed: 'Failed',
canceled: 'Canceled',
skipped: 'Skipped',
};
const loadChartErrorMessage = 'My load error message';
const noDataMessage = 'My no data message';
describe('InstanceStatisticsCountChart', () => {
let wrapper; let wrapper;
let queryHandler; let queryHandler;
...@@ -21,9 +31,19 @@ describe('PipelinesChart', () => { ...@@ -21,9 +31,19 @@ describe('PipelinesChart', () => {
}; };
const createComponent = apolloProvider => { const createComponent = apolloProvider => {
return shallowMount(PipelinesChart, { return shallowMount(InstanceStatisticsCountChart, {
localVue, localVue,
apolloProvider, apolloProvider,
propsData: {
keyToNameMap: PIPELINES_KEY_TO_NAME_MAP,
prefix: 'pipelines',
loadChartErrorMessage,
noDataMessage,
chartTitle: 'Foo',
yAxisTitle: 'Bar',
xAxisTitle: 'Baz',
query: pipelinesStatsQuery,
},
}); });
}; };
...@@ -69,7 +89,7 @@ describe('PipelinesChart', () => { ...@@ -69,7 +89,7 @@ describe('PipelinesChart', () => {
}); });
it('renders an no data message', () => { it('renders an no data message', () => {
expect(findAlert().text()).toBe('There is no data available.'); expect(findAlert().text()).toBe(noDataMessage);
}); });
it('hides the skeleton loader', () => { it('hides the skeleton loader', () => {
...@@ -180,9 +200,7 @@ describe('PipelinesChart', () => { ...@@ -180,9 +200,7 @@ describe('PipelinesChart', () => {
}); });
it('show an error message', () => { it('show an error message', () => {
expect(findAlert().text()).toBe( expect(findAlert().text()).toBe(loadChartErrorMessage);
'Could not load the pipelines chart. Please refresh the page to try again.',
);
}); });
}); });
}); });
......
...@@ -47,7 +47,21 @@ describe('getAverageByMonth', () => { ...@@ -47,7 +47,21 @@ describe('getAverageByMonth', () => {
describe('extractValues', () => { describe('extractValues', () => {
it('extracts only requested values', () => { it('extracts only requested values', () => {
const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' }; const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' };
expect(extractValues(data, ['fooBar'], 'foo', 'baz')).toEqual({ bazBar: 'quis' }); expect(extractValues(data, ['bar'], 'foo', 'baz')).toEqual({ bazBar: 'quis' });
});
it('it renames with the `renameKey` if provided', () => {
const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' };
expect(extractValues(data, ['bar'], 'foo', 'baz', { renameKey: 'renamed' })).toEqual({
renamedBar: 'quis',
});
});
it('is able to get nested data', () => {
const data = { fooBar: { even: [{ further: 'nested' }] }, ignored: 'ignored' };
expect(extractValues(data, ['bar'], 'foo', 'even[0].further')).toEqual({
'even[0].furtherBar': 'nested',
});
}); });
it('is able to extract multiple values', () => { it('is able to extract multiple values', () => {
...@@ -56,7 +70,7 @@ describe('extractValues', () => { ...@@ -56,7 +70,7 @@ describe('extractValues', () => {
fooBaz: { baz: 'quis' }, fooBaz: { baz: 'quis' },
fooQuis: { baz: 'quis' }, fooQuis: { baz: 'quis' },
}; };
expect(extractValues(data, ['fooBar', 'fooBaz', 'fooQuis'], 'foo', 'baz')).toEqual({ expect(extractValues(data, ['bar', 'baz', 'quis'], 'foo', 'baz')).toEqual({
bazBar: 'quis', bazBar: 'quis',
bazBaz: 'quis', bazBaz: 'quis',
bazQuis: 'quis', bazQuis: 'quis',
...@@ -65,7 +79,7 @@ describe('extractValues', () => { ...@@ -65,7 +79,7 @@ describe('extractValues', () => {
it('returns empty data set when keys are not found', () => { it('returns empty data set when keys are not found', () => {
const data = { foo: { baz: 'quis' }, ignored: 'ignored' }; const data = { foo: { baz: 'quis' }, ignored: 'ignored' };
expect(extractValues(data, ['fooBar'], 'foo', 'baz')).toEqual({}); expect(extractValues(data, ['bar'], 'foo', 'baz')).toEqual({});
}); });
it('returns empty data when params are missing', () => { it('returns empty data when params are missing', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment