Skip to content

Commit

Permalink
pinboard-query (#1030)
Browse files Browse the repository at this point in the history
* pinboard-query

* fix some tests
  • Loading branch information
adrianmroz-allegro committed Feb 13, 2023
1 parent 20648b3 commit ee531e4
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 119 deletions.
72 changes: 60 additions & 12 deletions src/client/components/pinboard-tile/pinboard-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,32 @@ import { Set } from "immutable";
import { Dataset, Datum } from "plywood";
import React from "react";
import { Clicker } from "../../../common/models/clicker/clicker";
import { DatasetRequest, error, isError, isLoaded, isLoading, loaded, loading } from "../../../common/models/dataset-request/dataset-request";
import {
DatasetRequest,
error,
isError,
isLoaded,
isLoading,
loaded,
loading
} from "../../../common/models/dataset-request/dataset-request";
import { Dimension } from "../../../common/models/dimension/dimension";
import { Essence } from "../../../common/models/essence/essence";
import { BooleanFilterClause, StringFilterAction, StringFilterClause } from "../../../common/models/filter-clause/filter-clause";
import {
BooleanFilterClause,
StringFilterAction,
StringFilterClause
} from "../../../common/models/filter-clause/filter-clause";
import { SortOn } from "../../../common/models/sort-on/sort-on";
import { SortDirection } from "../../../common/models/sort/sort";
import { Split } from "../../../common/models/split/split";
import { Timekeeper } from "../../../common/models/timekeeper/timekeeper";
import { debounceWithPromise, Unary } from "../../../common/utils/functional/functional";
import { MAX_SEARCH_LENGTH } from "../../config/constants";
import { setDragData, setDragGhost } from "../../utils/dom/dom";
import { DragManager } from "../../utils/drag-manager/drag-manager";
import { reportError } from "../../utils/error-reporter/error-reporter";
import { ApiContext, ApiContextValue } from "../../views/cube-view/api-context";
import { Loader } from "../loader/loader";
import { QueryError } from "../query-error/query-error";
import { SearchableTile } from "../searchable-tile/searchable-tile";
Expand All @@ -37,7 +52,6 @@ import { pinboardIcons } from "./pinboard-icons";
import "./pinboard-tile.scss";
import { isClauseEditable } from "./utils/is-clause-editable";
import { isDimensionPinnable } from "./utils/is-dimension-pinnable";
import { makeQuery } from "./utils/make-query";
import { isPinnableClause, PinnableClause } from "./utils/pinnable-clause";
import { equalParams, QueryParams } from "./utils/query-params";
import { EditState, RowMode, RowModeId } from "./utils/row-mode";
Expand All @@ -60,16 +74,19 @@ export interface PinboardTileState {
}

export class PinboardTile extends React.Component<PinboardTileProps, PinboardTileState> {
static contextType = ApiContext;

context: ApiContextValue;

state: PinboardTileState = {
searchText: "",
showSearch: false,
datasetLoad: loading
};

private loadData(params: QueryParams) {
private loadData() {
this.setState({ datasetLoad: loading });
this.fetchData(params)
this.fetchData(this.constructQueryParams())
.then(loadedDataset => {
// TODO: encode it better
// null is here when we get out of order request, so we just ignore it
Expand All @@ -86,8 +103,9 @@ export class PinboardTile extends React.Component<PinboardTileProps, PinboardTil
private lastQueryParams: Partial<QueryParams> = {};

private callExecutor = (params: QueryParams): Promise<DatasetRequest | null> => {
const { essence: { timezone, dataCube } } = params;
return dataCube.executor(makeQuery(params), { timezone })
const { essence, clause, split } = params;
const { pinboardQuery } = this.context;
return pinboardQuery(essence, clause, split)
.then((dataset: Dataset) => {
// signal out of order requests with null
if (!equalParams(params, this.lastQueryParams)) return null;
Expand All @@ -104,8 +122,7 @@ export class PinboardTile extends React.Component<PinboardTileProps, PinboardTil
private debouncedCallExecutor = debounceWithPromise(this.callExecutor, 500);

componentDidMount() {
const { essence, timekeeper, dimension, sortOn } = this.props;
this.loadData({ essence, timekeeper, dimension, sortOn, searchText: "" });
this.loadData();
}

componentWillUnmount() {
Expand All @@ -114,9 +131,7 @@ export class PinboardTile extends React.Component<PinboardTileProps, PinboardTil

componentDidUpdate(previousProps: PinboardTileProps, previousState: PinboardTileState) {
if (shouldFetchData(this.props, previousProps, this.state, previousState)) {
const { essence, timekeeper, dimension, sortOn } = this.props;
const { searchText } = this.state;
this.loadData({ essence, timekeeper, dimension, sortOn, searchText });
this.loadData();
}
}

Expand All @@ -141,6 +156,39 @@ export class PinboardTile extends React.Component<PinboardTileProps, PinboardTil
this.setState({ searchText });
};

constructQueryParams(): QueryParams {
const { sortOn, essence, dimension } = this.props;
const split = new Split({
reference: dimension.name,
sort: sortOn.toSort(SortDirection.descending),
// TODO: magic number
limit: 100
});
return {
clause: this.constructSearchTextClause(),
essence,
split
};
}

constructSearchTextClause(): StringFilterClause | null {
const { dimension } = this.props;
switch (dimension.kind) {
case "boolean":
return null;
case "string":
const { name: reference } = dimension;
const { searchText } = this.state;
return new StringFilterClause({
action: StringFilterAction.CONTAINS,
reference,
values: Set.of(searchText)
});
default:
throw new Error(`Expected String or Boolean dimension kind, got ${dimension.kind}`);
}
}

private getFormatter(): Unary<Datum, string> {
const { sortOn, essence } = this.props;
const series = essence.findConcreteSeries(sortOn.key);
Expand Down
64 changes: 0 additions & 64 deletions src/client/components/pinboard-tile/utils/make-query.ts

This file was deleted.

36 changes: 13 additions & 23 deletions src/client/components/pinboard-tile/utils/query-params.mocha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@
import { expect } from "chai";
import { DimensionFixtures } from "../../../../common/models/dimension/dimension.fixtures";
import { EssenceFixtures } from "../../../../common/models/essence/essence.fixtures";
import { stringContains } from "../../../../common/models/filter-clause/filter-clause.fixtures";
import { SortOnFixtures } from "../../../../common/models/sort-on/sort-on.fixtures";
import { booleanSplitCombine, stringSplitCombine } from "../../../../common/models/split/split.fixtures";
import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper";
import { equalParams, QueryParams } from "./query-params";

const wikiTime = DimensionFixtures.wikiTime();
const wikiChannel = DimensionFixtures.wikiChannel();
const wikiTotals = EssenceFixtures.wikiTotals();

const mockQueryParams = (): QueryParams => ({
essence: wikiTotals,
dimension: wikiTime,
timekeeper: Timekeeper.EMPTY,
searchText: "search",
sortOn: SortOnFixtures.defaultA()
split: stringSplitCombine(wikiChannel.name),
clause: stringContains(wikiChannel.name, "e"),
essence: wikiTotals
});

describe("QueryParams", () => {
Expand All @@ -39,32 +39,22 @@ describe("QueryParams", () => {
expect(equalParams(params, params)).to.be.true;
});

it("should return false if dimension is different", () => {
it("should return false if split is different", () => {
const params = mockQueryParams();
const changedDimension = { ...params, dimension: DimensionFixtures.countryURL() };
expect(equalParams(params, changedDimension)).to.be.false;
const changedSplit = { ...params, split: booleanSplitCombine(DimensionFixtures.wikiIsRobot().name) };
expect(equalParams(params, changedSplit)).to.be.false;
});

it("should return false if timekeeper is different", () => {
const params = mockQueryParams();
const timekeeper = Timekeeper.fromJS({ timeTags: {} });
const changedTimekeeper = { ...params, timekeeper };
expect(equalParams(params, changedTimekeeper)).to.be.false;
});
it("should return false if essence is different", () => {
const params = mockQueryParams();
const changedEssence = { ...params, essence: EssenceFixtures.wikiLineChart() };
expect(equalParams(params, changedEssence)).to.be.false;
});
it("should return false if searchText is different", () => {
const params = mockQueryParams();
const changedSearchText = { ...params, searchText: "foobar" };
expect(equalParams(params, changedSearchText)).to.be.false;
});
it("should return false if sortOn is different", () => {

it("should return false if clause is different", () => {
const params = mockQueryParams();
const changedSortOn = { ...params, sortOn: SortOnFixtures.defaultC() };
expect(equalParams(params, changedSortOn)).to.be.false;
const changedClause = { ...params, clause: stringContains(wikiChannel.name, "a") };
expect(equalParams(params, changedClause)).to.be.false;
});
});
});
22 changes: 9 additions & 13 deletions src/client/components/pinboard-tile/utils/query-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,21 @@
* limitations under the License.
*/

import { Dimension } from "../../../../common/models/dimension/dimension";
import { Essence } from "../../../../common/models/essence/essence";
import { SortOn } from "../../../../common/models/sort-on/sort-on";
import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper";
import { StringFilterClause } from "../../../../common/models/filter-clause/filter-clause";
import { Split } from "../../../../common/models/split/split";
import { safeEquals } from "../../../../common/utils/immutable-utils/immutable-utils";

export interface QueryParams {
essence: Essence;
searchText: string;
sortOn: SortOn;
timekeeper: Timekeeper;
dimension: Dimension;
split: Split;
clause: StringFilterClause | null;
}

export function equalParams(params: QueryParams, otherParams: Partial<QueryParams>): boolean {
const { essence, searchText, sortOn, dimension, timekeeper } = params;
const { essence: otherEssence, searchText: otherSearchText, sortOn: otherSortOn, dimension: otherDimension, timekeeper: otherTimekeeper } = otherParams;
const { essence, split, clause } = params;
const { essence: otherEssence, clause: otherClause, split: otherSplit } = otherParams;
return essence.equals(otherEssence) &&
searchText === otherSearchText &&
timekeeper === otherTimekeeper &&
dimension === otherDimension &&
sortOn.equals(otherSortOn);
safeEquals(clause, otherClause) &&
split.equals(otherSplit);
}
3 changes: 3 additions & 0 deletions src/client/components/pinboard-tile/utils/should-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import { SortOn } from "../../../../common/models/sort-on/sort-on";
import { PinboardTileProps, PinboardTileState } from "../pinboard-tile";

// TODO: there's mismatch between this method and QueryParams.
// Of course here we should have more properties (timekeeper, refreshRequestTimestamp)
// but we should operate on the same source of truth (clause vs searchText)
export function shouldFetchData(
{ essence, timekeeper, dimension, sortOn, refreshRequestTimestamp }: PinboardTileProps,
previousProps: PinboardTileProps,
Expand Down
20 changes: 19 additions & 1 deletion src/client/views/cube-view/api-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ import { ClientAppSettings } from "../../../common/models/app-settings/app-setti
import { Dimension } from "../../../common/models/dimension/dimension";
import { Essence } from "../../../common/models/essence/essence";
import { StringFilterClause } from "../../../common/models/filter-clause/filter-clause";
import { Binary, Nullary, Unary } from "../../../common/utils/functional/functional";
import { Split } from "../../../common/models/split/split";
import { Binary, Nullary, Ternary, Unary } from "../../../common/utils/functional/functional";
import { DEFAULT_VIEW_DEFINITION_VERSION, definitionConverters } from "../../../common/view-definitions";
import { filterDefinitionConverter } from "../../../common/view-definitions/version-4/filter-definition";
import { splitConverter } from "../../../common/view-definitions/version-4/split-definition";

import { Ajax } from "../../utils/ajax/ajax";

export type VisualizationQuery = Unary<Essence, Promise<Dataset>>;

export type PinboardQuery = Ternary<Essence, StringFilterClause | null, Split, Promise<Dataset>>;

export type RawDataQuery = Unary<Essence, Promise<Dataset>>;

export type BooleanFilterQuery = Binary<Essence, Dimension, Promise<Dataset>>;
Expand All @@ -41,6 +46,7 @@ export interface ApiContextValue {
rawDataQuery: RawDataQuery;
stringFilterQuery: StringFilterQuery;
numberFilterQuery: NumberFilterQuery;
pinboardQuery: PinboardQuery;
}

export const ApiContext = React.createContext<ApiContextValue>({
Expand All @@ -56,6 +62,9 @@ export const ApiContext = React.createContext<ApiContextValue>({
get rawDataQuery(): RawDataQuery {
throw new Error("Attempted to consume ApiContext when there was no Provider");
},
get pinboardQuery(): PinboardQuery {
throw new Error("Attempted to consume ApiContext when there was no Provider");
},
get stringFilterQuery(): StringFilterQuery {
throw new Error("Attempted to consume ApiContext when there was no Provider");
}
Expand Down Expand Up @@ -119,12 +128,21 @@ function createStringFilterQuery(settings: ClientAppSettings): StringFilterQuery
});
}

function createPinboardQuery(settings: ClientAppSettings): PinboardQuery {
return createApiCall(settings, "pinboard", (clause: StringFilterClause | null, split: Split) => {
const clauseJS = clause && filterDefinitionConverter.fromFilterClause(clause);
const splitJS = splitConverter.fromSplitCombine(split);
return ({ clause: clauseJS, split: splitJS });
});
}

function createRawDataQueryApi(settings: ClientAppSettings): RawDataQuery {
return createApiCall(settings, "raw-data", emptyParams);
}

function createApi(settings: ClientAppSettings): ApiContextValue {
return {
pinboardQuery: createPinboardQuery(settings),
numberFilterQuery: createNumberFilterQuery(settings),
booleanFilterQuery: createBooleanFilterQuery(settings),
visualizationQuery: createVizQueryApi(settings),
Expand Down
8 changes: 8 additions & 0 deletions src/common/models/filter-clause/filter-clause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,11 @@ export function fromJS(parameters: FilterDefinition): FilterClause {
}
}
}

export function isStringFilterClause(clause: FilterClause): clause is StringFilterClause {
return clause.type === FilterTypes.STRING;
}

export function isBooleanFilterClause(clause: FilterClause): clause is BooleanFilterClause {
return clause.type === FilterTypes.BOOLEAN;
}

0 comments on commit ee531e4

Please sign in to comment.