@lightdash/common 0.1478.7 → 0.1479.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/types/featureFlags.d.ts +5 -1
- package/dist/types/featureFlags.js +4 -0
- package/dist/types/savedCharts.d.ts +42 -1
- package/dist/types/scheduler.d.ts +7 -1
- package/dist/types/scheduler.js +2 -1
- package/dist/utils/charts.d.ts +9 -0
- package/dist/utils/charts.js +43 -0
- package/dist/utils/fields.d.ts +15 -2
- package/dist/utils/fields.js +123 -1
- package/dist/utils/fields.mock.d.ts +8 -1
- package/dist/utils/fields.mock.js +30 -1
- package/dist/utils/fields.test.js +131 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
@@ -135,6 +135,7 @@ export * from './utils/additionalMetrics';
|
|
135
135
|
export * from './utils/api';
|
136
136
|
export { default as assertUnreachable } from './utils/assertUnreachable';
|
137
137
|
export * from './utils/catalogMetricsTree';
|
138
|
+
export * from './utils/charts';
|
138
139
|
export * from './utils/conditionalFormatting';
|
139
140
|
export * from './utils/convertToDbt';
|
140
141
|
export * from './utils/dashboard';
|
@@ -564,3 +565,4 @@ export declare function formatRows(rows: {
|
|
564
565
|
export declare const removeEmptyProperties: (object: Record<string, AnyType>) => Record<string, any>;
|
565
566
|
export declare const deepEqual: (object1: Record<string, AnyType>, object2: Record<string, AnyType>) => boolean;
|
566
567
|
export declare const getProjectDirectory: (dbtConnection?: DbtProjectConfig) => string | undefined;
|
568
|
+
export declare function isNotNull<T>(arg: T): arg is Exclude<T, null>;
|
package/dist/index.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.getProjectDirectory = exports.deepEqual = exports.removeEmptyProperties = exports.formatRows = exports.itemsInMetricQuery = exports.getTableCalculationsFromItemsMap = exports.getMetricsFromItemsMap = exports.getFilterableDimensionsFromItemsMap = exports.getDimensionsFromItemsMap = exports.getItemMap = exports.getFieldMap = exports.getAxisName = exports.getDateGroupLabel = exports.getResultValueArray = exports.DbtProjectTypeLabels = exports.sensitiveDbtCredentialsFieldNames = exports.DBFieldTypes = exports.LightdashInstallType = exports.isLightdashMode = exports.LightdashMode = exports.isApiError = exports.hasInviteCode = exports.TableSelectionType = exports.hasSpecialCharacters = exports.snakeCaseName = exports.findFieldByIdInExplore = exports.getVisibleFields = exports.SEED_GROUP = exports.SEED_SPACE = exports.SEED_PROJECT = exports.SEED_EMBED_SECRET = exports.SEED_ORG_2_ADMIN_PASSWORD = exports.SEED_ORG_2_ADMIN_EMAIL = exports.SEED_ORG_2_ADMIN = exports.SEED_ORG_2 = exports.SEED_ORG_1_ADMIN_PASSWORD = exports.SEED_ORG_1_ADMIN_EMAIL = exports.SEED_ORG_1_ADMIN = exports.SEED_ORG_1 = exports.hexToRGB = exports.replaceStringInArray = exports.toggleArrayValue = exports.hasIntersection = exports.validatePassword = exports.getPasswordSchema = exports.getEmailSchema = exports.validateEmail = exports.assertUnreachable = exports.lightdashProjectConfigSchema = exports.lightdashDbtYamlSchema = void 0;
|
4
|
+
exports.isNotNull = void 0;
|
4
5
|
const tslib_1 = require("tslib");
|
5
6
|
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
6
7
|
const utc_1 = tslib_1.__importDefault(require("dayjs/plugin/utc"));
|
@@ -103,6 +104,7 @@ tslib_1.__exportStar(require("./utils/api"), exports);
|
|
103
104
|
var assertUnreachable_1 = require("./utils/assertUnreachable");
|
104
105
|
Object.defineProperty(exports, "assertUnreachable", { enumerable: true, get: function () { return tslib_1.__importDefault(assertUnreachable_1).default; } });
|
105
106
|
tslib_1.__exportStar(require("./utils/catalogMetricsTree"), exports);
|
107
|
+
tslib_1.__exportStar(require("./utils/charts"), exports);
|
106
108
|
tslib_1.__exportStar(require("./utils/conditionalFormatting"), exports);
|
107
109
|
tslib_1.__exportStar(require("./utils/convertToDbt"), exports);
|
108
110
|
tslib_1.__exportStar(require("./utils/dashboard"), exports);
|
@@ -497,3 +499,7 @@ const getProjectDirectory = (dbtConnection) => {
|
|
497
499
|
}
|
498
500
|
};
|
499
501
|
exports.getProjectDirectory = getProjectDirectory;
|
502
|
+
function isNotNull(arg) {
|
503
|
+
return arg !== null;
|
504
|
+
}
|
505
|
+
exports.isNotNull = isNotNull;
|
@@ -23,7 +23,11 @@ export declare enum FeatureFlags {
|
|
23
23
|
/**
|
24
24
|
* Enable dashboard comments
|
25
25
|
*/
|
26
|
-
DashboardComments = "dashboard-comments-enabled"
|
26
|
+
DashboardComments = "dashboard-comments-enabled",
|
27
|
+
/**
|
28
|
+
* Enable scheduler task that replaces custom metrics after project compile
|
29
|
+
*/
|
30
|
+
ReplaceCustomMetricsOnCompile = "replace-custom-metrics-on-compile"
|
27
31
|
}
|
28
32
|
export type FeatureFlag = {
|
29
33
|
id: string;
|
@@ -33,4 +33,8 @@ var FeatureFlags;
|
|
33
33
|
* Enable dashboard comments
|
34
34
|
*/
|
35
35
|
FeatureFlags["DashboardComments"] = "dashboard-comments-enabled";
|
36
|
+
/**
|
37
|
+
* Enable scheduler task that replaces custom metrics after project compile
|
38
|
+
*/
|
39
|
+
FeatureFlags["ReplaceCustomMetricsOnCompile"] = "replace-custom-metrics-on-compile";
|
36
40
|
})(FeatureFlags = exports.FeatureFlags || (exports.FeatureFlags = {}));
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { type ViewStatistics } from './analytics';
|
2
2
|
import { type ConditionalFormattingConfig } from './conditionalFormatting';
|
3
3
|
import { type ChartSourceType } from './content';
|
4
|
-
import { type CompactOrAlias } from './field';
|
4
|
+
import { type CompactOrAlias, type FieldId } from './field';
|
5
5
|
import { type MetricQuery, type MetricQueryRequest } from './metricQuery';
|
6
6
|
import { type SpaceShare } from './space';
|
7
7
|
import { type LightdashUser, type UpdatedByUser } from './user';
|
@@ -197,6 +197,7 @@ export type Series = {
|
|
197
197
|
showSymbol?: boolean;
|
198
198
|
smooth?: boolean;
|
199
199
|
markLine?: MarkLine;
|
200
|
+
isFilteredOut?: boolean;
|
200
201
|
};
|
201
202
|
export type EchartsLegend = {
|
202
203
|
show?: boolean;
|
@@ -381,4 +382,44 @@ export type ApiCalculateTotalResponse = {
|
|
381
382
|
status: 'ok';
|
382
383
|
results: Record<string, number>;
|
383
384
|
};
|
385
|
+
export type ReplaceableFieldMatchMap = {
|
386
|
+
[fieldId: string]: {
|
387
|
+
fieldId: string;
|
388
|
+
label: string;
|
389
|
+
match: {
|
390
|
+
fieldId: FieldId;
|
391
|
+
fieldLabel: string;
|
392
|
+
} | null;
|
393
|
+
suggestedMatches: Array<{
|
394
|
+
fieldId: FieldId;
|
395
|
+
fieldLabel: string;
|
396
|
+
}>;
|
397
|
+
};
|
398
|
+
};
|
399
|
+
export type ReplaceableCustomFields = {
|
400
|
+
[chartUuid: string]: {
|
401
|
+
uuid: string;
|
402
|
+
label: string;
|
403
|
+
customMetrics: ReplaceableFieldMatchMap;
|
404
|
+
};
|
405
|
+
};
|
406
|
+
export type ReplaceCustomFields = {
|
407
|
+
[chartUuid: string]: {
|
408
|
+
customMetrics: {
|
409
|
+
[customMetricId: string]: {
|
410
|
+
replaceWithFieldId: string;
|
411
|
+
};
|
412
|
+
};
|
413
|
+
};
|
414
|
+
};
|
415
|
+
export type SkippedReplaceCustomFields = {
|
416
|
+
[chartUuid: string]: {
|
417
|
+
customMetrics: {
|
418
|
+
[customMetricId: string]: {
|
419
|
+
replaceWithFieldId: string;
|
420
|
+
reason: string;
|
421
|
+
};
|
422
|
+
};
|
423
|
+
};
|
424
|
+
};
|
384
425
|
export {};
|
@@ -35,8 +35,9 @@ export declare enum JobPriority {
|
|
35
35
|
MEDIUM = 1,
|
36
36
|
LOW = 2
|
37
37
|
}
|
38
|
+
export declare const ReplaceCustomFieldsTask: "replaceCustomFields";
|
38
39
|
export type SchedulerLog = {
|
39
|
-
task: 'handleScheduledDelivery' | 'sendEmailNotification' | 'sendSlackNotification' | 'uploadGsheets' | 'downloadCsv' | 'uploadGsheetFromQuery' | 'createProjectWithCompile' | 'compileProject' | 'testAndCompileProject' | 'validateProject' | 'sqlRunner' | 'sqlRunnerPivotQuery' | 'semanticLayer' | 'indexCatalog';
|
40
|
+
task: 'handleScheduledDelivery' | 'sendEmailNotification' | 'sendSlackNotification' | 'uploadGsheets' | 'downloadCsv' | 'uploadGsheetFromQuery' | 'createProjectWithCompile' | 'compileProject' | 'testAndCompileProject' | 'validateProject' | 'sqlRunner' | 'sqlRunnerPivotQuery' | 'semanticLayer' | typeof ReplaceCustomFieldsTask | 'indexCatalog';
|
40
41
|
schedulerUuid?: string;
|
41
42
|
jobId: string;
|
42
43
|
jobGroup?: string;
|
@@ -267,6 +268,11 @@ export type CompileProjectPayload = {
|
|
267
268
|
jobUuid: string;
|
268
269
|
isPreview: boolean;
|
269
270
|
};
|
271
|
+
export type ReplaceCustomFieldsPayload = {
|
272
|
+
createdByUserUuid: string;
|
273
|
+
organizationUuid: string;
|
274
|
+
projectUuid: string;
|
275
|
+
};
|
270
276
|
export type ValidateProjectPayload = {
|
271
277
|
projectUuid: string;
|
272
278
|
context: 'lightdash_app' | 'dbt_refresh' | 'test_and_compile' | 'cli';
|
package/dist/types/scheduler.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.LightdashPage = exports.getSchedulerUuid = exports.hasSchedulerUuid = exports.isCreateScheduler = exports.isSchedulerGsheetsOptions = exports.isSchedulerImageOptions = exports.isSchedulerCsvOptions = exports.isCreateSchedulerSlackTarget = exports.isEmailTarget = exports.isSlackTarget = exports.isChartScheduler = exports.isUpdateSchedulerEmailTarget = exports.isUpdateSchedulerSlackTarget = exports.getSchedulerTargetUuid = exports.isDashboardScheduler = exports.operatorActionValue = exports.NotificationFrequency = exports.ThresholdOperator = exports.JobPriority = exports.SchedulerFormat = exports.SchedulerJobStatus = void 0;
|
3
|
+
exports.LightdashPage = exports.getSchedulerUuid = exports.hasSchedulerUuid = exports.isCreateScheduler = exports.isSchedulerGsheetsOptions = exports.isSchedulerImageOptions = exports.isSchedulerCsvOptions = exports.isCreateSchedulerSlackTarget = exports.isEmailTarget = exports.isSlackTarget = exports.isChartScheduler = exports.isUpdateSchedulerEmailTarget = exports.isUpdateSchedulerSlackTarget = exports.getSchedulerTargetUuid = exports.isDashboardScheduler = exports.operatorActionValue = exports.NotificationFrequency = exports.ThresholdOperator = exports.ReplaceCustomFieldsTask = exports.JobPriority = exports.SchedulerFormat = exports.SchedulerJobStatus = void 0;
|
4
4
|
const tslib_1 = require("tslib");
|
5
5
|
const assertUnreachable_1 = tslib_1.__importDefault(require("../utils/assertUnreachable"));
|
6
6
|
var SchedulerJobStatus;
|
@@ -22,6 +22,7 @@ var JobPriority;
|
|
22
22
|
JobPriority[JobPriority["MEDIUM"] = 1] = "MEDIUM";
|
23
23
|
JobPriority[JobPriority["LOW"] = 2] = "LOW";
|
24
24
|
})(JobPriority = exports.JobPriority || (exports.JobPriority = {}));
|
25
|
+
exports.ReplaceCustomFieldsTask = 'replaceCustomFields';
|
25
26
|
var ThresholdOperator;
|
26
27
|
(function (ThresholdOperator) {
|
27
28
|
ThresholdOperator["GREATER_THAN"] = "greaterThan";
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { type CreateSavedChartVersion, type ReplaceCustomFields, type SkippedReplaceCustomFields } from '../types/savedCharts';
|
2
|
+
export declare function maybeReplaceFieldsInChartVersion({ fieldsToReplace, chartVersion, }: {
|
3
|
+
fieldsToReplace: ReplaceCustomFields[string];
|
4
|
+
chartVersion: CreateSavedChartVersion;
|
5
|
+
}): {
|
6
|
+
hasChanges: boolean;
|
7
|
+
chartVersion: CreateSavedChartVersion;
|
8
|
+
skippedFields: SkippedReplaceCustomFields[string];
|
9
|
+
};
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.maybeReplaceFieldsInChartVersion = void 0;
|
4
|
+
const item_1 = require("./item");
|
5
|
+
function maybeReplaceFieldsInChartVersion({ fieldsToReplace, chartVersion, }) {
|
6
|
+
let hasChanges = false;
|
7
|
+
const skippedFields = {
|
8
|
+
customMetrics: {},
|
9
|
+
};
|
10
|
+
const newChartData = { ...chartVersion };
|
11
|
+
// Replace custom metrics
|
12
|
+
const customMetricsToReplace = Object.entries(fieldsToReplace.customMetrics);
|
13
|
+
customMetricsToReplace.forEach(([customMetricToReplace, replaceWith]) => {
|
14
|
+
if (customMetricToReplace === replaceWith.replaceWithFieldId) {
|
15
|
+
const foundCustomMetric = !!chartVersion.metricQuery.additionalMetrics?.find((additionalMetric) => (0, item_1.getItemId)(additionalMetric) === customMetricToReplace);
|
16
|
+
if (foundCustomMetric) {
|
17
|
+
hasChanges = true;
|
18
|
+
// remove custom metric
|
19
|
+
newChartData.metricQuery.additionalMetrics =
|
20
|
+
newChartData.metricQuery.additionalMetrics?.filter((additionalMetric) => (0, item_1.getItemId)(additionalMetric) !==
|
21
|
+
customMetricToReplace);
|
22
|
+
}
|
23
|
+
else {
|
24
|
+
skippedFields.customMetrics[customMetricToReplace] = {
|
25
|
+
reason: `Custom metric ${customMetricToReplace} not found in chart version.`,
|
26
|
+
replaceWithFieldId: replaceWith.replaceWithFieldId,
|
27
|
+
};
|
28
|
+
}
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
skippedFields.customMetrics[customMetricToReplace] = {
|
32
|
+
reason: 'Replacing custom metrics with a metric with a different ID is not supported yet.',
|
33
|
+
replaceWithFieldId: replaceWith.replaceWithFieldId,
|
34
|
+
};
|
35
|
+
}
|
36
|
+
});
|
37
|
+
return {
|
38
|
+
hasChanges,
|
39
|
+
skippedFields,
|
40
|
+
chartVersion: newChartData || chartVersion,
|
41
|
+
};
|
42
|
+
}
|
43
|
+
exports.maybeReplaceFieldsInChartVersion = maybeReplaceFieldsInChartVersion;
|
package/dist/utils/fields.d.ts
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
import { type Explore } from '../types/explore';
|
2
|
-
import { type CompiledDimension, type CompiledField, type CompiledMetric, type ItemsMap } from '../types/field';
|
3
|
-
import { type MetricQuery } from '../types/metricQuery';
|
2
|
+
import { type CompiledDimension, type CompiledField, type CompiledMetric, type ItemsMap, type Metric } from '../types/field';
|
3
|
+
import { type AdditionalMetric, type MetricQuery } from '../types/metricQuery';
|
4
|
+
import { type ReplaceableCustomFields, type ReplaceableFieldMatchMap, type ReplaceCustomFields } from '../types/savedCharts';
|
4
5
|
export declare const getDimensions: (explore: Explore) => CompiledDimension[];
|
5
6
|
export declare const getMetrics: (explore: Explore) => CompiledMetric[];
|
6
7
|
export declare const getFields: (explore: Explore) => CompiledField[];
|
7
8
|
export declare const getFieldsFromMetricQuery: (metricQuery: MetricQuery, explore: Explore) => ItemsMap;
|
9
|
+
export declare function compareMetricAndCustomMetric({ metric, customMetric, }: {
|
10
|
+
metric: Metric;
|
11
|
+
customMetric: AdditionalMetric;
|
12
|
+
}): {
|
13
|
+
isExactMatch: boolean;
|
14
|
+
isSuggestedMatch: boolean;
|
15
|
+
};
|
16
|
+
export declare function findReplaceableCustomMetrics({ customMetrics, metrics, }: {
|
17
|
+
customMetrics: AdditionalMetric[];
|
18
|
+
metrics: Metric[];
|
19
|
+
}): ReplaceableFieldMatchMap;
|
20
|
+
export declare function convertReplaceableFieldMatchMapToReplaceCustomFields(replaceableCustomFields: ReplaceableCustomFields): ReplaceCustomFields;
|
package/dist/utils/fields.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.getFieldsFromMetricQuery = exports.getFields = exports.getMetrics = exports.getDimensions = void 0;
|
3
|
+
exports.convertReplaceableFieldMatchMapToReplaceCustomFields = exports.findReplaceableCustomMetrics = exports.compareMetricAndCustomMetric = exports.getFieldsFromMetricQuery = exports.getFields = exports.getMetrics = exports.getDimensions = void 0;
|
4
4
|
const additionalMetrics_1 = require("./additionalMetrics");
|
5
5
|
const item_1 = require("./item");
|
6
6
|
// Helper function to get a list of all dimensions in an explore
|
@@ -54,3 +54,125 @@ const getFieldsFromMetricQuery = (metricQuery, explore) => {
|
|
54
54
|
};
|
55
55
|
};
|
56
56
|
exports.getFieldsFromMetricQuery = getFieldsFromMetricQuery;
|
57
|
+
function compareMetricAndCustomMetric({ metric, customMetric, }) {
|
58
|
+
const conditions = {
|
59
|
+
fieldIdMatch: {
|
60
|
+
isMatch: (0, item_1.getItemId)(metric) === (0, item_1.getItemId)(customMetric),
|
61
|
+
requiredForSuggestion: false,
|
62
|
+
},
|
63
|
+
labelMatch: {
|
64
|
+
isMatch: metric.label === customMetric.label,
|
65
|
+
requiredForSuggestion: false,
|
66
|
+
},
|
67
|
+
sqlMatch: {
|
68
|
+
isMatch: metric.sql === customMetric.sql,
|
69
|
+
requiredForSuggestion: true,
|
70
|
+
},
|
71
|
+
baseDimensionMatch: {
|
72
|
+
isMatch: metric.dimensionReference ===
|
73
|
+
`${customMetric.table}_${customMetric.baseDimensionName}`,
|
74
|
+
requiredForSuggestion: true,
|
75
|
+
},
|
76
|
+
metricTypeMatch: {
|
77
|
+
isMatch: metric.type === customMetric.type,
|
78
|
+
requiredForSuggestion: true,
|
79
|
+
},
|
80
|
+
formatMatch: {
|
81
|
+
// NOTE: We don't support format matching yet. Only match if both are undefined/null
|
82
|
+
isMatch: !metric.formatOptions &&
|
83
|
+
!metric.format &&
|
84
|
+
(!customMetric.formatOptions ||
|
85
|
+
customMetric.formatOptions.type === 'default') &&
|
86
|
+
!customMetric.format,
|
87
|
+
requiredForSuggestion: true,
|
88
|
+
},
|
89
|
+
percentileMatch: {
|
90
|
+
// NOTE: We don't support percentile matching yet. Only match if both are undefined/null
|
91
|
+
isMatch: !metric.percentile && !customMetric.percentile,
|
92
|
+
requiredForSuggestion: true,
|
93
|
+
},
|
94
|
+
filtersMatch: {
|
95
|
+
isMatch: (customMetric.filters || []).length ===
|
96
|
+
(metric.filters || []).length &&
|
97
|
+
(metric.filters || []).every((filter) => customMetric.filters?.find((customFilter) => {
|
98
|
+
const fieldRefMatch = customFilter.target.fieldRef ===
|
99
|
+
filter.target.fieldRef;
|
100
|
+
const operatorMatch = customFilter.operator === filter.operator;
|
101
|
+
const valuesMatch = customFilter.values === filter.values ||
|
102
|
+
customFilter.values?.every((value) => filter.values?.includes(value));
|
103
|
+
return fieldRefMatch && operatorMatch && valuesMatch;
|
104
|
+
})),
|
105
|
+
requiredForSuggestion: true,
|
106
|
+
},
|
107
|
+
};
|
108
|
+
const isExactMatch = Object.values(conditions).every((condition) => condition.isMatch);
|
109
|
+
const isSuggestedMatch = Object.values(conditions)
|
110
|
+
.filter((condition) => condition.requiredForSuggestion)
|
111
|
+
.every((condition) => condition.isMatch);
|
112
|
+
return {
|
113
|
+
isExactMatch,
|
114
|
+
isSuggestedMatch,
|
115
|
+
};
|
116
|
+
}
|
117
|
+
exports.compareMetricAndCustomMetric = compareMetricAndCustomMetric;
|
118
|
+
function findReplaceableCustomMetrics({ customMetrics, metrics, }) {
|
119
|
+
return customMetrics.reduce((acc, customMetric) => {
|
120
|
+
let match = null;
|
121
|
+
const suggestedMatches = [];
|
122
|
+
metrics.forEach((metric) => {
|
123
|
+
const fieldId = (0, item_1.getItemId)(metric);
|
124
|
+
const fieldLabel = metric.label;
|
125
|
+
const { isExactMatch, isSuggestedMatch } = compareMetricAndCustomMetric({
|
126
|
+
metric,
|
127
|
+
customMetric,
|
128
|
+
});
|
129
|
+
if (isExactMatch) {
|
130
|
+
match = {
|
131
|
+
fieldId,
|
132
|
+
fieldLabel,
|
133
|
+
};
|
134
|
+
}
|
135
|
+
else if (isSuggestedMatch) {
|
136
|
+
suggestedMatches.push({
|
137
|
+
fieldId,
|
138
|
+
fieldLabel,
|
139
|
+
});
|
140
|
+
}
|
141
|
+
});
|
142
|
+
if (match !== null || suggestedMatches.length > 0) {
|
143
|
+
return {
|
144
|
+
...acc,
|
145
|
+
[(0, item_1.getItemId)(customMetric)]: {
|
146
|
+
fieldId: customMetric.name,
|
147
|
+
label: customMetric.label || customMetric.name,
|
148
|
+
match,
|
149
|
+
suggestedMatches,
|
150
|
+
},
|
151
|
+
};
|
152
|
+
}
|
153
|
+
return acc;
|
154
|
+
}, {});
|
155
|
+
}
|
156
|
+
exports.findReplaceableCustomMetrics = findReplaceableCustomMetrics;
|
157
|
+
function convertReplaceableFieldMatchMapToReplaceCustomFields(replaceableCustomFields) {
|
158
|
+
return Object.entries(replaceableCustomFields).reduce((acc, [chartUuid, customFields]) => {
|
159
|
+
const customMetrics = Object.entries(customFields.customMetrics).reduce((acc2, [customFieldId, customField]) => {
|
160
|
+
if (customField.match) {
|
161
|
+
return {
|
162
|
+
...acc2,
|
163
|
+
[customFieldId]: {
|
164
|
+
replaceWithFieldId: customField.match.fieldId,
|
165
|
+
},
|
166
|
+
};
|
167
|
+
}
|
168
|
+
return acc2;
|
169
|
+
}, {});
|
170
|
+
if (Object.keys(customMetrics).length > 0) {
|
171
|
+
acc[chartUuid] = {
|
172
|
+
customMetrics,
|
173
|
+
};
|
174
|
+
}
|
175
|
+
return acc;
|
176
|
+
}, {});
|
177
|
+
}
|
178
|
+
exports.convertReplaceableFieldMatchMapToReplaceCustomFields = convertReplaceableFieldMatchMapToReplaceCustomFields;
|
@@ -1,5 +1,12 @@
|
|
1
|
-
import { type Explore, type MetricQuery } from '../index';
|
1
|
+
import { FilterOperator, type AdditionalMetric, type Explore, type Metric, type MetricFilterRule, type MetricQuery } from '../index';
|
2
2
|
export declare const metricQuery: MetricQuery;
|
3
3
|
export declare const explore: Explore;
|
4
4
|
export declare const emptyExplore: Explore;
|
5
5
|
export declare const emptyMetricQuery: MetricQuery;
|
6
|
+
export declare const customMetric: AdditionalMetric;
|
7
|
+
export declare const metric: Metric;
|
8
|
+
export declare const metricFilterRule: (args?: {
|
9
|
+
fieldRef?: string;
|
10
|
+
values?: unknown[];
|
11
|
+
operator?: FilterOperator;
|
12
|
+
}) => MetricFilterRule;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.emptyMetricQuery = exports.emptyExplore = exports.explore = exports.metricQuery = void 0;
|
3
|
+
exports.metricFilterRule = exports.metric = exports.customMetric = exports.emptyMetricQuery = exports.emptyExplore = exports.explore = exports.metricQuery = void 0;
|
4
4
|
const index_1 = require("../index");
|
5
5
|
exports.metricQuery = {
|
6
6
|
exploreName: 'table1',
|
@@ -123,3 +123,32 @@ exports.emptyMetricQuery = {
|
|
123
123
|
limit: 500,
|
124
124
|
tableCalculations: [],
|
125
125
|
};
|
126
|
+
exports.customMetric = {
|
127
|
+
type: index_1.MetricType.AVERAGE,
|
128
|
+
sql: '${TABLE}.dim1',
|
129
|
+
table: 'a',
|
130
|
+
name: 'average_dim1',
|
131
|
+
label: 'Average dim1',
|
132
|
+
baseDimensionName: 'dim1',
|
133
|
+
};
|
134
|
+
exports.metric = {
|
135
|
+
fieldType: index_1.FieldType.METRIC,
|
136
|
+
type: index_1.MetricType.AVERAGE,
|
137
|
+
name: 'average_dim1',
|
138
|
+
label: 'Average dim1',
|
139
|
+
table: 'a',
|
140
|
+
tableLabel: 'a',
|
141
|
+
sql: '${TABLE}.dim1',
|
142
|
+
isAutoGenerated: false,
|
143
|
+
hidden: false,
|
144
|
+
dimensionReference: 'a_dim1',
|
145
|
+
};
|
146
|
+
const metricFilterRule = (args) => ({
|
147
|
+
id: 'uuid',
|
148
|
+
operator: args?.operator || index_1.FilterOperator.GREATER_THAN_OR_EQUAL,
|
149
|
+
target: {
|
150
|
+
fieldRef: args?.fieldRef || 'a_dim1',
|
151
|
+
},
|
152
|
+
values: args?.values || [14],
|
153
|
+
});
|
154
|
+
exports.metricFilterRule = metricFilterRule;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
const field_1 = require("../types/field");
|
4
|
+
const filter_1 = require("../types/filter");
|
4
5
|
const fields_1 = require("./fields");
|
5
6
|
const fields_mock_1 = require("./fields.mock");
|
6
7
|
describe('getFieldsFromMetricQuery', () => {
|
@@ -42,3 +43,133 @@ describe('getFieldsFromMetricQuery', () => {
|
|
42
43
|
]);
|
43
44
|
});
|
44
45
|
});
|
46
|
+
describe('compareMetricAndCustomMetric', () => {
|
47
|
+
test('should return exact match for simple custom metric', async () => {
|
48
|
+
const result = (0, fields_1.compareMetricAndCustomMetric)({
|
49
|
+
customMetric: fields_mock_1.customMetric,
|
50
|
+
metric: fields_mock_1.metric,
|
51
|
+
});
|
52
|
+
expect(result.isExactMatch).toEqual(true);
|
53
|
+
expect(result.isSuggestedMatch).toEqual(true);
|
54
|
+
});
|
55
|
+
const mismatchButSuggestCases = [
|
56
|
+
['name', 'diff_name'],
|
57
|
+
['label', 'diff label'],
|
58
|
+
];
|
59
|
+
test.each(mismatchButSuggestCases)('should not match but suggest when %s has different value', (key, value) => {
|
60
|
+
const result = (0, fields_1.compareMetricAndCustomMetric)({
|
61
|
+
customMetric: {
|
62
|
+
...fields_mock_1.customMetric,
|
63
|
+
[key]: value,
|
64
|
+
},
|
65
|
+
metric: fields_mock_1.metric,
|
66
|
+
});
|
67
|
+
expect(result.isExactMatch).toEqual(false);
|
68
|
+
expect(result.isSuggestedMatch).toEqual(true);
|
69
|
+
});
|
70
|
+
const mismatchCases = [
|
71
|
+
['sql', 'diff_sql'],
|
72
|
+
['baseDimensionName', 'diff_baseDimensionName'],
|
73
|
+
['table', 'diff_table'],
|
74
|
+
['type', 'max'],
|
75
|
+
];
|
76
|
+
test.each(mismatchCases)('should not match or suggest when %s has different value', (key, value) => {
|
77
|
+
const result = (0, fields_1.compareMetricAndCustomMetric)({
|
78
|
+
customMetric: {
|
79
|
+
...fields_mock_1.customMetric,
|
80
|
+
[key]: value,
|
81
|
+
},
|
82
|
+
metric: fields_mock_1.metric,
|
83
|
+
});
|
84
|
+
expect(result.isExactMatch).toEqual(false);
|
85
|
+
expect(result.isSuggestedMatch).toEqual(false);
|
86
|
+
});
|
87
|
+
test('should return exact match with multiple filters', async () => {
|
88
|
+
const filters = [
|
89
|
+
(0, fields_mock_1.metricFilterRule)(),
|
90
|
+
(0, fields_mock_1.metricFilterRule)({
|
91
|
+
fieldRef: 'b_dim2',
|
92
|
+
values: ['2', '4'],
|
93
|
+
operator: filter_1.FilterOperator.IN_BETWEEN,
|
94
|
+
}),
|
95
|
+
];
|
96
|
+
const result = (0, fields_1.compareMetricAndCustomMetric)({
|
97
|
+
customMetric: {
|
98
|
+
...fields_mock_1.customMetric,
|
99
|
+
filters,
|
100
|
+
},
|
101
|
+
metric: {
|
102
|
+
...fields_mock_1.metric,
|
103
|
+
filters,
|
104
|
+
},
|
105
|
+
});
|
106
|
+
expect(result.isExactMatch).toEqual(true);
|
107
|
+
expect(result.isSuggestedMatch).toEqual(true);
|
108
|
+
});
|
109
|
+
test('should not match or suggest when filter has different value', async () => {
|
110
|
+
// Different filters length
|
111
|
+
const result = (0, fields_1.compareMetricAndCustomMetric)({
|
112
|
+
customMetric: {
|
113
|
+
...fields_mock_1.customMetric,
|
114
|
+
filters: [(0, fields_mock_1.metricFilterRule)(), (0, fields_mock_1.metricFilterRule)()],
|
115
|
+
},
|
116
|
+
metric: {
|
117
|
+
...fields_mock_1.metric,
|
118
|
+
filters: [(0, fields_mock_1.metricFilterRule)()],
|
119
|
+
},
|
120
|
+
});
|
121
|
+
expect(result.isExactMatch).toEqual(false);
|
122
|
+
expect(result.isSuggestedMatch).toEqual(false);
|
123
|
+
// Different operator
|
124
|
+
const result2 = (0, fields_1.compareMetricAndCustomMetric)({
|
125
|
+
customMetric: {
|
126
|
+
...fields_mock_1.customMetric,
|
127
|
+
filters: [(0, fields_mock_1.metricFilterRule)()],
|
128
|
+
},
|
129
|
+
metric: {
|
130
|
+
...fields_mock_1.metric,
|
131
|
+
filters: [
|
132
|
+
(0, fields_mock_1.metricFilterRule)({
|
133
|
+
operator: filter_1.FilterOperator.NOT_EQUALS,
|
134
|
+
}),
|
135
|
+
],
|
136
|
+
},
|
137
|
+
});
|
138
|
+
expect(result2.isExactMatch).toEqual(false);
|
139
|
+
expect(result2.isSuggestedMatch).toEqual(false);
|
140
|
+
// Different values
|
141
|
+
const result3 = (0, fields_1.compareMetricAndCustomMetric)({
|
142
|
+
customMetric: {
|
143
|
+
...fields_mock_1.customMetric,
|
144
|
+
filters: [(0, fields_mock_1.metricFilterRule)()],
|
145
|
+
},
|
146
|
+
metric: {
|
147
|
+
...fields_mock_1.metric,
|
148
|
+
filters: [
|
149
|
+
(0, fields_mock_1.metricFilterRule)({
|
150
|
+
values: ['2'],
|
151
|
+
}),
|
152
|
+
],
|
153
|
+
},
|
154
|
+
});
|
155
|
+
expect(result3.isExactMatch).toEqual(false);
|
156
|
+
expect(result3.isSuggestedMatch).toEqual(false);
|
157
|
+
// Different fieldRef
|
158
|
+
const result4 = (0, fields_1.compareMetricAndCustomMetric)({
|
159
|
+
customMetric: {
|
160
|
+
...fields_mock_1.customMetric,
|
161
|
+
filters: [(0, fields_mock_1.metricFilterRule)()],
|
162
|
+
},
|
163
|
+
metric: {
|
164
|
+
...fields_mock_1.metric,
|
165
|
+
filters: [
|
166
|
+
(0, fields_mock_1.metricFilterRule)({
|
167
|
+
fieldRef: 'b_dim2',
|
168
|
+
}),
|
169
|
+
],
|
170
|
+
},
|
171
|
+
});
|
172
|
+
expect(result4.isExactMatch).toEqual(false);
|
173
|
+
expect(result4.isSuggestedMatch).toEqual(false);
|
174
|
+
});
|
175
|
+
});
|