Commit a56335b8 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '267538-mlunoe-add-general-instance-statistics-chart-component' into 'master'

Instance Analytics: Add general count chart component

See merge request gitlab-org/gitlab!45561
parents 5b57bea1 b8f91478
<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 };
}, {});
} }
/** /**
......
...@@ -14149,15 +14149,27 @@ msgstr "" ...@@ -14149,15 +14149,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