mirror of https://github.com/buster-so/buster.git
Merge pull request #314 from buster-so/Payment-required-bug-fix
payment required bug fix
This commit is contained in:
commit
86fa1d8fe8
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "buster_server"
|
name = "buster_server"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "buster_server"
|
default-run = "buster_server"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "buster-cli"
|
name = "buster-cli"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"api_tag": "api/v0.1.5", "api_version": "0.1.5"
|
"api_tag": "api/v0.1.6", "api_version": "0.1.6"
|
||||||
,
|
,
|
||||||
"web_tag": "web/v0.1.5", "web_version": "0.1.5"
|
"web_tag": "web/v0.1.6", "web_version": "0.1.6"
|
||||||
,
|
,
|
||||||
"cli_tag": "cli/v0.1.5", "cli_version": "0.1.5"
|
"cli_tag": "cli/v0.1.6", "cli_version": "0.1.6"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev --turbo",
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { useMemo } from 'react';
|
||||||
import last from 'lodash/last';
|
import last from 'lodash/last';
|
||||||
import { prefetchGetMetricDataClient } from '../metrics/queryRequests';
|
import { prefetchGetMetricDataClient } from '../metrics/queryRequests';
|
||||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
import { useGetUserFavorites } from '../users/queryRequests';
|
import { useGetUserFavorites } from '../users/queryRequestFavorites';
|
||||||
import {
|
import {
|
||||||
useAddAssetToCollection,
|
useAddAssetToCollection,
|
||||||
useRemoveAssetFromCollection
|
useRemoveAssetFromCollection
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsPro
|
||||||
import { create } from 'mutative';
|
import { create } from 'mutative';
|
||||||
import type { BusterCollection } from '@/api/asset_interfaces/collection';
|
import type { BusterCollection } from '@/api/asset_interfaces/collection';
|
||||||
import { RustApiError } from '../errors';
|
import { RustApiError } from '../errors';
|
||||||
import { isQueryStale } from '@/lib';
|
import { hasOrganizationId, isQueryStale } from '@/lib';
|
||||||
|
|
||||||
export const useGetCollectionsList = (
|
export const useGetCollectionsList = (
|
||||||
filters: Omit<Parameters<typeof collectionsGetList>[0], 'page_token' | 'page_size'>,
|
filters: Omit<Parameters<typeof collectionsGetList>[0], 'page_token' | 'page_size'>,
|
||||||
|
@ -51,7 +51,7 @@ export const prefetchGetCollectionsList = async (
|
||||||
) => {
|
) => {
|
||||||
const options = collectionQueryKeys.collectionsGetList(params);
|
const options = collectionQueryKeys.collectionsGetList(params);
|
||||||
const isStale = isQueryStale(options, queryClient);
|
const isStale = isQueryStale(options, queryClient);
|
||||||
if (!isStale) return queryClient;
|
if (!isStale || !hasOrganizationId(queryClient)) return queryClient;
|
||||||
|
|
||||||
const lastQueryKey = options.queryKey[options.queryKey.length - 1];
|
const lastQueryKey = options.queryKey[options.queryKey.length - 1];
|
||||||
const compiledParams = lastQueryKey as Parameters<typeof collectionsGetList>[0];
|
const compiledParams = lastQueryKey as Parameters<typeof collectionsGetList>[0];
|
||||||
|
|
|
@ -43,7 +43,7 @@ import { useGetLatestMetricVersionMemoized } from '../metrics';
|
||||||
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
|
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
|
||||||
import last from 'lodash/last';
|
import last from 'lodash/last';
|
||||||
import { createDashboardFullConfirmModal } from './confirmModals';
|
import { createDashboardFullConfirmModal } from './confirmModals';
|
||||||
import { isQueryStale } from '@/lib';
|
import { hasOrganizationId, isQueryStale } from '@/lib';
|
||||||
|
|
||||||
export const useGetDashboard = <TData = BusterDashboardResponse>(
|
export const useGetDashboard = <TData = BusterDashboardResponse>(
|
||||||
{
|
{
|
||||||
|
@ -751,7 +751,7 @@ export const prefetchGetDashboardsList = async (
|
||||||
) => {
|
) => {
|
||||||
const options = dashboardQueryKeys.dashboardGetList(params);
|
const options = dashboardQueryKeys.dashboardGetList(params);
|
||||||
const isStale = isQueryStale(options, queryClient);
|
const isStale = isQueryStale(options, queryClient);
|
||||||
if (!isStale) return queryClient;
|
if (!isStale || !hasOrganizationId(queryClient)) return queryClient;
|
||||||
|
|
||||||
const lastQueryKey = options.queryKey[options.queryKey.length - 1];
|
const lastQueryKey = options.queryKey[options.queryKey.length - 1];
|
||||||
const compiledParams = lastQueryKey as Parameters<typeof dashboardsGetList>[0];
|
const compiledParams = lastQueryKey as Parameters<typeof dashboardsGetList>[0];
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
import { metricsQueryKeys } from '@/api/query_keys/metric';
|
import { metricsQueryKeys } from '@/api/query_keys/metric';
|
||||||
import { RustApiError } from '../errors';
|
import { RustApiError } from '../errors';
|
||||||
import { isQueryStale } from '@/lib';
|
import { hasOrganizationId, isQueryStale } from '@/lib';
|
||||||
|
import { useUserConfigContextSelector } from '@/context/Users';
|
||||||
|
|
||||||
export const useGetMetricsList = (
|
export const useGetMetricsList = (
|
||||||
params: Omit<Parameters<typeof listMetrics>[0], 'page_token' | 'page_size'>,
|
params: Omit<Parameters<typeof listMetrics>[0], 'page_token' | 'page_size'>,
|
||||||
|
@ -13,17 +14,18 @@ export const useGetMetricsList = (
|
||||||
'queryKey' | 'queryFn' | 'initialData'
|
'queryKey' | 'queryFn' | 'initialData'
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
|
const organizationId = useUserConfigContextSelector((state) => state.userOrganizations?.id);
|
||||||
const compiledParams: Parameters<typeof listMetrics>[0] = useMemo(
|
const compiledParams: Parameters<typeof listMetrics>[0] = useMemo(
|
||||||
() => ({ status: [], ...params, page_token: 0, page_size: 3500 }),
|
() => ({ status: [], ...params, page_token: 0, page_size: 3500 }),
|
||||||
[params]
|
[params]
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryFn = useMemoizedFn(() => listMetrics(compiledParams));
|
const queryFn = useMemoizedFn(() => listMetrics(compiledParams));
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
...metricsQueryKeys.metricsGetList(compiledParams),
|
...metricsQueryKeys.metricsGetList(compiledParams),
|
||||||
queryFn,
|
queryFn,
|
||||||
select: options?.select,
|
select: options?.select,
|
||||||
|
enabled: !!organizationId,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -34,7 +36,7 @@ export const prefetchGetMetricsList = async (
|
||||||
) => {
|
) => {
|
||||||
const options = metricsQueryKeys.metricsGetList(params);
|
const options = metricsQueryKeys.metricsGetList(params);
|
||||||
const isStale = isQueryStale(options, queryClient);
|
const isStale = isQueryStale(options, queryClient);
|
||||||
if (!isStale) return queryClient;
|
if (!isStale || !hasOrganizationId(queryClient)) return queryClient;
|
||||||
|
|
||||||
const lastQueryKey = options.queryKey[options.queryKey.length - 1];
|
const lastQueryKey = options.queryKey[options.queryKey.length - 1];
|
||||||
const compiledParams = lastQueryKey as Parameters<typeof listMetrics>[0];
|
const compiledParams = lastQueryKey as Parameters<typeof listMetrics>[0];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './requests';
|
export * from './requests';
|
||||||
export * from './queryRequests';
|
export * from './queryRequests';
|
||||||
export * from './permissions';
|
export * from './permissions';
|
||||||
|
export * from './queryRequestFavorites';
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||||
|
import {
|
||||||
|
createUserFavorite,
|
||||||
|
deleteUserFavorite,
|
||||||
|
getUserFavorites,
|
||||||
|
getUserFavorites_server,
|
||||||
|
updateUserFavorites
|
||||||
|
} from './requests';
|
||||||
|
import { QueryClient, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useUserConfigContextSelector } from '@/context/Users/BusterUserConfigProvider';
|
||||||
|
import { userQueryKeys } from '@/api/query_keys/users';
|
||||||
|
|
||||||
|
export const useGetUserFavorites = () => {
|
||||||
|
const queryFn = useMemoizedFn(async () => getUserFavorites());
|
||||||
|
const organizationId = useUserConfigContextSelector((state) => state.userOrganizations?.id);
|
||||||
|
return useQuery({
|
||||||
|
...userQueryKeys.favoritesGetList,
|
||||||
|
queryFn,
|
||||||
|
enabled: !!organizationId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prefetchGetUserFavorites = async (queryClientProp?: QueryClient) => {
|
||||||
|
const queryClient = queryClientProp || new QueryClient();
|
||||||
|
await queryClient.prefetchQuery({
|
||||||
|
...userQueryKeys.favoritesGetList,
|
||||||
|
queryFn: () => getUserFavorites_server()
|
||||||
|
});
|
||||||
|
return queryClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAddUserFavorite = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: createUserFavorite,
|
||||||
|
onMutate: (params) => {
|
||||||
|
queryClient.setQueryData(userQueryKeys.favoritesGetList.queryKey, (prev) => {
|
||||||
|
const prevIds = prev?.map((p) => p.id) || [];
|
||||||
|
const dedupedAdd = params.filter((p) => !prevIds.includes(p.id));
|
||||||
|
return [...dedupedAdd, ...(prev || [])];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.setQueryData(userQueryKeys.favoritesGetList.queryKey, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteUserFavorite = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: deleteUserFavorite,
|
||||||
|
onMutate: (id) => {
|
||||||
|
queryClient.setQueryData(userQueryKeys.favoritesGetList.queryKey, (prev) => {
|
||||||
|
return prev?.filter((fav) => !id.includes(fav.id));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.setQueryData(userQueryKeys.favoritesGetList.queryKey, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateUserFavorites = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: updateUserFavorites,
|
||||||
|
onMutate: (params) => {
|
||||||
|
queryClient.setQueryData(userQueryKeys.favoritesGetList.queryKey, (prev) => {
|
||||||
|
return prev?.filter((fav, index) => {
|
||||||
|
const id = fav.id;
|
||||||
|
const favorite = (prev || []).find((f) => f.id === id)!;
|
||||||
|
return { ...favorite, index };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -33,11 +33,9 @@ export const prefetchGetMyUserInfo = async (
|
||||||
queryClientProp?: QueryClient
|
queryClientProp?: QueryClient
|
||||||
) => {
|
) => {
|
||||||
const queryClient = queryClientProp || new QueryClient();
|
const queryClient = queryClientProp || new QueryClient();
|
||||||
const initialData = await getMyUserInfo_server(params);
|
|
||||||
await queryClient.prefetchQuery({
|
await queryClient.prefetchQuery({
|
||||||
...queryKeys.userGetUserMyself,
|
...queryKeys.userGetUserMyself,
|
||||||
queryFn: () => initialData!,
|
queryFn: () => getMyUserInfo_server(params)
|
||||||
initialData
|
|
||||||
});
|
});
|
||||||
return queryClient;
|
return queryClient;
|
||||||
};
|
};
|
||||||
|
@ -78,72 +76,6 @@ export const prefetchGetUser = async (userId: string, queryClientProp?: QueryCli
|
||||||
return queryClient;
|
return queryClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetUserFavorites = () => {
|
|
||||||
const queryFn = useMemoizedFn(async () => getUserFavorites());
|
|
||||||
return useQuery({
|
|
||||||
...queryKeys.favoritesGetList,
|
|
||||||
queryFn
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prefetchGetUserFavorites = async (queryClientProp?: QueryClient) => {
|
|
||||||
const queryClient = queryClientProp || new QueryClient();
|
|
||||||
await queryClient.prefetchQuery({
|
|
||||||
...queryKeys.favoritesGetList,
|
|
||||||
queryFn: () => getUserFavorites_server()
|
|
||||||
});
|
|
||||||
return queryClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAddUserFavorite = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: createUserFavorite,
|
|
||||||
onMutate: (params) => {
|
|
||||||
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => {
|
|
||||||
const prevIds = prev?.map((p) => p.id) || [];
|
|
||||||
const dedupedAdd = params.filter((p) => !prevIds.includes(p.id));
|
|
||||||
return [...dedupedAdd, ...(prev || [])];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: (data) => {
|
|
||||||
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeleteUserFavorite = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: deleteUserFavorite,
|
|
||||||
onMutate: (id) => {
|
|
||||||
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => {
|
|
||||||
return prev?.filter((fav) => !id.includes(fav.id));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: (data) => {
|
|
||||||
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpdateUserFavorites = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: updateUserFavorites,
|
|
||||||
onMutate: (params) => {
|
|
||||||
queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => {
|
|
||||||
return prev?.filter((fav, index) => {
|
|
||||||
const id = fav.id;
|
|
||||||
const favorite = (prev || []).find((f) => f.id === id)!;
|
|
||||||
return { ...favorite, index };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetUserList = (params: Parameters<typeof getUserList>[0]) => {
|
export const useGetUserList = (params: Parameters<typeof getUserList>[0]) => {
|
||||||
const queryFn = useMemoizedFn(() => getUserList(params));
|
const queryFn = useMemoizedFn(() => getUserList(params));
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,12 @@ export const getMyUserInfo_server = async ({
|
||||||
jwtToken
|
jwtToken
|
||||||
}: {
|
}: {
|
||||||
jwtToken: string | undefined;
|
jwtToken: string | undefined;
|
||||||
}): Promise<BusterUserResponse | undefined> => {
|
}): Promise<BusterUserResponse | null> => {
|
||||||
if (!jwtToken) {
|
if (!jwtToken) {
|
||||||
try {
|
//If Anonymous user, it will fail, so we catch the error and return undefined
|
||||||
//If Anonymous user, it will fail, so we catch the error and return undefined
|
return await serverFetch<BusterUserResponse>(`/users`, {
|
||||||
const res = await serverFetch<BusterUserResponse>(`/users`, {
|
method: 'GET'
|
||||||
method: 'GET'
|
});
|
||||||
});
|
|
||||||
return res;
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//use fetch instead of serverFetch because...
|
//use fetch instead of serverFetch because...
|
||||||
|
@ -37,16 +32,17 @@ export const getMyUserInfo_server = async ({
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${jwtToken}`
|
Authorization: `Bearer ${jwtToken}`
|
||||||
}
|
}
|
||||||
})
|
}).then(async (response) => {
|
||||||
.then((response) => {
|
if (!response.ok) {
|
||||||
if (!response.ok) {
|
const errorData = await response.json().catch(() => null);
|
||||||
return undefined;
|
throw {
|
||||||
}
|
status: response.status,
|
||||||
return response.json();
|
statusText: response.statusText,
|
||||||
})
|
...errorData
|
||||||
.catch((error) => {
|
};
|
||||||
return undefined;
|
}
|
||||||
});
|
return response.json();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUser = async ({ userId }: { userId: string }) => {
|
export const getUser = async ({ userId }: { userId: string }) => {
|
||||||
|
|
|
@ -19,7 +19,7 @@ const favoritesGetList = queryOptions<BusterUserFavorite[]>({
|
||||||
initialDataUpdatedAt: 0
|
initialDataUpdatedAt: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const userGetUserMyself = queryOptions<BusterUserResponse>({
|
const userGetUserMyself = queryOptions<BusterUserResponse | null>({
|
||||||
queryKey: ['myself'] as const,
|
queryKey: ['myself'] as const,
|
||||||
staleTime: 1000 * 60 * 60 // 1 hour
|
staleTime: 1000 * 60 * 60 // 1 hour
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,9 @@ import { AppProviders } from '@/context/AppProviders';
|
||||||
import { headers } from 'next/headers';
|
import { headers } from 'next/headers';
|
||||||
import { queryKeys } from '@/api/query_keys';
|
import { queryKeys } from '@/api/query_keys';
|
||||||
|
|
||||||
|
const newUserRoute = createBusterRoute({ route: BusterRoutes.NEW_USER });
|
||||||
|
const loginRoute = createBusterRoute({ route: BusterRoutes.AUTH_LOGIN });
|
||||||
|
|
||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
children
|
children
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
|
@ -32,8 +35,6 @@ export default async function Layout({
|
||||||
}
|
}
|
||||||
|
|
||||||
const userInfo = queryClient.getQueryData(queryKeys.userGetUserMyself.queryKey);
|
const userInfo = queryClient.getQueryData(queryKeys.userGetUserMyself.queryKey);
|
||||||
const newUserRoute = createBusterRoute({ route: BusterRoutes.NEW_USER });
|
|
||||||
const loginRoute = createBusterRoute({ route: BusterRoutes.AUTH_LOGIN });
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(supabaseContext.user?.is_anonymous && pathname !== loginRoute) ||
|
(supabaseContext.user?.is_anonymous && pathname !== loginRoute) ||
|
||||||
|
|
|
@ -102,20 +102,5 @@ describe('formatBarAndLineDataLabel', () => {
|
||||||
// 25 out of series total (50) = 50% (since second dataset is hidden)
|
// 25 out of series total (50) = 50% (since second dataset is hidden)
|
||||||
expect(result).toBe('50%');
|
expect(result).toBe('50%');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should ignore trendline datasets when counting multiple datasets', () => {
|
|
||||||
const mockContext = createMockContext([
|
|
||||||
baseDataset,
|
|
||||||
{ ...baseDataset, isTrendline: true }
|
|
||||||
]) as Context;
|
|
||||||
|
|
||||||
const result = formatBarAndLineDataLabel(25, mockContext, 'data-label', {
|
|
||||||
style: 'number',
|
|
||||||
columnType: 'number'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 25 out of series total (50) = 50% (since second dataset is a trendline)
|
|
||||||
expect(result).toBe('50%');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -122,51 +122,6 @@ describe('getLegendItems', () => {
|
||||||
expect(result[1].type).toBe(ChartType.Line);
|
expect(result[1].type).toBe(ChartType.Line);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter out hidden and trendline datasets', () => {
|
|
||||||
const mockChartRef = {
|
|
||||||
current: {
|
|
||||||
config: { type: ChartType.Bar },
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Visible Data',
|
|
||||||
data: [10, 20],
|
|
||||||
yAxisKey: 'value',
|
|
||||||
hidden: false,
|
|
||||||
isTrendline: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Hidden Data',
|
|
||||||
data: [15, 25],
|
|
||||||
yAxisKey: 'value',
|
|
||||||
hidden: true,
|
|
||||||
isTrendline: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Trendline Data',
|
|
||||||
data: [12, 22],
|
|
||||||
yAxisKey: 'value',
|
|
||||||
hidden: false,
|
|
||||||
isTrendline: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
} as unknown as Chart
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = getLegendItems({
|
|
||||||
chartRef: mockChartRef,
|
|
||||||
colors: mockColors,
|
|
||||||
inactiveDatasets: {},
|
|
||||||
selectedChartType: ChartType.Bar,
|
|
||||||
columnLabelFormats: defaultColumnLabelFormats,
|
|
||||||
columnSettings: defaultColumnSettings
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(result[0].id).toBe('Visible Data');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle scatter plot visualization in combo charts', () => {
|
it('should handle scatter plot visualization in combo charts', () => {
|
||||||
const mockChartRef = {
|
const mockChartRef = {
|
||||||
current: {
|
current: {
|
||||||
|
|
|
@ -4,9 +4,6 @@ import type { ChartSpecificOptionsProps } from './interfaces';
|
||||||
import type { ChartData } from 'chart.js';
|
import type { ChartData } from 'chart.js';
|
||||||
|
|
||||||
jest.mock('@/lib/colors');
|
jest.mock('@/lib/colors');
|
||||||
jest.mock('@tanstack/react-query', () => ({
|
|
||||||
isServer: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockChartData: ChartData = {
|
const mockChartData: ChartData = {
|
||||||
labels: ['Test'],
|
labels: ['Test'],
|
||||||
|
|
|
@ -61,7 +61,8 @@ describe('scatterSeriesBuilder_data', () => {
|
||||||
barShowTotalAtTop: false,
|
barShowTotalAtTop: false,
|
||||||
barGroupType: null,
|
barGroupType: null,
|
||||||
yAxisKeys: ['metric1'],
|
yAxisKeys: ['metric1'],
|
||||||
y2AxisKeys: []
|
y2AxisKeys: [],
|
||||||
|
trendlines: []
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should create basic scatter dataset without size options', () => {
|
it('should create basic scatter dataset without size options', () => {
|
||||||
|
@ -141,77 +142,8 @@ describe('scatterSeriesBuilder_labels', () => {
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should process date labels directly when x-axis is date type', () => {
|
|
||||||
const dateString1 = '2023-01-01';
|
|
||||||
const dateString2 = '2023-01-02';
|
|
||||||
|
|
||||||
const props: LabelBuilderProps = {
|
|
||||||
trendlineSeries: [{ yAxisKey: 'y1' } as any],
|
|
||||||
datasetOptions: {
|
|
||||||
ticks: [[dateString1], [dateString2]],
|
|
||||||
datasets: [{ dataKey: 'y1', data: [10, 20] } as any],
|
|
||||||
ticksKey: [{ key: 'x', value: 'X Axis' }]
|
|
||||||
} as any,
|
|
||||||
columnLabelFormats: {
|
|
||||||
x: {
|
|
||||||
columnType: 'date',
|
|
||||||
style: 'date'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xAxisKeys: ['x'],
|
|
||||||
sizeKey: [],
|
|
||||||
columnSettings: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = scatterSeriesBuilder_labels(props);
|
|
||||||
expect(result).toEqual([
|
|
||||||
createDayjsDate(dateString1).toDate(),
|
|
||||||
createDayjsDate(dateString2).toDate()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should collect all ticks without deduplication', () => {
|
|
||||||
const props: LabelBuilderProps = {
|
|
||||||
trendlineSeries: [{ yAxisKey: 'y1' } as any, { yAxisKey: 'y2' } as any],
|
|
||||||
datasetOptions: {
|
|
||||||
ticks: [],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
dataKey: 'y1',
|
|
||||||
data: [10, 20],
|
|
||||||
ticksForScatter: [
|
|
||||||
[1, 'A'],
|
|
||||||
[2, 'B']
|
|
||||||
]
|
|
||||||
} as any,
|
|
||||||
{
|
|
||||||
dataKey: 'y2',
|
|
||||||
data: [30, 40, 50],
|
|
||||||
ticksForScatter: [
|
|
||||||
[1, 'A'],
|
|
||||||
[3, 'C'],
|
|
||||||
[5, 'D']
|
|
||||||
]
|
|
||||||
} as any
|
|
||||||
],
|
|
||||||
ticksKey: [{ key: 'x', value: 'X Axis' }]
|
|
||||||
} as any,
|
|
||||||
columnLabelFormats: {
|
|
||||||
x: DEFAULT_COLUMN_LABEL_FORMAT
|
|
||||||
},
|
|
||||||
xAxisKeys: ['x'],
|
|
||||||
sizeKey: [],
|
|
||||||
columnSettings: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = scatterSeriesBuilder_labels(props);
|
|
||||||
// Should include duplicate [1, 'A'] from both datasets
|
|
||||||
expect(result).toEqual([1, 'A', 1, 'A', 2, 'B', 3, 'C', 5, 'D']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return undefined when no relevant datasets are found', () => {
|
test('should return undefined when no relevant datasets are found', () => {
|
||||||
const props: LabelBuilderProps = {
|
const props: LabelBuilderProps = {
|
||||||
trendlineSeries: [{ yAxisKey: 'y1' } as any],
|
|
||||||
datasetOptions: {
|
datasetOptions: {
|
||||||
ticks: [],
|
ticks: [],
|
||||||
datasets: [
|
datasets: [
|
||||||
|
|
|
@ -127,6 +127,7 @@ export type LabelBuilderProps = {
|
||||||
xAxisKeys: ChartEncodes['x'];
|
xAxisKeys: ChartEncodes['x'];
|
||||||
sizeKey: ScatterAxis['size'];
|
sizeKey: ScatterAxis['size'];
|
||||||
columnSettings: NonNullable<BusterChartProps['columnSettings']>;
|
columnSettings: NonNullable<BusterChartProps['columnSettings']>;
|
||||||
|
trendlineSeries?: Array<{ yAxisKey: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const labelsBuilderRecord: Record<
|
const labelsBuilderRecord: Record<
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { useSupabaseContext } from '../../Supabase';
|
||||||
import { createContext, useContextSelector } from 'use-context-selector';
|
import { createContext, useContextSelector } from 'use-context-selector';
|
||||||
import { SupabaseContextReturnType } from '../../Supabase';
|
import { SupabaseContextReturnType } from '../../Supabase';
|
||||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
|
import { useUserConfigContextSelector } from '@/context/Users';
|
||||||
|
|
||||||
const BUSTER_WS_URL = `${process.env.NEXT_PUBLIC_WEB_SOCKET_URL}/api/v1/ws`;
|
const BUSTER_WS_URL = `${process.env.NEXT_PUBLIC_WEB_SOCKET_URL}/api/v1/ws`;
|
||||||
|
|
||||||
|
@ -36,11 +37,13 @@ interface BusterSocket {
|
||||||
const useBusterWebSocketHook = ({
|
const useBusterWebSocketHook = ({
|
||||||
socketURL,
|
socketURL,
|
||||||
accessToken,
|
accessToken,
|
||||||
checkTokenValidity
|
checkTokenValidity,
|
||||||
|
organizationId
|
||||||
}: {
|
}: {
|
||||||
socketURL: string;
|
socketURL: string;
|
||||||
accessToken: string | undefined;
|
accessToken: string | undefined;
|
||||||
checkTokenValidity: SupabaseContextReturnType['checkTokenValidity'];
|
checkTokenValidity: SupabaseContextReturnType['checkTokenValidity'];
|
||||||
|
organizationId: string | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const { openErrorNotification } = useBusterNotifications();
|
const { openErrorNotification } = useBusterNotifications();
|
||||||
|
|
||||||
|
@ -77,7 +80,7 @@ const useBusterWebSocketHook = ({
|
||||||
const { sendJSONMessage, connectionStatus } = useWebSocket({
|
const { sendJSONMessage, connectionStatus } = useWebSocket({
|
||||||
url: socketURL,
|
url: socketURL,
|
||||||
onMessage,
|
onMessage,
|
||||||
canConnect: !!accessToken,
|
canConnect: !!accessToken && !!organizationId,
|
||||||
checkTokenValidity
|
checkTokenValidity
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -198,11 +201,13 @@ export const BusterWebSocketProvider: React.FC<{
|
||||||
}> = React.memo(({ children }) => {
|
}> = React.memo(({ children }) => {
|
||||||
const accessToken = useSupabaseContext((state) => state.accessToken);
|
const accessToken = useSupabaseContext((state) => state.accessToken);
|
||||||
const checkTokenValidity = useSupabaseContext((state) => state.checkTokenValidity);
|
const checkTokenValidity = useSupabaseContext((state) => state.checkTokenValidity);
|
||||||
|
const organizationId = useUserConfigContextSelector((state) => state.userOrganizations?.id);
|
||||||
|
|
||||||
const busterSocketHook = useBusterWebSocketHook({
|
const busterSocketHook = useBusterWebSocketHook({
|
||||||
socketURL: BUSTER_WS_URL,
|
socketURL: BUSTER_WS_URL,
|
||||||
accessToken,
|
accessToken,
|
||||||
checkTokenValidity
|
checkTokenValidity,
|
||||||
|
organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return <BusterWebSocket.Provider value={busterSocketHook}>{children}</BusterWebSocket.Provider>;
|
return <BusterWebSocket.Provider value={busterSocketHook}>{children}</BusterWebSocket.Provider>;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { RustApiError } from '@/api/buster_rest/errors';
|
import { RustApiError } from '@/api/buster_rest/errors';
|
||||||
|
import { userQueryKeys } from '@/api/query_keys/users';
|
||||||
import { QueryClient, queryOptions } from '@tanstack/react-query';
|
import { QueryClient, queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
export const isQueryStale = (
|
export const isQueryStale = (
|
||||||
|
@ -15,3 +16,9 @@ export const isQueryStale = (
|
||||||
|
|
||||||
return isStale;
|
return isStale;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasOrganizationId = (queryClient: QueryClient): boolean => {
|
||||||
|
const organizationId = queryClient.getQueryData(userQueryKeys.userGetUserMyself.queryKey)
|
||||||
|
?.organizations?.[0]?.id;
|
||||||
|
return !!organizationId;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue