Skip to content

Commit a33cd23

Browse files
committed
StatsHouse UI: update transport dashboard
1 parent 3c5b361 commit a33cd23

File tree

19 files changed

+336
-180
lines changed

19 files changed

+336
-180
lines changed

statshouse-ui/src/admin/AdminDashControl/AdminDashControl.tsx

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
// License, v. 2.0. If a copy of the MPL was not distributed with this
55
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
66

7-
import React, { useCallback, useEffect, useId, useMemo, useState } from 'react';
7+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
88
import { useStatsHouse } from 'store2';
9-
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
10-
import { apiDashboardListFetch } from '../../api/dashboardsList';
11-
import { apiDashboardFetch, DashboardInfo } from '../../api/dashboard';
9+
import { useMutation, useQueryClient } from '@tanstack/react-query';
10+
import { selectApiDashboardList, useApiDashboardList } from '../../api/dashboardsList';
11+
import { apiDashboardSave, DashboardInfo, useApiDashboard } from '../../api/dashboard';
1212
import cn from 'classnames';
1313
import { useIntersectionObserver } from '../../hooks';
1414
import { isObject, SearchFabric, toNumber } from '../../common/helpers';
@@ -17,7 +17,6 @@ import { arrToObj, getDefaultParams, toTreeObj, treeParamsObjectValueSymbol } fr
1717
import { GET_PARAMS } from '../../api/enum';
1818
import { loadDashboard } from '../../store2/urlStore/loadDashboard';
1919
import { Link, useSearchParams } from 'react-router-dom';
20-
import { saveDashboard } from '../../store2/urlStore';
2120
import { Button } from '../../components/UI';
2221
import { ReactComponent as SVGFloppy } from 'bootstrap-icons/icons/floppy.svg';
2322
import { produce } from 'immer';
@@ -26,32 +25,6 @@ import { fmtInputDateTime } from '../../view/utils2';
2625
const versionsList = [0, 1, 2, 3];
2726
const actualVersion = 3;
2827

29-
function useDashboardList() {
30-
return useQuery({
31-
queryKey: ['/api/dashboards-list'],
32-
queryFn: async () => {
33-
const { error, response } = await apiDashboardListFetch();
34-
if (error) {
35-
throw error;
36-
}
37-
return response?.data.dashboards ?? [];
38-
},
39-
});
40-
}
41-
function useDashboardInfo(id: number, enabled: boolean = true) {
42-
return useQuery({
43-
queryKey: ['/api/dashboard', id],
44-
enabled,
45-
retry: false,
46-
queryFn: async () => {
47-
const { error, response } = await apiDashboardFetch({ id: id.toString() });
48-
if (error) {
49-
throw error;
50-
}
51-
return response?.data;
52-
},
53-
});
54-
}
5528
function useUpdateDashboard() {
5629
const queryClient = useQueryClient();
5730
return useMutation({
@@ -64,7 +37,7 @@ function useUpdateDashboard() {
6437
if (errorLoad) {
6538
throw errorLoad;
6639
}
67-
const { response, error } = await saveDashboard(saveParams);
40+
const { response, error } = await apiDashboardSave(saveParams);
6841

6942
if (error) {
7043
throw error;
@@ -84,7 +57,7 @@ export function AdminDashControl() {
8457
const searchVersionValue = useMemo(() => toNumber(searchParams.get('v')), [searchParams]);
8558
const searchOldValue = useMemo(() => !!searchParams.get('o'), [searchParams]);
8659
const [autoUpdate, setAutoUpdate] = useState(false);
87-
const dashboardList = useDashboardList();
60+
const dashboardList = useApiDashboardList(selectApiDashboardList);
8861
const [dashVersions, setDashVersions] = useState<Record<string, number>>({});
8962

9063
const filterList = useMemo(() => {
@@ -233,10 +206,11 @@ function DashItem({ item, setDashVersions, autoUpdate }: DashItemProps) {
233206
}
234207
}, [visible]);
235208
const queryClient = useQueryClient();
236-
const dashboardInfo = useDashboardInfo(item.id, visibleBool);
209+
const dashboardInfo = useApiDashboard(item.id.toString(), undefined, undefined, visibleBool);
237210
const updateDashboard = useUpdateDashboard();
238-
const dashVersionKey = dashboardInfo.data?.dashboard.version;
239-
const dashVersion = useMemo(() => getVersion(dashboardInfo.data), [dashboardInfo]);
211+
const data = dashboardInfo.data?.data;
212+
const dashVersionKey = data?.dashboard.version;
213+
const dashVersion = useMemo(() => getVersion(data), [data]);
240214
const needUpdate = dashVersion > -1 && dashVersion < actualVersion;
241215

242216
useEffect(() => {
@@ -277,10 +251,7 @@ function DashItem({ item, setDashVersions, autoUpdate }: DashItemProps) {
277251
<Link to={`/view?id=${item.id}`} target="_blank">
278252
{dashVersionKey}
279253
</Link>{' '}
280-
<span>
281-
{dashboardInfo.data?.dashboard.update_time &&
282-
fmtInputDateTime(new Date(dashboardInfo.data?.dashboard.update_time * 1000))}
283-
</span>
254+
<span>{data?.dashboard.update_time && fmtInputDateTime(new Date(data?.dashboard.update_time * 1000))}</span>
284255
</div>
285256
<div>{item.name}</div>
286257
<div className="text-body-tertiary">{item.description}</div>

statshouse-ui/src/api/dashboard.ts

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,20 @@
55
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
66

77
import { GET_PARAMS } from './enum';
8-
import { apiFetch } from './api';
8+
import { apiFetch, ApiFetchResponse, ExtendedError } from './api';
9+
import {
10+
CancelledError,
11+
QueryClient,
12+
UndefinedInitialDataOptions,
13+
useMutation,
14+
UseMutationOptions,
15+
useQuery,
16+
useQueryClient,
17+
} from '@tanstack/react-query';
18+
import { queryClient } from '../common/queryClient';
19+
import { QueryParams, urlEncode } from '../url2';
20+
import { dashboardMigrateSaveToOld } from '../store2/urlStore/dashboardMigrate';
21+
import { toNumber } from '../common/helpers';
922

1023
const ApiDashboardEndpoint = '/api/dashboard';
1124

@@ -61,3 +74,139 @@ export async function apiDashboardSaveFetch(params: ApiDashboardPost, keyRequest
6174
keyRequest,
6275
});
6376
}
77+
78+
export function getDashboardOptions<T = ApiDashboard>(
79+
dashboardId: string,
80+
dashboardVersion?: string
81+
): UndefinedInitialDataOptions<ApiDashboard, ExtendedError, T, [string, ApiDashboardGet]> {
82+
const fetchParams: ApiDashboardGet = { [GET_PARAMS.dashboardID]: dashboardId };
83+
if (dashboardVersion != null) {
84+
fetchParams[GET_PARAMS.dashboardApiVersion] = dashboardVersion;
85+
}
86+
return {
87+
queryKey: [ApiDashboardEndpoint, fetchParams],
88+
queryFn: async ({ signal }) => {
89+
const { response, error } = await apiDashboardFetch(fetchParams, signal);
90+
if (error) {
91+
throw error;
92+
}
93+
if (!response) {
94+
throw new ExtendedError('empty response');
95+
}
96+
return response;
97+
},
98+
placeholderData: (previousData, previousQuery) => previousData,
99+
};
100+
}
101+
102+
export async function apiDashboard(dashboardId: string, dashboardVersion?: string) {
103+
const result: ApiFetchResponse<ApiDashboard> = { ok: false, status: 0 };
104+
try {
105+
result.response = await queryClient.fetchQuery(getDashboardOptions<ApiDashboard>(dashboardId, dashboardVersion));
106+
result.ok = true;
107+
} catch (error) {
108+
result.status = ExtendedError.ERROR_STATUS_UNKNOWN;
109+
if (error instanceof ExtendedError) {
110+
result.error = error;
111+
result.status = error.status;
112+
} else if (error instanceof CancelledError) {
113+
result.error = new ExtendedError(error, ExtendedError.ERROR_STATUS_ABORT);
114+
result.status = ExtendedError.ERROR_STATUS_ABORT;
115+
} else {
116+
result.error = new ExtendedError(error);
117+
}
118+
}
119+
return result;
120+
}
121+
122+
export function useApiDashboard<T = ApiDashboard>(
123+
dashboardId: string,
124+
dashboardVersion?: string,
125+
select?: (response?: ApiDashboard) => T,
126+
enabled: boolean = true
127+
) {
128+
const options = getDashboardOptions<ApiDashboard>(dashboardId, dashboardVersion);
129+
return useQuery({ ...options, select, enabled });
130+
}
131+
export function getDashboardSaveFetchParams(params: QueryParams, remove?: boolean): DashboardInfo {
132+
const searchParams = urlEncode(params);
133+
const oldDashboardParams = dashboardMigrateSaveToOld(params);
134+
oldDashboardParams.dashboard.data.searchParams = searchParams;
135+
const dashboardParams: DashboardInfo = {
136+
dashboard: {
137+
name: params.dashboardName,
138+
description: params.dashboardDescription,
139+
version: params.dashboardVersion ?? 0,
140+
dashboard_id: toNumber(params.dashboardId) ?? undefined,
141+
data: {
142+
...oldDashboardParams.dashboard.data,
143+
searchParams,
144+
},
145+
},
146+
};
147+
if (remove) {
148+
dashboardParams.delete_mark = true;
149+
}
150+
return dashboardParams;
151+
}
152+
153+
export function getDashboardSaveOptions(
154+
queryClient: QueryClient,
155+
remove?: boolean
156+
): UseMutationOptions<ApiDashboard, Error, QueryParams, unknown> {
157+
return {
158+
retry: false,
159+
mutationFn: async (params: QueryParams) => {
160+
const dashboardParams: DashboardInfo = getDashboardSaveFetchParams(params, remove);
161+
const { response, error } = await apiDashboardSaveFetch(dashboardParams);
162+
if (error) {
163+
throw error;
164+
}
165+
if (!response) {
166+
throw new ExtendedError('empty response');
167+
}
168+
return response;
169+
},
170+
onSuccess: (data, params) => {
171+
if (data.data.dashboard.dashboard_id) {
172+
const fetchParams: ApiDashboardGet = { [GET_PARAMS.dashboardID]: data.data.dashboard.dashboard_id.toString() };
173+
if (data.data.dashboard.version != null) {
174+
fetchParams[GET_PARAMS.dashboardApiVersion] = data.data.dashboard.version.toString();
175+
}
176+
queryClient.setQueryData([ApiDashboardEndpoint, fetchParams], data);
177+
}
178+
},
179+
};
180+
}
181+
182+
export async function apiDashboardSave(params: QueryParams, remove?: boolean): Promise<ApiFetchResponse<ApiDashboard>> {
183+
const options = getDashboardSaveOptions(queryClient, remove);
184+
const result: ApiFetchResponse<ApiDashboard> = { ok: false, status: 0 };
185+
try {
186+
result.response = await options.mutationFn?.(params);
187+
if (result.response) {
188+
result.ok = true;
189+
options.onSuccess?.(result.response, params, undefined);
190+
} else {
191+
result.error = new ExtendedError('empty response');
192+
result.status = ExtendedError.ERROR_STATUS_UNKNOWN;
193+
}
194+
} catch (error) {
195+
result.status = ExtendedError.ERROR_STATUS_UNKNOWN;
196+
if (error instanceof ExtendedError) {
197+
result.error = error;
198+
result.status = error.status;
199+
} else if (error instanceof CancelledError) {
200+
result.error = new ExtendedError(error, ExtendedError.ERROR_STATUS_ABORT);
201+
result.status = ExtendedError.ERROR_STATUS_ABORT;
202+
} else {
203+
result.error = new ExtendedError(error);
204+
}
205+
}
206+
return result;
207+
}
208+
209+
export function useApiDashboardSave(remove?: boolean) {
210+
const queryClient = useQueryClient();
211+
return useMutation(getDashboardSaveOptions(queryClient, remove));
212+
}

statshouse-ui/src/api/dashboardsList.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
// License, v. 2.0. If a copy of the MPL was not distributed with this
55
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
66

7-
import { apiFetch } from './api';
7+
import { apiFetch, ApiFetchResponse, ExtendedError } from './api';
8+
import { CancelledError, UndefinedInitialDataOptions, useQuery } from '@tanstack/react-query';
9+
import { queryClient } from '../common/queryClient';
810

911
const ApiDashboardListEndpoint = '/api/dashboards-list';
1012

@@ -28,3 +30,56 @@ export type DashboardShortInfo = {
2830
export async function apiDashboardListFetch(keyRequest?: unknown) {
2931
return await apiFetch<ApiDashboardList>({ url: ApiDashboardListEndpoint, keyRequest });
3032
}
33+
34+
export function getDashboardListOptions<T = ApiDashboardList>(): UndefinedInitialDataOptions<
35+
ApiDashboardList,
36+
ExtendedError,
37+
T,
38+
[string]
39+
> {
40+
return {
41+
queryKey: [ApiDashboardListEndpoint],
42+
queryFn: async ({ signal }) => {
43+
const { response, error } = await apiDashboardListFetch(signal);
44+
if (error) {
45+
throw error;
46+
}
47+
if (!response) {
48+
throw new ExtendedError('empty response');
49+
}
50+
return response;
51+
},
52+
placeholderData: (previousData, previousQuery) => previousData,
53+
};
54+
}
55+
56+
export async function apiDashboardList(): Promise<ApiFetchResponse<ApiDashboardList>> {
57+
const result: ApiFetchResponse<ApiDashboardList> = { ok: false, status: 0 };
58+
try {
59+
result.response = await queryClient.fetchQuery(getDashboardListOptions<ApiDashboardList>());
60+
result.ok = true;
61+
} catch (error) {
62+
result.status = ExtendedError.ERROR_STATUS_UNKNOWN;
63+
if (error instanceof ExtendedError) {
64+
result.error = error;
65+
result.status = error.status;
66+
} else if (error instanceof CancelledError) {
67+
result.error = new ExtendedError(error, ExtendedError.ERROR_STATUS_ABORT);
68+
result.status = ExtendedError.ERROR_STATUS_ABORT;
69+
} else {
70+
result.error = new ExtendedError(error);
71+
}
72+
}
73+
return result;
74+
}
75+
76+
export function useApiDashboardList<T = ApiDashboardList>(select?: (response?: ApiDashboardList) => T) {
77+
const options = getDashboardListOptions();
78+
return useQuery({
79+
...options,
80+
select,
81+
});
82+
}
83+
export function selectApiDashboardList(response?: ApiDashboardList) {
84+
return response?.data.dashboards ?? [];
85+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2022 V Kontakte LLC
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this
5+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
import { useEffect, useState } from 'react';
8+
9+
export function useDebounceValue<T>(value: T, delay: number = 200) {
10+
const [debounceValue, setDebounceValue] = useState<T>(value);
11+
12+
useEffect(() => {
13+
const timout = setTimeout(() => {
14+
setDebounceValue(() => value);
15+
}, delay);
16+
return () => {
17+
clearTimeout(timout);
18+
};
19+
}, [delay, value]);
20+
21+
return debounceValue;
22+
}

statshouse-ui/src/store/dashboardList/dashboardListStore.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
66

77
import { useErrorStore } from '../errors';
8-
import { apiDashboardListFetch, DashboardShortInfo } from '../../api/dashboardsList';
8+
import { apiDashboardList, DashboardShortInfo } from '../../api/dashboardsList';
99
import { createStore } from '../createStore';
10+
import { ExtendedError } from '../../api/api';
1011

1112
export type DashboardListStore = {
1213
list: DashboardShortInfo[];
@@ -27,13 +28,13 @@ export const useDashboardListStore = createStore<DashboardListStore>((setState)
2728
setState((state) => {
2829
state.loading = true;
2930
});
30-
const { response, error } = await apiDashboardListFetch('dashboardListState');
31+
const { response, error } = await apiDashboardList();
3132
if (response) {
3233
setState((state) => {
3334
state.list = response.data.dashboards ?? [];
3435
});
3536
}
36-
if (error) {
37+
if (error && error.status !== ExtendedError.ERROR_STATUS_ABORT) {
3738
errorRemove = useErrorStore.getState().addError(error);
3839
}
3940
setState((state) => {

0 commit comments

Comments
 (0)