diff --git a/src/api/tesseract/parse.ts b/src/api/tesseract/parse.ts
index 71a6b07..9ca2b08 100644
--- a/src/api/tesseract/parse.ts
+++ b/src/api/tesseract/parse.ts
@@ -36,7 +36,8 @@ export function queryParamsToRequest(params: QueryParams): TesseractDataRequest
item.active ? filterSerialize(item) : null
).join(","),
limit: `${params.pagiLimit || 0},${params.pagiOffset || 0}`,
- sort: params.sortKey ? `${params.sortKey}.${params.sortDir}` : undefined
+ sort: params.sortKey ? `${params.sortKey}.${params.sortDir}` : undefined,
+ time: params.timeComplete ? `${params.timeComplete}.complete` : undefined
// sparse: params.sparse,
// ranking:
// typeof params.ranking === "boolean"
@@ -108,6 +109,8 @@ export function requestToQueryParams(cube: TesseractCube, search: URLSearchParam
const [limit = "0", offset = "0"] = (search.get("limit") || "0").split(",");
const [sortKey, sortDir] = (search.get("sort") || "").split(".");
+ const timeComplete = search.get("time")?.split(".")[0] || undefined;
+
return {
cube: cube.name,
locale: search.get("locale") || undefined,
@@ -120,6 +123,7 @@ export function requestToQueryParams(cube: TesseractCube, search: URLSearchParam
sortDir: sortDir === "asc" ? "asc" : "desc",
sortKey: sortKey || undefined,
isPreview: false,
+ timeComplete: timeComplete || undefined,
booleans: {
// parents: search.get("parents") || undefined,
}
diff --git a/src/components/CubeSource.tsx b/src/components/CubeSource.tsx
index f4791b6..030d7ef 100644
--- a/src/components/CubeSource.tsx
+++ b/src/components/CubeSource.tsx
@@ -2,11 +2,10 @@ import {Anchor, Text, TextProps} from "@mantine/core";
import React from "react";
import {useSelector} from "react-redux";
import {useTranslation} from "../hooks/translation";
-import {selectOlapCube} from "../state/selectors";
import {getAnnotation} from "../utils/string";
-import {selectCurrentQueryItem, selectLocale} from "../state/queries";
+import {selectLocale} from "../state/queries";
import type {Annotated} from "../utils/types";
-import {useSelectedItem, useServerSchema} from "../hooks/useQueryApi";
+import {useSelectedItem} from "../hooks/useQueryApi";
export function CubeAnnotation(
props: TextProps & {
@@ -42,14 +41,13 @@ export function CubeSourceAnchor(
return (
{`${t("params.label_source")}: `}
- {srcLink ? {srcName} : {srcName}}
+ {srcLink ? {srcName} : {srcName}}
);
}
export default function CubeSource() {
const selectedItem = useSelectedItem();
- // TODO: agregar locale
const {code: locale} = useSelector(selectLocale);
return (
selectedItem && (
diff --git a/src/components/DrawerMenu.tsx b/src/components/DrawerMenu.tsx
index f70a27a..fd13f0b 100644
--- a/src/components/DrawerMenu.tsx
+++ b/src/components/DrawerMenu.tsx
@@ -327,6 +327,8 @@ function LevelItem({
const paddingLeft = `${5 * depth + 5}px`;
const properties = currentDrilldown.properties.length ? currentDrilldown.properties : null;
+
+ const dimensionIsTimeComplete = dimension.annotations.de_time_complete === "true";
return (
currentDrilldown && (
<>
@@ -336,10 +338,49 @@ function LevelItem({
onChange={() => {
actions.updateDrilldown({
...currentDrilldown,
- active: !currentDrilldown.active,
+ active: !currentDrilldown.active
});
- if (cut && cut.members.length > 0)
- actions.updateCut({...cut, active: !cut.active});
+ if (cut && cut.members.length > 0) actions.updateCut({...cut, active: !cut.active});
+
+ // if current dimension has time complete annotation
+ if (dimensionIsTimeComplete) {
+ const hierarchyLevels =
+ dimension.hierarchies.find(h => h.name === hierarchy.name)?.levels || [];
+
+ // select all levels that are either active or match the current drilldown level to be added
+ const availableLevels = hierarchyLevels.filter(
+ l =>
+ l.name &&
+ activeItems.some(item =>
+ !currentDrilldown.active
+ ? item.level === l.name || l.name === currentDrilldown.level
+ : item.level === l.name && item.level !== currentDrilldown.level
+ )
+ );
+
+ // take the higher order level
+ const timeCompleteLevel = availableLevels.find(
+ l => l.depth === Math.min(...availableLevels.map(level => level.depth))
+ );
+ const deepestLevel = hierarchyLevels.find(
+ l => l.depth === Math.max(...hierarchyLevels.map(level => level.depth))
+ );
+
+ const deepestLevelAvailable = availableLevels.find(
+ l => l.depth === deepestLevel?.depth
+ );
+
+ if (
+ timeCompleteLevel &&
+ deepestLevel &&
+ timeCompleteLevel.depth < deepestLevel.depth &&
+ !deepestLevelAvailable
+ ) {
+ actions.updateTimeComplete(timeCompleteLevel.name);
+ } else {
+ actions.removeTimeComplete();
+ }
+ }
}}
checked={checked}
label={label}
diff --git a/src/components/TableView.tsx b/src/components/TableView.tsx
index 3638536..5379004 100644
--- a/src/components/TableView.tsx
+++ b/src/components/TableView.tsx
@@ -97,7 +97,7 @@ function isColumnSorted(column: string, key: string) {
const removeColumn = (
queryItem: QueryItem,
- entity: TesseractMeasure | TesseractProperty | TesseractLevel,
+ entity: TesseractMeasure | TesseractProperty | TesseractLevel
) => {
const newQuery = buildQuery(cloneDeep(queryItem));
const params = newQuery.params;
@@ -122,15 +122,15 @@ const removeColumn = (
const mapPropertyToDrilldown = Object.fromEntries(
Object.values(params.drilldowns)
.filter(drilldown => drilldown.active)
- .flatMap(drilldown => drilldown.properties.map(prop => [prop.name, drilldown])),
+ .flatMap(drilldown => drilldown.properties.map(prop => [prop.name, drilldown]))
);
const drilldown = mapPropertyToDrilldown[entity.name];
if (drilldown) {
params.drilldowns[drilldown.key] = {
...drilldown,
properties: drilldown.properties.map(prop =>
- prop.name === entity.name ? {...prop, active: false} : prop,
- ),
+ prop.name === entity.name ? {...prop, active: false} : prop
+ )
};
return newQuery;
}
diff --git a/src/context/query.tsx b/src/context/query.tsx
index 58f6047..cd35b61 100644
--- a/src/context/query.tsx
+++ b/src/context/query.tsx
@@ -183,7 +183,8 @@ export function QueryProvider({children, defaultCube}: QueryProviderProps) {
};
function setDefaultValues(cube: TesseractCube) {
- const drilldowns = pickDefaultDrilldowns(cube.dimensions, cube).map(level =>
+ const {levels, timeComplete} = pickDefaultDrilldowns(cube.dimensions, cube);
+ const drilldowns = levels.map(level =>
buildDrilldown({
...level,
key: level.name,
@@ -213,7 +214,8 @@ export function QueryProvider({children, defaultCube}: QueryProviderProps) {
cube: cube.name,
measures: keyBy(measures, item => item.key),
drilldowns: keyBy(drilldowns, item => item.key),
- locale
+ locale,
+ timeComplete
},
panel: panel ?? "table"
});
diff --git a/src/hooks/permalink.tsx b/src/hooks/permalink.tsx
index 48cd444..6f12a96 100644
--- a/src/hooks/permalink.tsx
+++ b/src/hooks/permalink.tsx
@@ -1,4 +1,4 @@
-import {useCallback, useEffect} from "react";
+import {useCallback} from "react";
import type {TesseractCube} from "../api";
import {queryParamsToRequest, requestToQueryParams} from "../api/tesseract/parse";
import {selectCurrentQueryItem} from "../state/queries";
diff --git a/src/hooks/useQueryApi.ts b/src/hooks/useQueryApi.ts
index d6b0181..dd8d6f7 100644
--- a/src/hooks/useQueryApi.ts
+++ b/src/hooks/useQueryApi.ts
@@ -1,26 +1,20 @@
-import {useQuery, useMutation, useQueryClient, keepPreviousData} from "@tanstack/react-query";
+import {useQuery, useMutation, keepPreviousData} from "@tanstack/react-query";
import type {
TesseractCube,
TesseractDataResponse,
TesseractFormat,
- TesseractMembersResponse
} from "../api";
-import type {TesseractLevel, TesseractHierarchy, TesseractDimension} from "../api/tesseract/schema";
-import {queryParamsToRequest, requestToQueryParams} from "../api/tesseract/parse";
-import {mapDimensionHierarchyLevels} from "../api/traverse";
+import {queryParamsToRequest} from "../api/tesseract/parse";
import {filterMap} from "../utils/array";
import {describeData, getOrderValue, getValues} from "../utils/object";
import {
buildDrilldown,
- buildMeasure,
buildProperty,
- buildQuery,
QueryParams
} from "../utils/structs";
import {keyBy} from "../utils/transform";
import type {FileDescriptor} from "../utils/types";
import {isValidQuery} from "../utils/validation";
-import {pickDefaultDrilldowns} from "../state/utils";
import {useLogicLayer} from "../api/context";
import {useSettings} from "./settings";
import {useSelector} from "../state";
@@ -255,251 +249,3 @@ export function useFetchQuery(
placeholderData: withoutPagination ? undefined : keepPreviousData
});
}
-
-// Hook to fetch members for a level
-export function useMembers(level: string, localeStr?: string, cubeName?: string) {
- const {tesseract} = useLogicLayer();
-
- return useQuery({
- queryKey: ["members", level, localeStr, cubeName],
- queryFn: async (): Promise => {
- return tesseract.fetchMembers({
- request: {cube: cubeName || "", level, locale: localeStr}
- });
- },
- enabled: !!level
- });
-}
-
-// Hook to hydrate params
-export function useHydrateParams(
- cubeMap: Record,
- queries: Array<{
- key: string;
- params: {
- cube: string;
- measures: Record;
- drilldowns: Record<
- string,
- {level: string; properties: Array<{active: boolean; name: string}>}
- >;
- };
- }>,
- suggestedCube = ""
-) {
- const queryClient = useQueryClient();
- const {tesseract} = useLogicLayer();
-
- function isCompleteTuple(
- tuple: [TesseractLevel, TesseractHierarchy, TesseractDimension] | undefined
- ): tuple is [TesseractLevel, TesseractHierarchy, TesseractDimension] {
- return tuple !== undefined && tuple.length === 3 && tuple.every(item => item !== undefined);
- }
-
- return useMutation({
- mutationFn: async () => {
- const defaultCube = cubeMap[suggestedCube] || Object.values(cubeMap)[0];
-
- const queryPromises = queries.map(queryItem => {
- const {params} = queryItem;
- const {measures: measureItems} = params;
-
- const cube = cubeMap[params.cube] || defaultCube;
- const levelMap = mapDimensionHierarchyLevels(cube);
-
- const resolvedMeasures = cube.measures.map(measure =>
- buildMeasure(
- measureItems[measure.name] || {
- active: false,
- key: measure.name,
- name: measure.name,
- caption: measure.caption
- }
- )
- );
-
- const resolvedDrilldowns = filterMap(
- Object.values(params.drilldowns),
- (item: {level: string; properties: Array<{active: boolean; name: string}>}) => {
- const levelTuple = levelMap[item.level];
- if (!isCompleteTuple(levelTuple)) return null;
-
- const [level, hierarchy, dimension] = levelTuple!;
-
- const activeProperties = filterMap(
- item.properties,
- (prop: {active: boolean; name: string}) => (prop.active ? prop.name : null)
- );
- return buildDrilldown({
- active: true,
- key: level.name,
- dimension: dimension.name!,
- hierarchy: hierarchy.name!,
- level: level.name,
- captionProperty: "",
- members: [],
- properties: level.properties.map(property =>
- buildProperty({
- active: activeProperties.includes(property.name),
- level: level.name,
- name: property.name
- })
- )
- });
- }
- );
-
- return {
- ...queryItem,
- params: {
- ...params,
- cube: cube.name,
- drilldowns: keyBy(resolvedDrilldowns, item => item.key),
- measures: keyBy(resolvedMeasures, item => item.key)
- }
- };
- });
-
- const resolvedQueries = await Promise.all(queryPromises);
- return keyBy(resolvedQueries, i => i.key);
- },
- onSuccess: queryMap => {
- // Update the query client cache with the hydrated queries
- queryClient.setQueryData(["hydratedQueries"], queryMap);
- }
- });
-}
-
-// Hook to parse query URL
-export function useParseQueryUrl() {
- const queryClient = useQueryClient();
- const {tesseract} = useLogicLayer();
-
- return useMutation({
- mutationFn: async ({
- url,
- cubeMap
- }: {
- url: string | URL;
- cubeMap: Record;
- }) => {
- const search = new URL(url).searchParams;
- const cube = search.get("cube");
-
- if (cube && cubeMap[cube]) {
- const params = requestToQueryParams(cubeMap[cube], search);
-
- const queryItem = buildQuery({
- panel: search.get("panel") || "table",
- chart: search.get("chart") || "",
- params
- });
-
- return queryItem;
- }
-
- return null;
- },
- onSuccess: queryItem => {
- if (queryItem) {
- // Update the query client cache
- queryClient.setQueryData(["currentQuery"], queryItem);
- queryClient.setQueryData(["selectedQuery"], queryItem.key);
- }
- }
- });
-}
-
-// Hook to reload cubes
-export function useReloadCubes() {
- const {tesseract} = useLogicLayer();
-
- return useMutation({
- mutationFn: async ({locale}: {locale: {code: string}}) => {
- const schema = await tesseract.fetchSchema({locale: locale.code});
- const cubes = schema.cubes.filter(cube => !cube.annotations.hide_in_ui);
- return keyBy(cubes, i => i.name);
- },
- onSuccess: cubeMap => {
- // Update the query client cache
- const queryClient = useQueryClient();
- queryClient.setQueryData(["cubeMap"], cubeMap);
- }
- });
-}
-
-// Hook to set cube
-export function useSetCube() {
- const queryClient = useQueryClient();
- const {tesseract, dataLocale} = useLogicLayer();
-
- return useMutation({
- mutationFn: async ({
- cubeName,
- cubeMap,
- measuresActive
- }: {
- cubeName: string;
- cubeMap: Record;
- measuresActive?: number;
- }) => {
- const nextCube = cubeMap[cubeName];
- if (!nextCube) return null;
-
- const measuresLimit =
- typeof measuresActive !== "undefined" ? measuresActive : nextCube.measures.length;
-
- const nextMeasures = nextCube.measures.slice(0, measuresLimit).map(measure => {
- return buildMeasure({
- active: true,
- key: measure.name,
- name: measure.name,
- caption: measure.caption
- });
- });
-
- const nextDrilldowns = pickDefaultDrilldowns(nextCube.dimensions).map(level =>
- buildDrilldown({
- ...level,
- key: level.name,
- active: true,
- properties: level.properties.map(prop =>
- buildProperty({level: level.name, name: prop.name})
- )
- })
- );
-
- // Fetch members for each drilldown
- const drilldownPromises = nextDrilldowns.map(async dd => {
- const levelMeta = await tesseract.fetchMembers({
- request: {cube: nextCube.name, level: dd.level, locale: dataLocale}
- });
-
- return {
- ...dd,
- members: levelMeta.members
- };
- });
-
- const drilldownsWithMembers = await Promise.all(drilldownPromises);
-
- return {
- cube: nextCube.name,
- measures: keyBy(nextMeasures, item => item.key),
- drilldowns: keyBy(drilldownsWithMembers, item => item.key),
- locale: dataLocale
- };
- },
- onSuccess: result => {
- if (result) {
- // Update the query client cache
- queryClient.setQueryData(["currentCube"], result.cube);
- queryClient.setQueryData(["measures"], result.measures);
- queryClient.setQueryData(["drilldowns"], result.drilldowns);
- if (result.locale) {
- queryClient.setQueryData(["locale"], result.locale);
- }
- }
- }
- });
-}
diff --git a/src/state/index.ts b/src/state/index.ts
index 90f5d6b..fe54c38 100644
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -1,6 +1,4 @@
import {queriesActions} from "./queries";
-// import {serverActions} from "./server";
-// import * as thunks from "./thunks";
export type {QueriesState} from "./queries";
export type {ServerState} from "./server";
@@ -17,5 +15,3 @@ export {queriesActions};
export type ExplorerActionMap = typeof actions;
export const actions = queriesActions;
-
-// TODO: Remove thunks
diff --git a/src/state/queries.ts b/src/state/queries.ts
index 667a46a..c15db8b 100644
--- a/src/state/queries.ts
+++ b/src/state/queries.ts
@@ -127,6 +127,10 @@ export const queriesSlice = createSlice({
delete query.params.filters[action.payload];
},
+ removeTimeComplete(state) {
+ const query = taintCurrentQuery(state);
+ delete query.params.timeComplete;
+ },
/**
* Replaces multiple QueryParams for the current QueryItem at once.
*/
@@ -233,6 +237,15 @@ export const queriesSlice = createSlice({
query.params.drilldowns[payload.key] = payload;
},
+ /**
+ * Replaces the timeComplete value in the current QueryItem.
+ */
+ updateTimeComplete(state, {payload}: Action) {
+ const query = taintCurrentQuery(state);
+ if (payload !== query.params.timeComplete) {
+ query.params.timeComplete = payload;
+ }
+ },
/**
* Replaces a single FilterItem in the current QueryItem.
*/
@@ -247,7 +260,6 @@ export const queriesSlice = createSlice({
updateLocale(state, {payload}: Action) {
const query = state.itemMap[state.current];
if (payload !== query.params.locale) {
- // query.isDirty = true;
query.params.locale = payload;
}
},
diff --git a/src/state/thunks.ts b/src/state/thunks.ts
deleted file mode 100644
index f09c8dc..0000000
--- a/src/state/thunks.ts
+++ /dev/null
@@ -1,436 +0,0 @@
-import type {
- TesseractCube,
- TesseractDataResponse,
- TesseractFormat,
- TesseractMembersResponse
-} from "../api";
-import {queryParamsToRequest, requestToQueryParams} from "../api/tesseract/parse";
-import {mapDimensionHierarchyLevels} from "../api/traverse";
-import {filterMap} from "../utils/array";
-import {describeData} from "../utils/object";
-import {
- type QueryResult,
- buildCut,
- buildDrilldown,
- buildMeasure,
- buildProperty,
- buildQuery
-} from "../utils/structs";
-import {keyBy} from "../utils/transform";
-import type {FileDescriptor} from "../utils/types";
-import {isValidQuery, noop} from "../utils/validation";
-import {loadingActions} from "./loading";
-import {
- queriesActions,
- selectCubeName,
- selectCurrentQueryParams,
- selectLocale,
- selectQueryItems
-} from "./queries";
-import {selectOlapCubeMap, serverActions} from "./server";
-import type {ExplorerThunk} from "./store";
-import {pickDefaultDrilldowns} from "./utils";
-
-/**
- * Initiates a new download of the queried data by the current parameters.
- *
- * @param format - The format the user wants the data to be.
- * @returns A blob with the data contents, in the request format.
- */
-export function willDownloadQuery(
- format: `${TesseractFormat}`
-): ExplorerThunk> {
- return (dispatch, getState, {tesseract}) => {
- const state = getState();
- const params = selectCurrentQueryParams(state);
-
- if (!isValidQuery(params)) {
- return Promise.reject(new Error("The current query is not valid."));
- }
-
- const queryParams = {...params, pagiLimit: 0, pagiOffset: 0};
- return tesseract
- .fetchData({request: queryParamsToRequest(queryParams), format})
- .then(response => response.blob())
- .then(result => ({
- content: result,
- extension: format.replace(/json\w+/, "json"),
- name: `${params.cube}_${new Date().toISOString()}`
- }));
- };
-}
-
-/**
- * Takes the current parameters, and queries the OLAP server for data with them.
- * The result is stored in QueryItem["result"].
- * This operation does not activate the Loading overlay in the UI; you must use
- * `willRequestQuery()` for that.
- */
-export function willExecuteQuery(params?: {
- limit?: number;
- offset?: number;
-}): ExplorerThunk> {
- const {limit = 0, offset = 0} = params || {};
- return dispatch => {
- return dispatch(willFetchQuery(params)).then(
- result => {
- dispatch(queriesActions.updateResult(result));
- },
- error => {
- if (error.name === "TypeError" && error.message.includes("NetworkError")) {
- console.error("Network error or CORS error occurred:", error);
- } else if (error.message.includes("Unexpected token")) {
- console.error("Syntax error while parsing JSON:", error);
- } else if (error.message.includes("Backend Error")) {
- console.error("Server returned an error response:", error);
- } else {
- console.error("An unknown error occurred:", error);
- }
- dispatch(
- queriesActions.updateResult({
- data: [],
- types: {},
- page: {limit, offset, total: 0},
- status: 0,
- url: ""
- })
- );
- }
- );
- };
-}
-
-export function willFetchQuery(params?: {
- limit?: number;
- offset?: number;
- withoutPagination?: boolean;
-}): ExplorerThunk> {
- const {limit = 0, offset = 0} = params || {};
- return (dispatch, getState, {tesseract}) => {
- const state = getState();
- const queryParams = selectCurrentQueryParams(state);
- const cube = selectOlapCubeMap(state)[queryParams.cube];
-
- if (!isValidQuery(queryParams) || !cube) {
- return Promise.reject(new Error("Invalid query"));
- }
-
- const request = queryParamsToRequest(queryParams);
- if (limit || offset) {
- request.limit = `${limit},${offset}`;
- } else if (params?.withoutPagination) {
- request.limit = "0,0";
- }
-
- return tesseract.fetchData({request, format: "jsonrecords"}).then(response =>
- response.json().then((content: TesseractDataResponse) => {
- if (!response.ok) {
- throw new Error(`Backend Error: ${content.detail}`);
- }
- return {
- data: content.data,
- page: content.page,
- types: describeData(cube, queryParams, content),
- headers: Object.fromEntries(response.headers),
- status: response.status || 200,
- url: response.url
- };
- })
- );
- };
-}
-
-/**
- * Requests the list of associated Members for a certain Level.
- *
- * @param level - The name of the Level for whom we want to retrieve members.
- * @returns The list of members for the requested level.
- */
-export function willFetchMembers(
- level: string,
- localeStr?: string,
- cubeName?: string
-): ExplorerThunk> {
- return (dispatch, getState, {tesseract}) => {
- const state = getState();
- const cube = selectCubeName(state);
- const locale = selectLocale(state);
-
- return tesseract.fetchMembers({
- request: {cube: cubeName || cube, level, locale: localeStr || locale.code}
- });
- };
-}
-
-/**
- * Checks the state of the current QueryParams and fills missing information.
- *
- * @param suggestedCube The cube to resolve the missing data from.
- */
-export function willHydrateParams(suggestedCube = ""): ExplorerThunk> {
- return (dispatch, getState) => {
- const state = getState();
- const cubeMap = selectOlapCubeMap(state);
- const queries = selectQueryItems(state);
-
- const defaultCube = cubeMap[suggestedCube] || Object.values(cubeMap)[0];
-
- const queryPromises = queries.map(queryItem => {
- const {params} = queryItem;
- const {measures: measureItems} = params;
-
- const cube = cubeMap[params.cube] || defaultCube;
- const levelMap = mapDimensionHierarchyLevels(cube);
-
- const resolvedMeasures = cube.measures.map(measure =>
- buildMeasure(
- measureItems[measure.name] || {
- active: false,
- key: measure.name,
- name: measure.name,
- caption: measure.caption
- }
- )
- );
-
- const resolvedDrilldowns = filterMap(Object.values(params.drilldowns), item => {
- const [level, hierarchy, dimension] = levelMap[item.level] || [];
- if (!level) return null;
- const activeProperties = filterMap(item.properties, prop =>
- prop.active ? prop.name : null
- );
- return level
- ? buildDrilldown({
- ...item,
- key: level.name,
- dimension: dimension.name,
- hierarchy: hierarchy.name,
- properties: level.properties.map(property =>
- buildProperty({
- active: activeProperties.includes(property.name),
- level: level.name,
- name: property.name
- })
- )
- })
- : null;
- });
-
- return {
- ...queryItem,
- params: {
- ...params,
- locale: params.locale || state.explorerServer.locale,
- cube: cube.name,
- drilldowns: keyBy(resolvedDrilldowns, item => item.key),
- measures: keyBy(resolvedMeasures, item => item.key)
- }
- };
- });
-
- return Promise.all(queryPromises).then(resolvedQueries => {
- const queryMap = keyBy(resolvedQueries, i => i.key);
- dispatch(queriesActions.resetQueries(queryMap));
- });
- };
-}
-
-/**
- * Parses the search parameters in an URL to create a QueryParam object,
- * then creates a new QueryItem in the UI containing it.
- */
-export function willParseQueryUrl(url: string | URL): ExplorerThunk> {
- return async (dispatch, getState) => {
- const state = getState();
- const cubeMap = selectOlapCubeMap(state);
-
- const search = new URL(url).searchParams;
- const cube = search.get("cube");
- if (cube && cubeMap[cube]) {
- const params = requestToQueryParams(cubeMap[cube], search);
- // const promises = Object.values(params.drilldowns).map(dd => {
- // return dispatch(willFetchMembers(dd.level)).then(levelMeta => {
- // dispatch(queriesActions.updateCut(buildCut({...dd, active: true})));
- // return {
- // ...dd,
- // members: levelMeta.members
- // };
- // });
- // });
-
- // const dds = await Promise.all(promises);
-
- const queryItem = buildQuery({
- panel: search.get("panel") || "table",
- chart: search.get("chart") || "",
- // params: {...params, drilldowns: keyBy(dds, "key")}
- params
- });
-
- dispatch(queriesActions.updateQuery(queryItem));
- dispatch(queriesActions.selectQuery(queryItem.key));
- }
-
- return Promise.resolve();
- };
-}
-
-/**
- * Performs a full replacement of the cubes stored in the state with fresh data
- * from the server.
- */
-
-export function willReloadCubes(params?: {locale: {code: string}}): ExplorerThunk<
- Promise<{[k: string]: TesseractCube}>
-> {
- const {locale} = params || {};
- return (dispatch, getState, {tesseract}) => {
- const state = getState();
- const newLocale = locale || selectLocale(state);
-
- return tesseract.fetchSchema({locale: newLocale.code}).then(schema => {
- const cubes = schema.cubes.filter(cube => !cube.annotations.hide_in_ui);
- const cubeMap = keyBy(cubes, i => i.name);
- dispatch(serverActions.updateServer({cubeMap}));
- return cubeMap;
- });
- };
-}
-
-/**
- * Executes the full Query request procedure, including the calls to activate
- * the loading overlay.
- */
-export function willRequestQuery(): ExplorerThunk> {
- return (dispatch, getState) => {
- const state = getState();
- const params = selectCurrentQueryParams(state);
-
- if (!isValidQuery(params)) return Promise.resolve();
-
- dispatch(loadingActions.setLoadingState("FETCHING"));
- return dispatch(willExecuteQuery()).then(
- () => {
- dispatch(loadingActions.setLoadingState("SUCCESS"));
- },
- error => {
- dispatch(loadingActions.setLoadingState("FAILURE", error.message));
- }
- );
- };
-}
-
-/**
- * Changes the current cube and updates related state
- * If the new cube contains a measure with the same name as a measure in the
- * previous cube, keep its state.
- *
- * @param cubeName The name of the cube we intend to switch to.
- */
-export function willSetCube(
- cubeName: string,
- measuresActive?: number,
- locale?: string
-): ExplorerThunk> {
- return (dispatch, getState) => {
- const state = getState();
-
- const cubeMap = selectOlapCubeMap(state);
- const nextCube = cubeMap[cubeName];
- if (!nextCube) return Promise.resolve();
-
- const measuresLimit =
- typeof measuresActive !== "undefined" ? measuresActive : nextCube.measures.length;
-
- const nextMeasures = nextCube.measures.slice(0, measuresLimit).map(measure => {
- return buildMeasure({
- active: true,
- key: measure.name,
- name: measure.name,
- caption: measure.caption
- });
- });
-
- const nextDrilldowns = pickDefaultDrilldowns(nextCube.dimensions).map(level =>
- buildDrilldown({
- ...level,
- key: level.name,
- active: true,
- properties: level.properties.map(prop =>
- buildProperty({level: level.name, name: prop.name})
- )
- })
- );
-
- locale && dispatch(queriesActions.updateLocale(locale));
-
- dispatch(
- queriesActions.updateCube({
- cube: nextCube.name,
- measures: keyBy(nextMeasures, item => item.key),
- drilldowns: keyBy(nextDrilldowns, item => item.key)
- })
- );
-
- const promises = nextDrilldowns.map(dd => {
- return dispatch(willFetchMembers(dd.level, locale)).then(levelMeta => {
- dispatch(
- queriesActions.updateDrilldown({
- ...dd,
- members: levelMeta.members
- })
- );
- dispatch(queriesActions.updateCut(buildCut({...dd, active: false})));
- });
- });
-
- return Promise.all(promises).then(noop);
- };
-}
-
-export function willReloadCube({locale}: {locale: {code: string}}): ExplorerThunk> {
- return (dispatch, getState) => {
- const state = getState();
- const cubeName = selectCubeName(state);
-
- return dispatch(willSetCube(cubeName, undefined, locale.code));
- };
-}
-/**
- * Sets the necessary info for the client instance to be able to connect to the
- * server, then loads the base data from its schema.
- */
-export function willSetupClient(
- baseURL: string,
- defaultLocale?: string,
- requestConfig?: RequestInit
-): ExplorerThunk> {
- return (dispatch, getState, {tesseract}) => {
- tesseract.baseURL = baseURL.replace(/\/?$/, '/');
- const state = getState();
- const search = new URLSearchParams(location.search);
- const locale = search.get("locale");
- Object.assign(tesseract.requestConfig, requestConfig || {headers: new Headers()});
-
- return tesseract.fetchSchema({locale: locale || defaultLocale}).then(
- schema => {
- const cubes = schema.cubes.filter(cube => !cube.annotations.hide_in_ui);
- const cubeMap = keyBy(cubes, "name");
- dispatch(
- serverActions.updateServer({
- cubeMap,
- locale: defaultLocale || schema.default_locale,
- localeOptions: schema.locales,
- online: true,
- url: baseURL
- })
- );
- return cubeMap;
- },
- error => {
- dispatch(serverActions.updateServer({online: false, url: baseURL}));
- throw error;
- }
- );
- };
-}
diff --git a/src/state/utils.ts b/src/state/utils.ts
index 6c2a251..ba2e6dd 100644
--- a/src/state/utils.ts
+++ b/src/state/utils.ts
@@ -9,6 +9,7 @@ function calcMaxMemberCount(lengths) {
*/
export function pickDefaultDrilldowns(dimensions: TesseractDimension[], cube: TesseractCube) {
const levels: TesseractLevel[] = [];
+ let timeComplete;
let suggestedLevels: string[] = [];
for (const key in cube.annotations) {
if (key === "suggested_levels") {
@@ -22,9 +23,18 @@ export function pickDefaultDrilldowns(dimensions: TesseractDimension[], cube: Te
for (const dimension of dimensions) {
if (dimension.type === "time" || levels.length < 4) {
const hierarchy = findDefaultHierarchy(dimension);
+ const hierarchyDepth = Math.max(...hierarchy.levels.map(l => l.depth));
// uses deepest level for geo dimensions
const levelIndex = dimension.type === "geo" ? hierarchy.levels.length - 1 : 0;
- levels.push({...hierarchy.levels[levelIndex], type: dimension.type});
+ const defaultLevel = hierarchy.levels[levelIndex];
+ if (
+ dimension.type === "time" &&
+ dimension.annotations.de_time_complete === "true" &&
+ defaultLevel.depth < hierarchyDepth
+ ) {
+ timeComplete = defaultLevel.name;
+ }
+ levels.push({...defaultLevel, type: dimension.type});
}
}
@@ -67,5 +77,5 @@ export function pickDefaultDrilldowns(dimensions: TesseractDimension[], cube: Te
totalCount = calcMaxMemberCount(levels.map(l => l.count)); // Recalculate totalCount
}
- return levels;
+ return {levels, timeComplete};
}
diff --git a/src/utils/structs.ts b/src/utils/structs.ts
index fbad758..87125e2 100644
--- a/src/utils/structs.ts
+++ b/src/utils/structs.ts
@@ -28,6 +28,7 @@ export interface QueryParams {
pagiOffset: number;
sortDir: "asc" | "desc";
sortKey: string | undefined;
+ timeComplete: string | undefined;
}
export interface QueryResult> {
@@ -159,7 +160,8 @@ export function buildQueryParams(props): QueryParams {
pagiLimit: props.pagiLimit || props.limitAmount || props.limit || 100,
pagiOffset: props.pagiOffset || props.limitOffset || props.offset || 0,
sortDir: props.sortDir || props.sortDirection || props.sortOrder || props.order || "desc",
- sortKey: props.sortKey || props.sortProperty || ""
+ sortKey: props.sortKey || props.sortProperty || "",
+ timeComplete: props.timeComplete || undefined
};
}