@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 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';
@@ -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.7",
3
+ "version": "0.1479.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [