Skip to content

Commit

Permalink
Merge pull request #281 from gooddata/tsa-bb-1073-multiple-date-datas…
Browse files Browse the repository at this point in the history
…ets-measure-filters

RELATED: BB-1073 Support execution of AFM with multiple date datasets…

Reviewed-by: Martin
             https://github.com/martinmosko
  • Loading branch information
gdgate authored Jul 18, 2018
2 parents 7826f19 + b240f6d commit 1e6230b
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 113 deletions.
65 changes: 45 additions & 20 deletions src/DataLayer/helpers/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import cloneDeep = require('lodash/cloneDeep');
import isEmpty = require('lodash/isEmpty');
import { AFM } from '@gooddata/typings';
import { unwrapSimpleMeasure } from '../utils/AfmUtils';
import {
unwrapSimpleMeasure,
getId,
getDateFilterDateDataSet
} from '../utils/AfmUtils';

function isFilterItem(filter: AFM.CompatibilityFilter): filter is AFM.FilterItem {
return !(filter as AFM.IExpressionFilter).value;
Expand Down Expand Up @@ -55,15 +59,18 @@ function getGlobalDateFilters(afm: AFM.IAfm): AFM.DateFilterItem[] {

/**
* AFM Date Filter logic:
* Prerequisities: At least one metric (M1) with date filter & global date filter (D1)
*
* Steps:
* 1. Remove date filter (D1) from global
* 2. Add D1 to each metric without date filter
* 3. M1 is untouched
* When:
* There is at least one metric with date filter & global date filter present
* Then:
* 1. To each metric add all global date filters if there isn't other date filter with the same date data set
* 2. Remove global date filter
* Otherwise
* Return provided AFM without any change
*/

export function handleMeasureDateFilter(afm: AFM.IAfm): AFM.IAfm {
const globalDateFilters = getGlobalDateFilters(afm);
const globalDateFilters: AFM.DateFilterItem[] = getGlobalDateFilters(afm);
if (!globalDateFilters.length) {
return afm;
}
Expand All @@ -83,20 +90,38 @@ export function handleMeasureDateFilter(afm: AFM.IAfm): AFM.IAfm {
return {
...afm,
filters: (afm.filters || []).filter(f => isFilterItem(f) && !isDateFilter(f)),
measures: (afm.measures || []).map((item: AFM.IMeasure): AFM.IMeasure => {
const simpleMeasure = unwrapSimpleMeasure(item);
if (!simpleMeasure || measureHasDateFilter(simpleMeasure)) {
return item;
}
return {
...item,
definition: {
measure: {
...simpleMeasure,
filters: [...(simpleMeasure.filters || []), ...globalDateFilters]
measures: (afm.measures || []).map((measure: AFM.IMeasure): AFM.IMeasure => {
const simpleMeasure = unwrapSimpleMeasure(measure);
if (simpleMeasure) {
const simpleMeasureFilters = simpleMeasure.filters || [];
return {
...measure,
definition: {
measure: {
...simpleMeasure,
filters: joinGlobalAndMeasureFilters(simpleMeasureFilters, globalDateFilters)
}
}
}
};
};
}

return measure;
})
};
}

function joinGlobalAndMeasureFilters(
measureFilters: AFM.FilterItem[], globalDateFilters: AFM.DateFilterItem[]
): AFM.FilterItem[] {
const measureDateFilters: AFM.DateFilterItem[] = measureFilters.filter(isDateFilter);
const globalDateFiltersToAdd = globalDateFilters.filter((globalDateFilter: AFM.DateFilterItem) => {
const dateDataSetPresent = !measureDateFilters.some((measureDateFilter: AFM.DateFilterItem) => {
const globalFilterDataSet = getDateFilterDateDataSet(globalDateFilter);
const measureFilterDataSet = getDateFilterDateDataSet(measureDateFilter);
return getId(globalFilterDataSet) === getId(measureFilterDataSet);
});
return dateDataSetPresent;
});

return [...measureFilters, ...globalDateFiltersToAdd];
}
217 changes: 127 additions & 90 deletions src/DataLayer/helpers/tests/filters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,143 +159,178 @@ describe('mergeFilters', () => {
});

describe('handleMeasureDateFilter', () => {
const measureWithDateFilter: AFM.IMeasure = {
localIdentifier: 'm1',
definition: {
measure: {
item: {
identifier: 'm1'
},
filters: [
{
absoluteDateFilter: {
dataSet: {
identifier: 'd2'
},
from: '1',
to: '2'
}
}
]
}
const dataSet1DateFilterA: AFM.DateFilterItem = {
relativeDateFilter: {
dataSet: {
uri: 'uri/of/dataSet1'
},
granularity: 'day',
from: 1,
to: 2
}
};

const globalDateFilterA: AFM.DateFilterItem = {
const dataSet1DateFilterB: AFM.DateFilterItem = {
absoluteDateFilter: {
dataSet: {
identifier: 'd1'
uri: 'uri/of/dataSet1'
},
from: '1',
to: '2'
from: '2000-03-03',
to: '2000-04-04'
}
};

const globalDateFilterB: AFM.DateFilterItem = {
const dataSet2DateFilterA: AFM.DateFilterItem = {
absoluteDateFilter: {
dataSet: {
identifier: 'd2'
identifier: 'dateSet2'
},
from: '2000-05-05',
to: '2000-06-06'
}
};

const dataSet2DateFilterB: AFM.DateFilterItem = {
relativeDateFilter: {
dataSet: {
identifier: 'dateSet2'
},
granularity: 'day',
from: 7,
to: 8
}
};

const attrFilter1: AFM.IPositiveAttributeFilter = {
positiveAttributeFilter: {
displayForm: {
identifier: 'user filter 1'
},
in: ['4', '5', '6']
}
};

const attrFilter2: AFM.INegativeAttributeFilter = {
negativeAttributeFilter: {
displayForm: {
identifier: 'user filter 2'
},
from: '3',
to: '4'
notIn: ['4', '5', '6']
}
};

it('should move global filters to measures without date filter, if some metric has date filter', () => {
function buildMeasureWithFilters(identifier: AFM.Identifier, filters: AFM.FilterItem[]): AFM.IMeasure {
return {
localIdentifier: identifier,
definition: {
measure: {
item: {
identifier
},
filters
}
}
};
}

it('should move single global date filter to measure date filters if dataSet of the filter is not equal', () => {
const afm: AFM.IAfm = {
measures: [
measureWithDateFilter,
{
localIdentifier: 'm2',
definition: {
measure: {
item: {
identifier: 'm2'
}
}
}
},
{
localIdentifier: 'm3',
definition: {
measure: {
item: {
identifier: 'm3'
}
}
}
}
buildMeasureWithFilters('m1', []),
buildMeasureWithFilters('m2', [dataSet1DateFilterA]),
buildMeasureWithFilters('m3', [dataSet2DateFilterA]),
buildMeasureWithFilters('m4', [dataSet1DateFilterA, dataSet2DateFilterA])
],
filters: [
globalDateFilterA,
globalDateFilterB
dataSet1DateFilterB
]
};

const expectedAfm: AFM.IAfm = {
measures: [
measureWithDateFilter,
{
localIdentifier: 'm2',
definition: {
measure: {
item: {
identifier: 'm2'
},
filters: [globalDateFilterA, globalDateFilterB]
}
}
},
{
localIdentifier: 'm3',
definition: {
measure: {
item: {
identifier: 'm3'
},
filters: [globalDateFilterA, globalDateFilterB]
}
}
}
buildMeasureWithFilters('m1', [dataSet1DateFilterB]),
buildMeasureWithFilters('m2', [dataSet1DateFilterA]),
buildMeasureWithFilters('m3', [dataSet2DateFilterA, dataSet1DateFilterB]),
buildMeasureWithFilters('m4', [dataSet1DateFilterA, dataSet2DateFilterA])
],
filters: []
};
expect(handleMeasureDateFilter(afm)).toEqual(expectedAfm);
});

it('should move multiple global date filters to measure date filters if dataSet of the filter is not equal', () => {
const afm: AFM.IAfm = {
measures: [
buildMeasureWithFilters('m1', []),
buildMeasureWithFilters('m2', [dataSet1DateFilterA]),
buildMeasureWithFilters('m3', [dataSet2DateFilterA]),
buildMeasureWithFilters('m4', [dataSet1DateFilterA, dataSet2DateFilterA])
],
filters: [
dataSet1DateFilterB,
dataSet2DateFilterB
]
};

const expectedAfm: AFM.IAfm = {
measures: [
buildMeasureWithFilters('m1', [dataSet1DateFilterB, dataSet2DateFilterB]),
buildMeasureWithFilters('m2', [dataSet1DateFilterA, dataSet2DateFilterB]),
buildMeasureWithFilters('m3', [dataSet2DateFilterA, dataSet1DateFilterB]),
buildMeasureWithFilters('m4', [dataSet1DateFilterA, dataSet2DateFilterA])
],
filters: []
};
expect(handleMeasureDateFilter(afm)).toEqual(expectedAfm);
});

it('should move the global date filters but keep the attribute filters', () => {
const afm: AFM.IAfm = {
measures: [
buildMeasureWithFilters('m5', [dataSet1DateFilterA, attrFilter2])
],
filters: [
dataSet2DateFilterB,
attrFilter1
]
};

const expectedAfm: AFM.IAfm = {
measures: [
buildMeasureWithFilters('m5', [dataSet1DateFilterA, attrFilter2, dataSet2DateFilterB])
],
filters: [
attrFilter1
]
};
expect(handleMeasureDateFilter(afm)).toEqual(expectedAfm);
});

it('should not modify AFM if no metric has date filter', () => {
const afm: AFM.IAfm = {
measures: [
{
localIdentifier: 'm2',
definition: {
measure: {
item: {
identifier: 'm2'
}
}
}
}
buildMeasureWithFilters('m1', [])
],
filters: [
globalDateFilterA
dataSet1DateFilterA
]
};
expect(handleMeasureDateFilter(afm)).toEqual(afm);
});

it('should remove global filter if there is one measure with date filter', () => {
it('should remove global date filter if there is one measure with date filter', () => {
const measure: AFM.IMeasure = buildMeasureWithFilters('m1', [dataSet1DateFilterB]);
const afm: AFM.IAfm = {
measures: [
measureWithDateFilter
measure
],
filters: [
globalDateFilterA
dataSet1DateFilterA
]
};
const expectedAfm: AFM.IAfm = {
measures: [
measureWithDateFilter
measure
],
filters: []
};
Expand All @@ -304,7 +339,9 @@ describe('handleMeasureDateFilter', () => {

it('should handle AFM without measures', () => {
const afmWithFilterOnly: AFM.IAfm = {
filters: [globalDateFilterA]
filters: [
dataSet1DateFilterA
]
};

expect(handleMeasureDateFilter(afmWithFilterOnly)).toEqual(afmWithFilterOnly);
Expand Down
11 changes: 9 additions & 2 deletions src/DataLayer/utils/AfmUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,21 @@ export function getId(obj: AFM.ObjQualifier): string | null {
return null;
}

function getDateFilterDateDataSet(filter: AFM.DateFilterItem): AFM.ObjQualifier | null {
/**
* Returns date filter date dataset
*
* @method getDateFilterDateDataSet
* @param {AFM.DateFilterItem} filter
* @returns {AFM.ObjQualifier | null }
*/
export function getDateFilterDateDataSet(filter: AFM.DateFilterItem): AFM.ObjQualifier {
if (isDateFilterRelative(filter)) {
return filter.relativeDateFilter.dataSet;
}
if (isDateFilterAbsolute(filter)) {
return filter.absoluteDateFilter.dataSet;
}
return null;
throw new Error('Unsupported type of date filter');
}

/**
Expand Down
Loading

0 comments on commit 1e6230b

Please sign in to comment.