mirror of https://github.com/buster-so/buster.git
move currency around to dictionary
This commit is contained in:
parent
ec2a3f6238
commit
95e0c8d7b5
|
@ -1,4 +1,4 @@
|
|||
import type { Currency } from '@buster/server-shared/currency';
|
||||
import type { Currency } from '@buster/server-shared/dictionary';
|
||||
|
||||
const codeToFlag = {
|
||||
AED: '🇦🇪',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { CurrencyResponse } from '@buster/server-shared/currency';
|
||||
import type { CurrencyResponse } from '@buster/server-shared/dictionary';
|
||||
import { Hono } from 'hono';
|
||||
import { CURRENCIES_MAP } from './config';
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './queryRequests';
|
|
@ -1,26 +0,0 @@
|
|||
import { queryKeys } from '@/api/query_keys';
|
||||
import { QueryClient, usePrefetchQuery, useQuery } from '@tanstack/react-query';
|
||||
import { mainApiV2 } from '../instances';
|
||||
import { Currency } from '@buster/server-shared/currency';
|
||||
|
||||
export const useGetCurrencies = () => {
|
||||
return useQuery({
|
||||
...queryKeys.getCurrencies,
|
||||
queryFn: async () => {
|
||||
return mainApiV2.get<Currency[]>('/dictionaries/currency').then(async (res) => res.data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const prefetchGetCurrencies = async (queryClientProp?: QueryClient) => {
|
||||
const queryClient = queryClientProp || new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
...queryKeys.getCurrencies,
|
||||
queryFn: async () => {
|
||||
return mainApiV2.get<Currency[]>('/dictionaries/currency').then(async (res) => res.data);
|
||||
}
|
||||
});
|
||||
|
||||
return queryClient;
|
||||
};
|
|
@ -1,11 +1,10 @@
|
|||
import { useQuery, QueryClient } from '@tanstack/react-query';
|
||||
import { getColorThemes } from './requests';
|
||||
import { dictionariesQueryKeys } from '../../query_keys/dictionaries';
|
||||
import { getColorThemes, getCurrencies } from './requests';
|
||||
import { dictionariesQueryKeys } from '@/api/query_keys/dictionaries';
|
||||
|
||||
export const useColorDictionaryThemes = () => {
|
||||
return useQuery({
|
||||
...dictionariesQueryKeys.colorThemes,
|
||||
initialData: [],
|
||||
queryFn: getColorThemes
|
||||
});
|
||||
};
|
||||
|
@ -20,3 +19,21 @@ export const prefetchColorThemes = async (queryClientProp?: QueryClient) => {
|
|||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
export const useGetCurrencies = () => {
|
||||
return useQuery({
|
||||
...dictionariesQueryKeys.getCurrencies,
|
||||
queryFn: getCurrencies
|
||||
});
|
||||
};
|
||||
|
||||
export const prefetchGetCurrencies = async (queryClientProp?: QueryClient) => {
|
||||
const queryClient = queryClientProp || new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
...dictionariesQueryKeys.getCurrencies,
|
||||
queryFn: getCurrencies
|
||||
});
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import type { ColorThemeDictionariesResponse } from '@buster/server-shared/dictionary';
|
||||
import { mainApiV2 } from '../instances';
|
||||
import type { CurrencyResponse } from '@buster/server-shared/dictionary';
|
||||
|
||||
export const getColorThemes = async () => {
|
||||
return await mainApiV2
|
||||
.get<ColorThemeDictionariesResponse>('/dictionaries/color-themes')
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
export const getCurrencies = async () => {
|
||||
return await mainApiV2.get<CurrencyResponse>('/dictionaries/currency').then((res) => res.data);
|
||||
};
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { CurrencyResponse } from '@buster/server-shared/currency';
|
||||
|
||||
export const getCurrencies = queryOptions<CurrencyResponse>({
|
||||
queryKey: ['nextjs', 'list', 'currencies'],
|
||||
initialData: [],
|
||||
initialDataUpdatedAt: 0,
|
||||
staleTime: 1000 * 60 * 60 * 24 * 7 //7 days
|
||||
});
|
||||
|
||||
export const currencyQueryKeys = {
|
||||
getCurrencies
|
||||
};
|
|
@ -1,11 +1,22 @@
|
|||
import type { CurrencyResponse } from '@buster/server-shared/dictionary';
|
||||
import type { ColorThemeDictionariesResponse } from '@buster/server-shared/dictionary';
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
export const colorThemes = queryOptions<ColorThemeDictionariesResponse>({
|
||||
queryKey: ['color-themes', 'list'],
|
||||
staleTime: 60 * 1000 * 60 * 24 * 3 // 3 days
|
||||
queryKey: ['dictionaries', 'color-themes', 'list'] as const,
|
||||
initialData: [],
|
||||
initialDataUpdatedAt: 0,
|
||||
staleTime: 1000 * 60 * 60 * 24 * 7 // 7 days
|
||||
});
|
||||
|
||||
export const getCurrencies = queryOptions<CurrencyResponse>({
|
||||
queryKey: ['dictionaries', 'currencies', 'list'] as const,
|
||||
initialData: [],
|
||||
initialDataUpdatedAt: 0,
|
||||
staleTime: 1000 * 60 * 60 * 24 * 7 // 7 days
|
||||
});
|
||||
|
||||
export const dictionariesQueryKeys = {
|
||||
colorThemes
|
||||
colorThemes,
|
||||
getCurrencies
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@ import { datasetGroupQueryKeys } from './dataset_groups';
|
|||
import { datasetQueryKeys } from './datasets';
|
||||
import { datasourceQueryKeys } from './datasources';
|
||||
import { metricsQueryKeys } from './metric';
|
||||
import { currencyQueryKeys } from './currency';
|
||||
import { permissionGroupQueryKeys } from './permission_groups';
|
||||
import { searchQueryKeys } from './search';
|
||||
import { termsQueryKeys } from './terms';
|
||||
|
@ -26,7 +25,6 @@ export const queryKeys = {
|
|||
...datasourceQueryKeys,
|
||||
...datasetGroupQueryKeys,
|
||||
...permissionGroupQueryKeys,
|
||||
...currencyQueryKeys,
|
||||
...securityQueryKeys,
|
||||
...slackQueryKeys,
|
||||
...dictionariesQueryKeys
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React from 'react';
|
||||
import { DefaultThemeSelectorBase } from './DefaultThemeSelectorBase';
|
||||
import { useGetMyUserInfo } from '@/api/buster_rest/users/queryRequests';
|
||||
import { useColorDictionaryThemes } from '@/api/buster_rest/dictionaries';
|
||||
import { StatusCard } from '@/components/ui/card/StatusCard';
|
||||
import { CircleSpinnerLoader } from '../../../ui/loaders';
|
||||
import { useThemeOperations } from '@/context-hooks/useThemeOperations';
|
||||
|
@ -9,11 +7,8 @@ import { useGetPalettes } from '@/context-hooks/usePalettes';
|
|||
|
||||
export const DefaultThemeSelector = React.memo(
|
||||
({ className, themeListClassName }: { className?: string; themeListClassName?: string }) => {
|
||||
const { data: userData } = useGetMyUserInfo();
|
||||
|
||||
const {
|
||||
isErrorDictionaryPalettes,
|
||||
isFetchedDictionaryPalettes,
|
||||
organizationPalettes,
|
||||
dictionaryPalettes,
|
||||
selectedPaletteId
|
||||
|
@ -22,7 +17,12 @@ export const DefaultThemeSelector = React.memo(
|
|||
const { onCreateCustomTheme, onDeleteCustomTheme, onModifyCustomTheme, onSelectTheme } =
|
||||
useThemeOperations();
|
||||
|
||||
if (!isFetchedDictionaryPalettes) return <CircleSpinnerLoader />;
|
||||
if (dictionaryPalettes.length === 0 && !isErrorDictionaryPalettes)
|
||||
return (
|
||||
<div className="flex h-24 w-full min-w-24 items-center justify-center">
|
||||
<CircleSpinnerLoader size={24} />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isErrorDictionaryPalettes)
|
||||
return (
|
||||
|
|
|
@ -40,10 +40,6 @@ const PickButton = React.memo(() => {
|
|||
|
||||
const hasDefaultPalette = !!defaultPalette;
|
||||
|
||||
useMount(() => {
|
||||
prefetchColorThemes();
|
||||
});
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className="p-0"
|
||||
|
|
|
@ -3,6 +3,7 @@ import type { ColorPalette } from '@buster/server-shared/organization';
|
|||
import { useMemo } from 'react';
|
||||
import { useColorDictionaryThemes } from '../api/buster_rest/dictionaries';
|
||||
import { DEFAULT_CHART_THEME } from '@buster/server-shared/metrics';
|
||||
import { useGetCurrencies } from '../api/buster_rest/dictionaries';
|
||||
|
||||
const useGetOrganizationPalettes = () => {
|
||||
const { data: userData } = useGetMyUserInfo();
|
||||
|
@ -27,11 +28,11 @@ const useGetOrganizationPalettes = () => {
|
|||
|
||||
export const useGetPalettes = () => {
|
||||
const { organizationPalettes, selectedPaletteId } = useGetOrganizationPalettes();
|
||||
const {
|
||||
data: dictionaryPalettes,
|
||||
isFetched: isFetchedDictionaryPalettes,
|
||||
isError: isErrorDictionaryPalettes
|
||||
} = useColorDictionaryThemes();
|
||||
const { data: dictionaryPalettes, isError: isErrorDictionaryPalettes } =
|
||||
useColorDictionaryThemes();
|
||||
const { data: currencies } = useGetCurrencies();
|
||||
|
||||
console.log(currencies, dictionaryPalettes);
|
||||
|
||||
return useMemo(() => {
|
||||
const allPalettes = [...dictionaryPalettes, ...organizationPalettes];
|
||||
|
@ -43,10 +44,10 @@ export const useGetPalettes = () => {
|
|||
dictionaryPalettes,
|
||||
selectedPaletteId,
|
||||
defaultPalette,
|
||||
isFetchedDictionaryPalettes,
|
||||
|
||||
isErrorDictionaryPalettes
|
||||
};
|
||||
}, [dictionaryPalettes, organizationPalettes, selectedPaletteId, isFetchedDictionaryPalettes]);
|
||||
}, [dictionaryPalettes, organizationPalettes, selectedPaletteId]);
|
||||
};
|
||||
|
||||
export const useSelectedColorPalette = (colors: string[] | undefined | null): string[] => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import type { ColumnLabelFormat } from '@buster/server-shared/metrics';
|
||||
import { useGetCurrencies } from '@/api/buster_rest/currency';
|
||||
import { useGetCurrencies } from '@/api/buster_rest/dictionaries';
|
||||
import { Select, type SelectItem } from '@/components/ui/select';
|
||||
import { Text } from '@/components/ui/typography';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
|||
import type { ChartConfigProps } from '@buster/server-shared/metrics';
|
||||
import { type ChartEncodes, type ColumnSettings } from '@buster/server-shared/metrics';
|
||||
import type { ColumnLabelFormat } from '@buster/server-shared/metrics';
|
||||
import { prefetchGetCurrencies } from '@/api/buster_rest/currency';
|
||||
import { prefetchGetCurrencies } from '@/api/buster_rest/dictionaries';
|
||||
import { ErrorBoundary } from '@/components/ui/error';
|
||||
import { Text } from '@/components/ui/typography';
|
||||
import { useUpdateMetricChart } from '@/context/Metrics';
|
||||
|
|
|
@ -36,10 +36,6 @@
|
|||
"types": "./dist/dashboards/index.d.ts",
|
||||
"default": "./dist/dashboards/index.js"
|
||||
},
|
||||
"./currency": {
|
||||
"types": "./dist/currency/index.d.ts",
|
||||
"default": "./dist/currency/index.js"
|
||||
},
|
||||
"./slack": {
|
||||
"types": "./dist/slack/index.d.ts",
|
||||
"default": "./dist/slack/index.js"
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { CurrencySchema } from './currency.types';
|
||||
|
||||
describe('CurrencySchema', () => {
|
||||
it('should parse valid currency object', () => {
|
||||
const validCurrency = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.safeParse(validCurrency);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.code).toBe('USD');
|
||||
expect(result.data.description).toBe('United States Dollar');
|
||||
expect(result.data.flag).toBe('🇺🇸');
|
||||
}
|
||||
});
|
||||
|
||||
it('should require all fields', () => {
|
||||
const incompleteObjects = [
|
||||
{
|
||||
// missing code
|
||||
description: 'Euro',
|
||||
flag: '🇪🇺',
|
||||
},
|
||||
{
|
||||
code: 'EUR',
|
||||
// missing description
|
||||
flag: '🇪🇺',
|
||||
},
|
||||
{
|
||||
code: 'EUR',
|
||||
description: 'Euro',
|
||||
// missing flag
|
||||
},
|
||||
];
|
||||
|
||||
for (const obj of incompleteObjects) {
|
||||
const result = CurrencySchema.safeParse(obj);
|
||||
expect(result.success).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate that all fields are strings', () => {
|
||||
const invalidTypes = [
|
||||
{
|
||||
code: 123, // should be string
|
||||
description: 'Invalid Code',
|
||||
flag: '🇺🇸',
|
||||
},
|
||||
{
|
||||
code: 'USD',
|
||||
description: true, // should be string
|
||||
flag: '🇺🇸',
|
||||
},
|
||||
{
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: null, // should be string
|
||||
},
|
||||
];
|
||||
|
||||
for (const obj of invalidTypes) {
|
||||
const result = CurrencySchema.safeParse(obj);
|
||||
expect(result.success).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle various currency examples', () => {
|
||||
const currencies = [
|
||||
{
|
||||
code: 'EUR',
|
||||
description: 'Euro',
|
||||
flag: '🇪🇺',
|
||||
},
|
||||
{
|
||||
code: 'GBP',
|
||||
description: 'British Pound Sterling',
|
||||
flag: '🇬🇧',
|
||||
},
|
||||
{
|
||||
code: 'JPY',
|
||||
description: 'Japanese Yen',
|
||||
flag: '🇯🇵',
|
||||
},
|
||||
{
|
||||
code: 'CAD',
|
||||
description: 'Canadian Dollar',
|
||||
flag: '🇨🇦',
|
||||
},
|
||||
{
|
||||
code: 'AUD',
|
||||
description: 'Australian Dollar',
|
||||
flag: '🇦🇺',
|
||||
},
|
||||
];
|
||||
|
||||
for (const currency of currencies) {
|
||||
const result = CurrencySchema.safeParse(currency);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.code).toBe(currency.code);
|
||||
expect(result.data.description).toBe(currency.description);
|
||||
expect(result.data.flag).toBe(currency.flag);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty strings', () => {
|
||||
const currencyWithEmptyStrings = {
|
||||
code: '',
|
||||
description: '',
|
||||
flag: '',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.safeParse(currencyWithEmptyStrings);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.code).toBe('');
|
||||
expect(result.data.description).toBe('');
|
||||
expect(result.data.flag).toBe('');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle long strings', () => {
|
||||
const currencyWithLongStrings = {
|
||||
code: 'VERYLONGCURRENCYCODE',
|
||||
description:
|
||||
'This is a very long description for a currency that might not exist in real life but should still be valid according to our schema',
|
||||
flag: '🏳️🌈🏳️⚧️🇺🇳', // Multiple flag emojis
|
||||
};
|
||||
|
||||
const result = CurrencySchema.safeParse(currencyWithLongStrings);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.code).toBe('VERYLONGCURRENCYCODE');
|
||||
expect(result.data.description).toBe(
|
||||
'This is a very long description for a currency that might not exist in real life but should still be valid according to our schema'
|
||||
);
|
||||
expect(result.data.flag).toBe('🏳️🌈🏳️⚧️🇺🇳');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle special characters and unicode', () => {
|
||||
const currencyWithSpecialChars = {
|
||||
code: 'BTC-₿',
|
||||
description: 'Bitcoin (₿) - Digital Currency with special chars: @#$%^&*()[]{}',
|
||||
flag: '₿',
|
||||
};
|
||||
|
||||
const result = CurrencySchema.safeParse(currencyWithSpecialChars);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.code).toBe('BTC-₿');
|
||||
expect(result.data.description).toBe(
|
||||
'Bitcoin (₿) - Digital Currency with special chars: @#$%^&*()[]{}'
|
||||
);
|
||||
expect(result.data.flag).toBe('₿');
|
||||
}
|
||||
});
|
||||
|
||||
it('should reject additional properties', () => {
|
||||
const currencyWithExtraProps = {
|
||||
code: 'USD',
|
||||
description: 'United States Dollar',
|
||||
flag: '🇺🇸',
|
||||
extraProperty: 'This should not be allowed',
|
||||
anotherExtra: 123,
|
||||
};
|
||||
|
||||
const result = CurrencySchema.safeParse(currencyWithExtraProps);
|
||||
// Zod by default allows additional properties unless .strict() is used
|
||||
// Since the schema doesn't use .strict(), extra properties are allowed but ignored
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
if (result.success) {
|
||||
// Extra properties should not be included in the result
|
||||
expect('extraProperty' in result.data).toBe(false);
|
||||
expect('anotherExtra' in result.data).toBe(false);
|
||||
expect(result.data.code).toBe('USD');
|
||||
expect(result.data.description).toBe('United States Dollar');
|
||||
expect(result.data.flag).toBe('🇺🇸');
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,2 +0,0 @@
|
|||
export * from './currency.types';
|
||||
export * from './responses';
|
|
@ -1,6 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
import { CurrencySchema } from './currency.types';
|
||||
|
||||
export const CurrencyResponseSchema = z.array(CurrencySchema);
|
||||
|
||||
export type CurrencyResponse = z.infer<typeof CurrencyResponseSchema>;
|
|
@ -7,3 +7,7 @@ export const CurrencySchema = z.object({
|
|||
});
|
||||
|
||||
export type Currency = z.infer<typeof CurrencySchema>;
|
||||
|
||||
export const CurrencyResponseSchema = z.array(CurrencySchema);
|
||||
|
||||
export type CurrencyResponse = z.infer<typeof CurrencyResponseSchema>;
|
|
@ -1 +1,2 @@
|
|||
export * from './color-themes';
|
||||
export * from './currency';
|
||||
|
|
Loading…
Reference in New Issue