// (C) 2007-2019 GoodData Corporation import { buildProject } from "../../../../schema/builder"; import { schema } from "../../../../schema/fixtures/dummySchema"; import { AFMBuilder } from "../../../../utils/AFMBuilder/AFMBuilder"; import { DataResultBuilder, RandomMetricValueGenerator, IRandomDataResult } from "../dataResult"; import { createComparator, FunSort, SortDefinitionError } from "../sorting"; describe("applySorting", () => { const randomGenerator: RandomMetricValueGenerator = params => { return `${(params.row + 1) * (params.column + 1)}`; }; const MEASURE_A = { measureHeaderItem: { name: "Simple metric", order: 0, }, }; const JOHN = { attributeHeaderItem: { name: "John Doe", uri: "/gdc/md/mockproject/obj/attr.employee/elements?id=1", }, }; const JANE = { attributeHeaderItem: { name: "Jane Doe", uri: "/gdc/md/mockproject/obj/attr.employee/elements?id=2", }, }; const JIM = { attributeHeaderItem: { name: "Jim Doe", uri: "/gdc/md/mockproject/obj/attr.employee/elements?id=3", }, }; const COMPANY_A = { attributeHeaderItem: { name: "Company A", uri: "/gdc/md/mockproject/obj/1234/elements?id=1", }, }; const COMPANY_B = { attributeHeaderItem: { name: "Company B", uri: "/gdc/md/mockproject/obj/1234/elements?id=2", }, }; it("should sort result by attribute in ASC order", () => { const execution = AFMBuilder.buildFromSchema(schema) .addMeasure() .addAttribute() .addResultSpec({ dimensions: [{ itemIdentifiers: ["a0"] }, { itemIdentifiers: ["measureGroup"] }], sorts: [{ attributeSortItem: { direction: "asc", attributeIdentifier: "a0" } }], }) .build(); const dataResult = DataResultBuilder.forProject(buildProject(schema)) .withGenerator(randomGenerator) .forAfmAndResultSpec(execution) .build() as IRandomDataResult; expect(dataResult.headerItems[0]).toEqual([[JANE, JIM, JOHN]]); }); it("should sort result by attribute in DESC order", () => { const execution = AFMBuilder.buildFromSchema(schema) .addMeasure() .addAttribute() .addResultSpec({ dimensions: [{ itemIdentifiers: ["a0"] }, { itemIdentifiers: ["measureGroup"] }], sorts: [{ attributeSortItem: { direction: "desc", attributeIdentifier: "a0" } }], }) .build(); const dataResult = DataResultBuilder.forProject(buildProject(schema)) .withGenerator(randomGenerator) .forAfmAndResultSpec(execution) .build() as IRandomDataResult; expect(dataResult.headerItems[0]).toEqual([[JOHN, JIM, JANE]]); }); it("should sort result by measure", () => { const execution = AFMBuilder.buildFromSchema(schema) .addMeasure() .addAttribute() .addResultSpec({ dimensions: [{ itemIdentifiers: ["a0"] }, { itemIdentifiers: ["measureGroup"] }], sorts: [ { measureSortItem: { direction: "desc", locators: [{ measureLocatorItem: { measureIdentifier: "m0" } }], }, }, ], }) .build(); const dataResult = DataResultBuilder.forProject(buildProject(schema)) .withGenerator(randomGenerator) .forAfmAndResultSpec(execution) .build() as IRandomDataResult; expect(dataResult.data).toEqual([["3"], ["2"], ["1"]]); }); it("should sort result by pivoted measure ASC", () => { const execution = AFMBuilder.buildFromSchema(schema) .addMeasure() .addAttribute() .addAttribute() .addResultSpec({ dimensions: [{ itemIdentifiers: ["a0"] }, { itemIdentifiers: ["a1", "measureGroup"] }], sorts: [ { measureSortItem: { direction: "asc", locators: [ { attributeLocatorItem: { attributeIdentifier: "a1", element: "/gdc/md/mockproject/obj/1234/elements?id=1", }, }, { measureLocatorItem: { measureIdentifier: "m0" } }, ], }, }, ], }) .build(); const dataResult = DataResultBuilder.forProject(buildProject(schema)) .withGenerator(randomGenerator) .forAfmAndResultSpec(execution) .build() as IRandomDataResult; expect(dataResult.data).toEqual([["1", "2"], ["2", "4"], ["3", "6"]]); }); it("should order pivoted attribute headers", () => { const execution = AFMBuilder.buildFromSchema(schema) .addMeasure() .addAttribute() .addAttribute() .addResultSpec({ dimensions: [{ itemIdentifiers: ["a0"] }, { itemIdentifiers: ["a1", "measureGroup"] }], sorts: [{ attributeSortItem: { attributeIdentifier: "a1", direction: "desc" } }], }) .build(); const dataResult = DataResultBuilder.forProject(buildProject(schema)) .withGenerator(randomGenerator) .forAfmAndResultSpec(execution) .build() as IRandomDataResult; expect(dataResult.headerItems[1]).toEqual([[COMPANY_B, COMPANY_A], [MEASURE_A, MEASURE_A]]); expect(dataResult.data).toEqual([["2", "1"], ["4", "2"], ["6", "3"]]); }); describe("sort comparator", () => { const entry500 = { values: ["500.0"], originalIndex: 0 }; const entry9 = { values: ["9"], originalIndex: 1 }; const sameValueEntryIdx0 = { values: ["my header"], originalIndex: 0 }; const sameValueEntryIdx1 = { values: ["my header"], originalIndex: 1 }; it("should sort data in numeric sort order", () => { const descComparator = createComparator([{ type: "data", direction: "desc", sortIndex: 0 }]); const ascComparator = createComparator([{ type: "data", direction: "asc", sortIndex: 0 }]); // sorting desc = larger to smaller; 500 > 9 => comparator says left comes before right expect(descComparator(entry500, entry9)).toEqual(-1); // sorting asc = smaller to larger; 500 > 9 => comparator says left comes after right expect(ascComparator(entry500, entry9)).toEqual(1); }); it("should sort headers in lexicographical order", () => { const descComparator = createComparator([{ type: "headers", direction: "desc", sortIndex: 0 }]); const ascComparator = createComparator([{ type: "headers", direction: "asc", sortIndex: 0 }]); // sorting desc = larger to smaller; "500" < "9" => comparator says left comes after right expect(descComparator(entry500, entry9)).toEqual(1); // sorting asc = smaller to larger; "500" > "9" => comparator says left comes before right expect(ascComparator(entry500, entry9)).toEqual(-1); }); it("should retain natural order based on originalIndex", () => { const descComparator = createComparator([{ type: "headers", direction: "desc", sortIndex: 0 }]); const ascComparator = createComparator([{ type: "headers", direction: "asc", sortIndex: 0 }]); // sorting desc; same value; the originalIndex is compared, same way, all the time expect(descComparator(sameValueEntryIdx0, sameValueEntryIdx1)).toEqual(-1); // sorting asc; same value; the originalIndex is compared, same way, all the time expect(ascComparator(sameValueEntryIdx0, sameValueEntryIdx1)).toEqual(-1); expect(descComparator(sameValueEntryIdx1, sameValueEntryIdx0)).toEqual(1); // sorting asc; same value; the originalIndex is compared, same way, all the time expect(ascComparator(sameValueEntryIdx1, sameValueEntryIdx0)).toEqual(1); }); }); describe("input checking", () => { const execution = AFMBuilder.buildFromSchema(schema) .addMeasure() .addAttribute() .addAttribute() .addResultSpec({ dimensions: [{ itemIdentifiers: ["a0"] }, { itemIdentifiers: ["a1", "measureGroup"] }], }) .build(); const dataResult = DataResultBuilder.forProject(buildProject(schema)) .withGenerator(randomGenerator) .forAfmAndResultSpec(execution) .build() as IRandomDataResult; it("should throw error when sorting on missing attribute", () => { try { new FunSort(dataResult).run([ { attributeSortItem: { attributeIdentifier: "a2", direction: "desc" } }, ]); fail("Result builder did not throw error for bad sorting definition"); } catch (e) { if (e instanceof SortDefinitionError) { // expected state } else { throw e; } } }); it("should throw error when missing measure identifier", () => { try { new FunSort(dataResult).run([ { measureSortItem: { direction: "desc", locators: [{ measureLocatorItem: { measureIdentifier: "mBANG" } }], }, }, ]); fail("Result builder did not throw error for bad sorting definition"); } catch (e) { if (e instanceof SortDefinitionError) { // expected state } else { throw e; } } }); it("should throw error when ambiguous measure locator", () => { try { new FunSort(dataResult).run([ { measureSortItem: { direction: "desc", locators: [{ measureLocatorItem: { measureIdentifier: "m0" } }], }, }, ]); fail("Result builder did not throw error for bad sorting definition"); } catch (e) { if (e instanceof SortDefinitionError) { // expected state } else { throw e; } } }); it("should throw error when measure locator matches nothing", () => { try { new FunSort(dataResult).run([ { measureSortItem: { direction: "desc", locators: [ { measureLocatorItem: { measureIdentifier: "m0" } }, { attributeLocatorItem: { attributeIdentifier: "a1", element: "BANG" } }, ], }, }, ]); fail("Result builder did not throw error for bad sorting definition"); } catch (e) { if (e instanceof SortDefinitionError) { // expected state } else { throw e; } } }); it("should throw error when measure locator contains missing attribute", () => { try { new FunSort(dataResult).run([ { measureSortItem: { direction: "desc", locators: [ { measureLocatorItem: { measureIdentifier: "m0" } }, { attributeLocatorItem: { attributeIdentifier: "BANG", element: "irrelevant", }, }, ], }, }, ]); fail("Result builder did not throw error for bad sorting definition"); } catch (e) { if (e instanceof SortDefinitionError) { // expected state } else { throw e; } } }); }); });