@lightdash/common 0.1478.7 → 0.1479.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
});
|