Skip to content

Commit

Permalink
Merge pull request #3327 from ingef/feature/preview-v2
Browse files Browse the repository at this point in the history
Feature/preview v2
  • Loading branch information
awildturtok authored Mar 5, 2024
2 parents 06042ec + b2d7ae8 commit 0d071ce
Show file tree
Hide file tree
Showing 36 changed files with 1,678 additions and 781 deletions.
339 changes: 339 additions & 0 deletions frontend/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.1.1",
"apache-arrow": "^13.0.0",
"axios": "^1.6.0",
"chance": "^1.1.11",
"chart.js": "^4.4.0",
Expand All @@ -57,6 +58,7 @@
"mustache": "^4.2.0",
"nodemon": "^3.0.1",
"prettier-plugin-organize-imports": "^3.2.3",
"rc-table": "^7.35.2",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-datepicker": "^4.21.0",
Expand Down Expand Up @@ -99,6 +101,7 @@
"@testing-library/react": "^14.0.0",
"@types/axios": "^0.14.0",
"@types/chance": "^1.1.5",
"@types/chart.js": "^2.9.41",
"@types/compression": "^1.7.4",
"@types/cors": "^2.8.15",
"@types/express": "^4.17.20",
Expand Down
43 changes: 42 additions & 1 deletion frontend/src/js/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from "react";
import { useCallback, useContext, useEffect, useRef } from "react";

import { EditorV2Query } from "../editor-v2/types";
import { EntityId } from "../entity-history/reducer";
Expand All @@ -11,6 +11,7 @@ import type { QueryToUploadT } from "../previous-queries/upload/CSVColumnPicker"
import { StandardQueryStateT } from "../standard-query-editor/queryReducer";
import { ValidatedTimebasedQueryStateT } from "../timebased-query-editor/reducer";

import { AuthTokenContext } from "../authorization/AuthTokenProvider";
import { transformQueryToApi } from "./apiHelper";
import type {
ConceptIdT,
Expand All @@ -34,6 +35,7 @@ import type {
PostLoginResponseT,
PostQueriesResponseT,
PostResolveEntitiesResponse,
PreviewStatisticsResponse,
QueryIdT,
UploadQueryResponseT,
} from "./types";
Expand Down Expand Up @@ -405,3 +407,42 @@ export const usePostResolveEntities = () => {
[api],
);
};

export const useGetResult = () => {
const { authToken } = useContext(AuthTokenContext);
const authTokenRef = useRef<string>(authToken);
useEffect(
function updateRef() {
authTokenRef.current = authToken;
},
[authToken],
);
return useCallback(
(queryId: string, limit = 1000) => {
const url =
`/result/arrow/${queryId}.arrs?` +
new URLSearchParams({ limit: limit.toString() });
const res = fetch(getProtectedUrl(url), {
headers: {
Authorization: `Bearer ${authTokenRef.current}`,
},
});
return res;
},
[authTokenRef],
);
};

export const usePreviewStatistics = () => {
const api = useApi<PreviewStatisticsResponse>();

return useCallback(
(queryId: string) =>
api({
url: getProtectedUrl(`/queries/${queryId}/statistics`),
method: "GET",
data: queryId,
}),
[api],
);
};
34 changes: 34 additions & 0 deletions frontend/src/js/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,37 @@ export type GetEntityHistoryResponse = {
export type PostResolveEntitiesResponse = {
[idKind: string]: string; // idKind is the key, the value is the resolved ID
}[];

export type BaseStatistics = {
label: string;
description?: string;
count: number;
nullValues: number;
};

export type BarStatistics = BaseStatistics & {
chart: "HISTO";
type: "INTEGER" | "DECIMAL" | "MONEY" | "STRING" | "REAL";
entries: { label: string; value: number }[];
extras: { [key: string]: string };
};

export type DateStatistics = BaseStatistics & {
chart: "DATES";
type: "DATE_RANGE" | "DATE";
quarterCounts: Record<string, number>;
monthCounts: Record<string, number>;
span: {
min: string; // format "yyyy-MM-dd"
max: string;
};
};

export type PreviewStatistics = BarStatistics | DateStatistics;

export type PreviewStatisticsResponse = {
entities: number;
total: number; // Number of rows
statistics: PreviewStatistics[];
dateRange: DateRangeT;
};
10 changes: 7 additions & 3 deletions frontend/src/js/app/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import styled from "@emotion/styled";
import { useSelector } from "react-redux";

import { History } from "../entity-history/History";
import Preview from "../preview/Preview";
import ActivateTooltip from "../tooltip/ActivateTooltip";
import Tooltip from "../tooltip/Tooltip";

import { useMemo } from "react";
import { Panel, PanelGroup } from "react-resizable-panels";
import { ResizeHandle } from "../common/ResizeHandle";
import Preview from "../preview/Preview";
import DndProvider from "./DndProvider";
import LeftPane from "./LeftPane";
import RightPane from "./RightPane";
Expand All @@ -33,6 +33,10 @@ const Content = () => {
(state) => state.entityHistory.isOpen,
);

const disableDragHandles = useSelector<StateT, boolean>(
(state) => state.panes.disableDragHandles,
);

const collapsedStyles = useMemo(() => {
if (displayTooltip) return {};

Expand All @@ -58,11 +62,11 @@ const Content = () => {
>
{displayTooltip ? <Tooltip /> : <ActivateTooltip />}
</Panel>
<ResizeHandle disabled={!displayTooltip} />
{!disableDragHandles && <ResizeHandle />}
<Panel minSize={350} defaultSize={600}>
<LeftPane />
</Panel>
<ResizeHandle />
{!disableDragHandles && <ResizeHandle />}
<Panel minSize={250}>
<RightPane />
</Panel>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { DatasetActions } from "../dataset/actions";
import type { EntityHistoryActions } from "../entity-history/actions";
import type { ExternalFormActions } from "../external-forms/actions";
import type { PaneActions } from "../pane/actions";
import type { PreviewActions } from "../preview/actions";
import { PreviewActions } from "../preview/actions";
import type { ProjectItemsFilterActions } from "../previous-queries/filter/actions";
import type { FolderFilterActions } from "../previous-queries/folder-filter/actions";
import type { PreviousQueryListActions } from "../previous-queries/list/actions";
Expand Down
49 changes: 31 additions & 18 deletions frontend/src/js/button/PreviewButton.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
import styled from "@emotion/styled";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";

import type { ColumnDescription } from "../api/types";
import { useGetAuthorizedUrl } from "../authorization/useAuthorizedUrl";
import {
faMagnifyingGlass,
faSpinner,
} from "@fortawesome/free-solid-svg-icons";
import { useMemo, useState } from "react";
import { StateT } from "../app/reducers";
import { openPreview, useLoadPreviewData } from "../preview/actions";
import IconButton, { IconButtonPropsT } from "./IconButton";

import { TransparentButton } from "./TransparentButton";

const Button = styled(TransparentButton)`
const Button = styled(IconButton)`
white-space: nowrap;
height: 35px;
padding: 5px 12px;
`;

const PreviewButton = ({
url,
columns,
...restProps
}: {
columns: ColumnDescription[];
url: string;
}) => {
const PreviewButton = (buttonProps: Partial<IconButtonPropsT>) => {
const { t } = useTranslation();
const dispatch = useDispatch();

const loadPreviewData = useLoadPreviewData();
const getAuthorizedUrl = useGetAuthorizedUrl();
const queryId = useSelector<StateT, string | null>(
(state) => state.preview.lastQuery,
);

const [isLoading, setLoading] = useState(false);
const icon = useMemo(
() => (isLoading ? faSpinner : faMagnifyingGlass),
[isLoading],
);

return (
<Button
frame
icon={icon}
onClick={async () => {
await loadPreviewData(getAuthorizedUrl(url), columns);
dispatch(openPreview());
if (queryId) {
setLoading(true);
setTimeout(async () => {
await loadPreviewData(queryId);
setLoading(false);
dispatch(openPreview());
});
}
}}
{...restProps}
{...buttonProps}
>
{t("preview.preview")}
</Button>
Expand Down
9 changes: 2 additions & 7 deletions frontend/src/js/button/QueryResultHistoryButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { faListUl, faSpinner } from "@fortawesome/free-solid-svg-icons";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";

import type { ColumnDescription } from "../api/types";
import type { StateT } from "../app/reducers";
import { useGetAuthorizedUrl } from "../authorization/useAuthorizedUrl";
import { openHistory, useNewHistorySession } from "../entity-history/actions";

import IconButton from "./IconButton";
Expand All @@ -16,27 +14,24 @@ const SxIconButton = styled(IconButton)`
`;

interface PropsT {
columns: ColumnDescription[];
label: string;
url: string;
}

export const QueryResultHistoryButton = ({ url, label, columns }: PropsT) => {
export const QueryResultHistoryButton = ({ label }: PropsT) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const isLoading = useSelector<StateT, boolean>(
(state) => state.entityHistory.isLoading,
);

const getAuthorizedUrl = useGetAuthorizedUrl();
const newHistorySession = useNewHistorySession();

return (
<SxIconButton
icon={isLoading ? faSpinner : faListUl}
frame
onClick={async () => {
await newHistorySession(getAuthorizedUrl(url), columns, label);
await newHistorySession(label);
dispatch(openHistory());
}}
>
Expand Down
19 changes: 16 additions & 3 deletions frontend/src/js/entity-history/TimeStratifiedChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
CategoryScale,
Chart as ChartJS,
ChartOptions,
LineElement,
LinearScale,
PointElement,
Title,
Tooltip,
} from "chart.js";
import { useMemo } from "react";
Expand All @@ -18,7 +21,17 @@ import { formatCurrency } from "./timeline/util";

const TRUNCATE_X_AXIS_LABELS_LEN = 18;

ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip);
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
);

const ChartContainer = styled("div")`
height: 190px;
Expand All @@ -27,7 +40,7 @@ const ChartContainer = styled("div")`
justify-content: flex-end;
`;

function hexToRgbA(hex: string) {
export function hexToRgbA(hex: string) {
let c: string | string[];
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
c = hex.substring(1).split("");
Expand All @@ -41,7 +54,7 @@ function hexToRgbA(hex: string) {
throw new Error("Bad Hex");
}

function interpolateDecreasingOpacity(index: number) {
export function interpolateDecreasingOpacity(index: number) {
return Math.min(1, 1 / (index + 0.3));
}

Expand Down
Loading

0 comments on commit 0d071ce

Please sign in to comment.