@lightdash/common 0.1478.8 → 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 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';
@@ -382,4 +382,44 @@ export type ApiCalculateTotalResponse = {
382
382
  status: 'ok';
383
383
  results: Record<string, number>;
384
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
+ };
385
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';
@@ -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;
@@ -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;
@@ -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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/common",
3
- "version": "0.1478.8",
3
+ "version": "0.1479.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [