Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-06 18:09:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-06 18:09:14 +0300
commita268b09416c8dc3da3af38933028fa26375b88e0 (patch)
tree8f10484408a40e386b79f8bb3c2f4095dded85f7 /app/assets/javascripts/analytics
parent4ff56b118438f4fa6191b691fd968c75d8e94d5a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/analytics')
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/app.vue50
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/charts_config.js87
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue219
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql13
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql34
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql76
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/utils.js55
7 files changed, 224 insertions, 310 deletions
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/app.vue b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
index abe9b45ed79..8df4d2e2524 100644
--- a/app/assets/javascripts/analytics/instance_statistics/components/app.vue
+++ b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
@@ -1,32 +1,11 @@
<script>
-import { s__ } from '~/locale';
import InstanceCounts from './instance_counts.vue';
import InstanceStatisticsCountChart from './instance_statistics_count_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 ProjectsAndGroupsChart from './projects_and_groups_chart.vue';
+import ChartsConfig from './charts_config';
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 {
name: 'InstanceStatisticsApp',
components: {
@@ -38,28 +17,7 @@ export default {
TOTAL_DAYS_TO_SHOW,
START_DATE,
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,
- },
- ],
+ configs: ChartsConfig,
};
</script>
@@ -79,9 +37,7 @@ export default {
<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"
+ :queries="chartOptions.queries"
:x-axis-title="chartOptions.xAxisTitle"
:y-axis-title="chartOptions.yAxisTitle"
:load-chart-error-message="chartOptions.loadChartError"
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/charts_config.js b/app/assets/javascripts/analytics/instance_statistics/components/charts_config.js
new file mode 100644
index 00000000000..6fba3c56cfe
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/components/charts_config.js
@@ -0,0 +1,87 @@
+import { s__, __, sprintf } from '~/locale';
+import query from '../graphql/queries/instance_count.query.graphql';
+
+const noDataMessage = s__('InstanceStatistics|No data available.');
+
+export default [
+ {
+ loadChartError: sprintf(
+ s__(
+ 'InstanceStatistics|Could not load the pipelines chart. Please refresh the page to try again.',
+ ),
+ ),
+ noDataMessage,
+ chartTitle: s__('InstanceStatistics|Pipelines'),
+ yAxisTitle: s__('InstanceStatistics|Items'),
+ xAxisTitle: s__('InstanceStatistics|Month'),
+ queries: [
+ {
+ query,
+ title: s__('InstanceStatistics|Pipelines total'),
+ identifier: 'PIPELINES',
+ loadError: sprintf(
+ s__('InstanceStatistics|There was an error fetching the total pipelines'),
+ ),
+ },
+ {
+ query,
+ title: s__('InstanceStatistics|Pipelines succeeded'),
+ identifier: 'PIPELINES_SUCCEEDED',
+ loadError: sprintf(
+ s__('InstanceStatistics|There was an error fetching the successful pipelines'),
+ ),
+ },
+ {
+ query,
+ title: s__('InstanceStatistics|Pipelines failed'),
+ identifier: 'PIPELINES_FAILED',
+ loadError: sprintf(
+ s__('InstanceStatistics|There was an error fetching the failed pipelines'),
+ ),
+ },
+ {
+ query,
+ title: s__('InstanceStatistics|Pipelines canceled'),
+ identifier: 'PIPELINES_CANCELED',
+ loadError: sprintf(
+ s__('InstanceStatistics|There was an error fetching the cancelled pipelines'),
+ ),
+ },
+ {
+ query,
+ title: s__('InstanceStatistics|Pipelines skipped'),
+ identifier: 'PIPELINES_SKIPPED',
+ loadError: sprintf(
+ s__('InstanceStatistics|There was an error fetching the skipped pipelines'),
+ ),
+ },
+ ],
+ },
+ {
+ loadChartError: sprintf(
+ s__(
+ 'InstanceStatistics|Could not load the issues and merge requests chart. Please refresh the page to try again.',
+ ),
+ ),
+ noDataMessage,
+ chartTitle: s__('InstanceStatistics|Issues & Merge Requests'),
+ yAxisTitle: s__('InstanceStatistics|Items'),
+ xAxisTitle: s__('InstanceStatistics|Month'),
+ queries: [
+ {
+ query,
+ title: __('Issues'),
+ identifier: 'ISSUES',
+ loadError: sprintf(s__('InstanceStatistics|There was an error fetching the issues')),
+ },
+ {
+ query,
+ title: __('Merge requests'),
+ identifier: 'MERGE_REQUESTS',
+ loadError: sprintf(
+ s__('InstanceStatistics|There was an error fetching the merge requests'),
+ ),
+ },
+ ],
+ },
+];
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue
index 740af834618..a9bd1bb2f41 100644
--- a/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue
+++ b/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue
@@ -1,17 +1,19 @@
<script>
import { GlLineChart } from '@gitlab/ui/dist/charts';
import { GlAlert } from '@gitlab/ui';
-import { mapValues, some, sum } from 'lodash';
+import { some, every } from 'lodash';
+import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import {
differenceInMonths,
formatDateAsMonth,
getDayDifference,
} from '~/lib/utils/datetime_utility';
-import { convertToTitleCase } from '~/lib/utils/text_utility';
-import { getAverageByMonth, sortByDate, extractValues } from '../utils';
+import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils';
import { TODAY, START_DATE } from '../constants';
+const QUERY_DATA_KEY = 'instanceStatisticsMeasurements';
+
export default {
name: 'InstanceStatisticsCountChart',
components: {
@@ -21,18 +23,7 @@ export default {
},
startDate: START_DATE,
endDate: TODAY,
- dataKey: 'nodes',
- pageInfoKey: 'pageInfo',
- firstKey: 'first',
props: {
- prefix: {
- type: String,
- required: true,
- },
- keyToNameMap: {
- type: Object,
- required: true,
- },
chartTitle: {
type: String,
required: true,
@@ -53,112 +44,46 @@ export default {
type: String,
required: true,
},
- query: {
- type: Object,
+ queries: {
+ type: Array,
required: true,
},
},
data() {
return {
- loading: true,
- loadingError: null,
+ errors: { ...generateDataKeys(this.queries, '') },
+ ...generateDataKeys(this.queries, []),
};
},
- apollo: {
- pipelineStats: {
- query() {
- return this.query;
- },
- variables() {
- return this.nameKeys.reduce((memo, key) => {
- const firstKey = `${this.$options.firstKey}${convertToTitleCase(key)}`;
- return { ...memo, [firstKey]: this.totalDaysToShow };
- }, {});
- },
- update(data) {
- const allData = extractValues(data, this.nameKeys, this.prefix, this.$options.dataKey);
- const allPageInfo = extractValues(
- data,
- this.nameKeys,
- this.prefix,
- this.$options.pageInfoKey,
- );
-
- return {
- ...mapValues(allData, sortByDate),
- ...allPageInfo,
- };
- },
- result() {
- if (this.hasNextPage) {
- this.fetchNextPage();
- }
- },
- error() {
- this.handleError();
- },
- },
- },
computed: {
- nameKeys() {
- return Object.keys(this.keyToNameMap);
+ errorMessages() {
+ return Object.values(this.errors);
},
isLoading() {
- return this.$apollo.queries.pipelineStats.loading;
+ return some(this.$apollo.queries, query => query?.loading);
},
- totalDaysToShow() {
- return getDayDifference(this.$options.startDate, this.$options.endDate);
+ allQueriesFailed() {
+ return every(this.errorMessages, message => message.length);
},
- firstVariables() {
- const firstDataPoints = extractValues(
- this.pipelineStats,
- this.nameKeys,
- this.$options.dataKey,
- '[0].recordedAt',
- { renameKey: this.$options.firstKey },
- );
-
- return Object.keys(firstDataPoints).reduce((memo, name) => {
- const recordedAt = firstDataPoints[name];
- if (!recordedAt) {
- return { ...memo, [name]: 0 };
- }
-
- const numberOfDays = Math.max(
- 0,
- getDayDifference(this.$options.startDate, new Date(recordedAt)),
- );
-
- return { ...memo, [name]: numberOfDays };
- }, {});
- },
- cursorVariables() {
- return extractValues(
- this.pipelineStats,
- this.nameKeys,
- this.$options.pageInfoKey,
- 'endCursor',
- );
- },
- hasNextPage() {
- return (
- sum(Object.values(this.firstVariables)) > 0 &&
- some(this.pipelineStats, ({ hasNextPage }) => hasNextPage)
- );
+ hasLoadingErrors() {
+ return some(this.errorMessages, message => message.length);
+ },
+ errorMessage() {
+ // show the generic loading message if all requests fail
+ return this.allQueriesFailed ? this.loadChartErrorMessage : this.errorMessages.join('\n\n');
},
hasEmptyDataSet() {
return this.chartData.every(({ data }) => data.length === 0);
},
+ totalDaysToShow() {
+ return getDayDifference(this.$options.startDate, this.$options.endDate);
+ },
chartData() {
const options = { shouldRound: true };
-
- return this.nameKeys.map(key => {
- const dataKey = `${this.$options.dataKey}${convertToTitleCase(key)}`;
- return {
- name: this.keyToNameMap[key],
- data: getAverageByMonth(this.pipelineStats?.[dataKey], options),
- };
- });
+ return this.queries.map(({ identifier, title }) => ({
+ name: title,
+ data: getAverageByMonth(this[identifier]?.nodes, options),
+ }));
},
range() {
return {
@@ -188,26 +113,73 @@ export default {
};
},
},
+ created() {
+ this.queries.forEach(({ query, identifier, loadError }) => {
+ this.$apollo.addSmartQuery(identifier, {
+ query,
+ variables() {
+ return {
+ identifier,
+ first: this.totalDaysToShow,
+ after: null,
+ };
+ },
+ update(data) {
+ const { nodes = [], pageInfo } = data[QUERY_DATA_KEY] || {};
+ return {
+ nodes,
+ pageInfo,
+ };
+ },
+ result() {
+ const { pageInfo, nodes } = this[identifier];
+ if (pageInfo?.hasNextPage && this.calculateDaysToFetch(getEarliestDate(nodes)) > 0) {
+ this.fetchNextPage({
+ query: this.$apollo.queries[identifier],
+ errorMessage: loadError,
+ pageInfo,
+ identifier,
+ });
+ }
+ },
+ error(error) {
+ this.handleError({
+ message: loadError,
+ identifier,
+ error,
+ });
+ },
+ });
+ });
+ },
methods: {
- handleError() {
+ calculateDaysToFetch(firstDataPointDate = null) {
+ return firstDataPointDate
+ ? Math.max(0, getDayDifference(this.$options.startDate, new Date(firstDataPointDate)))
+ : 0;
+ },
+ handleError({ identifier, error, message }) {
this.loadingError = true;
+ this.errors = { ...this.errors, [identifier]: message };
+ Sentry.captureException(error);
},
- fetchNextPage() {
- this.$apollo.queries.pipelineStats
+ fetchNextPage({ query, pageInfo, identifier, errorMessage }) {
+ query
.fetchMore({
variables: {
- ...this.firstVariables,
- ...this.cursorVariables,
+ identifier,
+ first: this.calculateDaysToFetch(getEarliestDate(this[identifier].nodes)),
+ after: pageInfo.endCursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
- return Object.keys(fetchMoreResult).reduce((memo, key) => {
- const { nodes, ...rest } = fetchMoreResult[key];
- const previousNodes = previousResult[key].nodes;
- return { ...memo, [key]: { ...rest, nodes: [...previousNodes, ...nodes] } };
- }, {});
+ const { nodes, ...rest } = fetchMoreResult[QUERY_DATA_KEY];
+ const { nodes: previousNodes } = previousResult[QUERY_DATA_KEY];
+ return {
+ [QUERY_DATA_KEY]: { ...rest, nodes: [...previousNodes, ...nodes] },
+ };
},
})
- .catch(this.handleError);
+ .catch(error => this.handleError({ identifier, error, message: errorMessage }));
},
},
};
@@ -215,13 +187,20 @@ export default {
<template>
<div>
<h3>{{ chartTitle }}</h3>
- <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
- {{ loadChartErrorMessage }}
- </gl-alert>
- <chart-skeleton-loader v-else-if="isLoading" />
- <gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
- {{ noDataMessage }}
+ <gl-alert v-if="hasLoadingErrors" variant="danger" :dismissible="false" class="gl-mt-3">
+ {{ errorMessage }}
</gl-alert>
- <gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" />
+ <div v-if="!allQueriesFailed">
+ <chart-skeleton-loader v-if="isLoading" />
+ <gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
+ {{ noDataMessage }}
+ </gl-alert>
+ <gl-line-chart
+ v-else
+ :option="chartOptions"
+ :include-legend-avg-max="true"
+ :data="chartData"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql
new file mode 100644
index 00000000000..dd22a16cd51
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql
@@ -0,0 +1,13 @@
+#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "../fragments/count.fragment.graphql"
+
+query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) {
+ instanceStatisticsMeasurements(identifier: $identifier, first: $first, after: $after) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+}
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql
deleted file mode 100644
index 96f21403b34..00000000000
--- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql
+++ /dev/null
@@ -1,34 +0,0 @@
-#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
- }
- }
-}
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql
deleted file mode 100644
index 3bf40403f91..00000000000
--- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql
+++ /dev/null
@@ -1,76 +0,0 @@
-#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
-#import "./count.fragment.graphql"
-
-query pipelineStats(
- $firstTotal: Int
- $firstSucceeded: Int
- $firstFailed: Int
- $firstCanceled: Int
- $firstSkipped: Int
- $endCursorTotal: String
- $endCursorSucceeded: String
- $endCursorFailed: String
- $endCursorCanceled: String
- $endCursorSkipped: String
-) {
- pipelinesTotal: instanceStatisticsMeasurements(
- identifier: PIPELINES
- first: $firstTotal
- after: $endCursorTotal
- ) {
- nodes {
- ...Count
- }
- pageInfo {
- ...PageInfo
- }
- }
- pipelinesSucceeded: instanceStatisticsMeasurements(
- identifier: PIPELINES_SUCCEEDED
- first: $firstSucceeded
- after: $endCursorSucceeded
- ) {
- nodes {
- ...Count
- }
- pageInfo {
- ...PageInfo
- }
- }
- pipelinesFailed: instanceStatisticsMeasurements(
- identifier: PIPELINES_FAILED
- first: $firstFailed
- after: $endCursorFailed
- ) {
- nodes {
- ...Count
- }
- pageInfo {
- ...PageInfo
- }
- }
- pipelinesCanceled: instanceStatisticsMeasurements(
- identifier: PIPELINES_CANCELED
- first: $firstCanceled
- after: $endCursorCanceled
- ) {
- nodes {
- ...Count
- }
- pageInfo {
- ...PageInfo
- }
- }
- pipelinesSkipped: instanceStatisticsMeasurements(
- identifier: PIPELINES_SKIPPED
- first: $firstSkipped
- after: $endCursorSkipped
- ) {
- nodes {
- ...Count
- }
- pageInfo {
- ...PageInfo
- }
- }
-}
diff --git a/app/assets/javascripts/analytics/instance_statistics/utils.js b/app/assets/javascripts/analytics/instance_statistics/utils.js
index eef66165945..e1fa5d155a2 100644
--- a/app/assets/javascripts/analytics/instance_statistics/utils.js
+++ b/app/assets/javascripts/analytics/instance_statistics/utils.js
@@ -1,7 +1,6 @@
import { masks } from 'dateformat';
-import { get, sortBy } from 'lodash';
+import { get } from 'lodash';
import { formatDate } from '~/lib/utils/datetime_utility';
-import { convertToTitleCase } from '~/lib/utils/text_utility';
const { isoDate } = masks;
@@ -42,38 +41,28 @@ export function getAverageByMonth(items = [], options = {}) {
}
/**
- * Extracts values given a data set and a set of keys
- * @example
- * const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' };
- * extractValues(data, ['fooBar'], 'foo', 'baz') => { bazBar: 'quis' }
- * @param {Object} data set to extract values from
- * @param {Array} nameKeys keys describing where to look for values 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,
- * 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
+ * Takes an array of instance counts and returns the last item in the list
+ * @param {Array} arr array of instance counts in the form { count: Number, recordedAt: date String }
+ * @return {String} the 'recordedAt' value of the earliest item
*/
-export function extractValues(data, nameKeys = [], dataPrefix, nestedKey, options = {}) {
- const { renameKey = nestedKey } = options;
-
- 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 };
- }, {});
-}
+export const getEarliestDate = (arr = []) => {
+ const len = arr.length;
+ return get(arr, `[${len - 1}].recordedAt`, null);
+};
/**
- * Creates a new array of items sorted by the date string of each item
- * @param {Array} items [description]
- * @param {String} items[0] date string
- * @return {Array} the new sorted array.
+ * Takes an array of queries and produces an object with the query identifier as key
+ * and a supplied defaultValue as its value
+ * @param {Array} queries array of chart query configs,
+ * see ./analytics/instance_statistics/components/charts_config.js
+ * @param {any} defaultValue value to set each identifier to
+ * @return {Object} key value pair of the form { queryIdentifier: defaultValue }
*/
-export function sortByDate(items = []) {
- return sortBy(items, ({ recordedAt }) => new Date(recordedAt).getTime());
-}
+export const generateDataKeys = (queries, defaultValue) =>
+ queries.reduce(
+ (acc, { identifier }) => ({
+ ...acc,
+ [identifier]: defaultValue,
+ }),
+ {},
+ );