mirror of https://github.com/buster-so/buster.git
objects are not doing equality very well
This commit is contained in:
parent
1d272b36ec
commit
634e5f0460
|
@ -2,7 +2,8 @@ import { DashboardLayout } from '@/layouts/DashboardLayout';
|
|||
|
||||
export default async function Layout({
|
||||
children,
|
||||
params
|
||||
params,
|
||||
...rest
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ dashboardId: string }>;
|
||||
|
|
|
@ -2,10 +2,12 @@ import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
|
|||
|
||||
export default async function MetricLayout({
|
||||
children,
|
||||
params
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ metricId: string }>;
|
||||
searchParams: Promise<{ metric_version_number?: number }>;
|
||||
}) {
|
||||
const { metricId } = await params;
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const useGetDashboardMemoized = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const getDashboardMemoized = useMemoizedFn((dashboardId: string) => {
|
||||
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId);
|
||||
const data = queryClient.getQueryData(options.queryKey);
|
||||
return data;
|
||||
});
|
||||
return getDashboardMemoized;
|
||||
};
|
|
@ -4,6 +4,7 @@ import { useMemoizedFn } from '@/hooks';
|
|||
import { metricsQueryKeys } from '@/api/query_keys/metric';
|
||||
import { useGetMetric } from '@/api/buster_rest/metrics';
|
||||
import { compareObjectsByKeys } from '@/lib/objects';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useIsMetricChanged = ({ metricId }: { metricId: string }) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
@ -29,9 +30,8 @@ export const useIsMetricChanged = ({ metricId }: { metricId: string }) => {
|
|||
refetchCurrentMetric();
|
||||
});
|
||||
|
||||
return {
|
||||
onResetMetricToOriginal,
|
||||
isMetricChanged:
|
||||
const isMetricChanged = useMemo(
|
||||
() =>
|
||||
!originalMetric ||
|
||||
!currentMetric ||
|
||||
!compareObjectsByKeys(originalMetric, currentMetric, [
|
||||
|
@ -39,6 +39,12 @@ export const useIsMetricChanged = ({ metricId }: { metricId: string }) => {
|
|||
'description',
|
||||
'chart_config',
|
||||
'file'
|
||||
])
|
||||
]),
|
||||
[originalMetric, currentMetric]
|
||||
);
|
||||
|
||||
return {
|
||||
onResetMetricToOriginal,
|
||||
isMetricChanged
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useDashboardContentStore } from '@/context/Dashboards';
|
|||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { canEdit } from '@/lib/share';
|
||||
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
|
||||
import { StatusCard } from '@/components/ui/card/StatusCard';
|
||||
|
||||
export const DashboardViewDashboardController: React.FC<{
|
||||
dashboardId: string;
|
||||
|
@ -19,7 +20,12 @@ export const DashboardViewDashboardController: React.FC<{
|
|||
readOnly?: boolean;
|
||||
}> = ({ dashboardId, chatId, readOnly: readOnlyProp = false }) => {
|
||||
const isVersionHistoryMode = useChatLayoutContextSelector((x) => x.isVersionHistoryMode);
|
||||
const { data: dashboardResponse } = useGetDashboard({ id: dashboardId });
|
||||
const {
|
||||
data: dashboardResponse,
|
||||
isFetched,
|
||||
isError,
|
||||
error
|
||||
} = useGetDashboard({ id: dashboardId });
|
||||
const { mutateAsync: onUpdateDashboard } = useUpdateDashboard();
|
||||
const { mutateAsync: onUpdateDashboardConfig } = useUpdateDashboardConfig();
|
||||
const onOpenAddContentModal = useDashboardContentStore((x) => x.onOpenAddContentModal);
|
||||
|
@ -28,6 +34,18 @@ export const DashboardViewDashboardController: React.FC<{
|
|||
const dashboard = dashboardResponse?.dashboard;
|
||||
const readOnly = readOnlyProp || !canEdit(dashboardResponse?.permission) || isVersionHistoryMode;
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="p-10">
|
||||
<StatusCard variant="danger" title="Error" message={error?.message || ''} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFetched) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="flex h-full flex-col space-y-3 p-10">
|
||||
|
|
|
@ -26,10 +26,10 @@ export const ChatResponseMessage_File: React.FC<ChatResponseMessageProps> = Reac
|
|||
|
||||
const { file_type, id } = responseMessage;
|
||||
|
||||
const { isSelectedFile, isLatestVersion } = useGetIsSelectedFile({ responseMessage });
|
||||
const { isSelectedFile } = useGetIsSelectedFile({ responseMessage });
|
||||
const onSetSelectedFile = useChatLayoutContextSelector((x) => x.onSetSelectedFile);
|
||||
|
||||
const href = useGetFileHref({ isLatestVersion, responseMessage, isSelectedFile, chatId });
|
||||
const href = useGetFileHref({ responseMessage, isSelectedFile, chatId });
|
||||
|
||||
const onLinkClick = useMemoizedFn(() => {
|
||||
if (isSelectedFile) {
|
||||
|
|
|
@ -7,12 +7,10 @@ import { useMemo } from 'react';
|
|||
export const useGetFileHref = ({
|
||||
responseMessage,
|
||||
isSelectedFile,
|
||||
isLatestVersion,
|
||||
chatId
|
||||
}: {
|
||||
responseMessage: BusterChatResponseMessage_file;
|
||||
isSelectedFile: boolean;
|
||||
isLatestVersion: boolean;
|
||||
chatId: string;
|
||||
}) => {
|
||||
const { file_type, id, version_number } = responseMessage;
|
||||
|
@ -28,36 +26,20 @@ export const useGetFileHref = ({
|
|||
}
|
||||
|
||||
if (file_type === 'metric') {
|
||||
if (isLatestVersion) {
|
||||
return createBusterRoute({
|
||||
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART,
|
||||
chatId,
|
||||
metricId: id
|
||||
});
|
||||
}
|
||||
|
||||
return createBusterRoute({
|
||||
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_VERSION_NUMBER,
|
||||
chatId,
|
||||
metricId: id,
|
||||
versionNumber: version_number.toString()
|
||||
versionNumber: version_number
|
||||
});
|
||||
}
|
||||
|
||||
if (file_type === 'dashboard') {
|
||||
if (isLatestVersion) {
|
||||
return createBusterRoute({
|
||||
route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID,
|
||||
chatId,
|
||||
dashboardId: id
|
||||
});
|
||||
}
|
||||
|
||||
return createBusterRoute({
|
||||
route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID_VERSION_NUMBER,
|
||||
chatId,
|
||||
dashboardId: id,
|
||||
versionNumber: version_number.toString()
|
||||
versionNumber: version_number
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BusterChatResponseMessage_file } from '@/api/asset_interfaces';
|
||||
import { queryKeys } from '@/api/query_keys';
|
||||
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
|
||||
import { useChatIndividualContextSelector } from '@/layouts/ChatLayout/ChatContext';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
|
@ -9,36 +10,30 @@ export const useGetIsSelectedFile = ({
|
|||
responseMessage: Pick<BusterChatResponseMessage_file, 'file_type' | 'id' | 'version_number'>;
|
||||
}): {
|
||||
isSelectedFile: boolean;
|
||||
isLatestVersion: boolean;
|
||||
} => {
|
||||
const queryClient = useQueryClient();
|
||||
const isSelectedFile = useChatIndividualContextSelector(
|
||||
(x) => x.selectedFileId === responseMessage.id
|
||||
);
|
||||
|
||||
const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber);
|
||||
const dashboardVersionNumber = useChatLayoutContextSelector((x) => x.dashboardVersionNumber);
|
||||
const versionNumber = responseMessage.version_number;
|
||||
|
||||
switch (responseMessage.file_type) {
|
||||
case 'metric': {
|
||||
const options = queryKeys.metricsGetMetric(responseMessage.id);
|
||||
const data = queryClient.getQueryData(options.queryKey);
|
||||
const lastVersion = data?.versions[data.versions.length - 1];
|
||||
const isLatestVersion = lastVersion?.version_number === versionNumber;
|
||||
return { isSelectedFile: isSelectedFile && isLatestVersion, isLatestVersion };
|
||||
const isSelectedVersion = versionNumber.toString() === metricVersionNumber;
|
||||
return { isSelectedFile: isSelectedFile && isSelectedVersion };
|
||||
}
|
||||
case 'dashboard': {
|
||||
const options = queryKeys.dashboardGetDashboard(responseMessage.id);
|
||||
const versions = queryClient.getQueryData(options.queryKey)?.versions;
|
||||
const lastVersion = versions?.[versions.length - 1];
|
||||
const isLatestVersion = lastVersion?.version_number === versionNumber;
|
||||
return { isSelectedFile: isSelectedFile && isLatestVersion, isLatestVersion };
|
||||
const isSelectedVersion = versionNumber.toString() === dashboardVersionNumber;
|
||||
return { isSelectedFile: isSelectedFile && isSelectedVersion };
|
||||
}
|
||||
case 'reasoning': {
|
||||
return { isSelectedFile: false, isLatestVersion: false };
|
||||
return { isSelectedFile: false };
|
||||
}
|
||||
default: {
|
||||
const exhaustiveCheck: never = responseMessage.file_type;
|
||||
return { isSelectedFile: false, isLatestVersion: false };
|
||||
return { isSelectedFile: false };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useGetChat } from '@/api/buster_rest/chats';
|
|||
import { useQueries } from '@tanstack/react-query';
|
||||
import { queryKeys } from '@/api/query_keys';
|
||||
import { IBusterChatMessage } from '@/api/asset_interfaces/chat';
|
||||
import { useChatLayoutContextSelector } from '..';
|
||||
|
||||
const useChatIndividualContext = ({
|
||||
chatId,
|
||||
|
@ -84,30 +85,22 @@ const IndividualChatContext = createContext<ReturnType<typeof useChatIndividualC
|
|||
{} as ReturnType<typeof useChatIndividualContext>
|
||||
);
|
||||
|
||||
export const ChatContextProvider = React.memo(
|
||||
({
|
||||
export const ChatContextProvider = React.memo(({ children }: PropsWithChildren<{}>) => {
|
||||
const chatId = useChatLayoutContextSelector((x) => x.chatId);
|
||||
const selectedFile = useChatLayoutContextSelector((x) => x.selectedFile);
|
||||
const onSetSelectedFile = useChatLayoutContextSelector((x) => x.onSetSelectedFile);
|
||||
const useChatContextValue = useChatIndividualContext({
|
||||
chatId,
|
||||
selectedFile,
|
||||
onSetSelectedFile,
|
||||
children
|
||||
}: PropsWithChildren<{
|
||||
chatId: string | undefined;
|
||||
selectedFile: SelectedFile | null;
|
||||
onSetSelectedFile: (file: SelectedFile) => void;
|
||||
}>) => {
|
||||
const useChatContextValue = useChatIndividualContext({
|
||||
chatId,
|
||||
selectedFile,
|
||||
onSetSelectedFile
|
||||
});
|
||||
onSetSelectedFile
|
||||
});
|
||||
|
||||
return (
|
||||
<IndividualChatContext.Provider value={useChatContextValue}>
|
||||
{children}
|
||||
</IndividualChatContext.Provider>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<IndividualChatContext.Provider value={useChatContextValue}>
|
||||
{children}
|
||||
</IndividualChatContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
ChatContextProvider.displayName = 'ChatContextProvider';
|
||||
|
||||
|
|
|
@ -4,7 +4,11 @@ import { useGetChatMessageMemoized, useGetChatMessage } from '@/api/buster_rest/
|
|||
import type { SelectedFile } from '../interfaces';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import findLast from 'lodash/findLast';
|
||||
import { BusterChatResponseMessage_file } from '@/api/asset_interfaces/chat';
|
||||
import { BusterChatResponseMessage_file, FileType } from '@/api/asset_interfaces/chat';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useChatLayoutContextSelector } from '../ChatLayoutContext';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
|
||||
export const useAutoChangeLayout = ({
|
||||
lastMessageId,
|
||||
|
@ -17,17 +21,21 @@ export const useAutoChangeLayout = ({
|
|||
selectedFileId: string | undefined;
|
||||
chatId: string | undefined;
|
||||
}) => {
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const previousLastMessageId = useRef<string | null>(null);
|
||||
const reasoningMessagesLength = useGetChatMessage(
|
||||
lastMessageId,
|
||||
(x) => x?.reasoning_message_ids?.length || 0
|
||||
);
|
||||
const getChatMessageMemoized = useGetChatMessageMemoized();
|
||||
const getFileHref = useGetFileHref({ chatId });
|
||||
|
||||
const isCompletedStream = useGetChatMessage(lastMessageId, (x) => x?.isCompletedStream);
|
||||
|
||||
const hasReasoning = !!reasoningMessagesLength;
|
||||
|
||||
//change the page to reasoning file if we get a reasoning message
|
||||
//change the page to the file if we get a file
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isCompletedStream &&
|
||||
|
@ -49,9 +57,76 @@ export const useAutoChangeLayout = ({
|
|||
| BusterChatResponseMessage_file
|
||||
| undefined;
|
||||
|
||||
if (lastFile && lastFileId && selectedFileId !== lastFileId) {
|
||||
onSetSelectedFile({ id: lastFileId, type: lastFile.file_type });
|
||||
if (lastFileId && lastFile) {
|
||||
if (selectedFileId !== lastFileId) {
|
||||
}
|
||||
|
||||
const href = getFileHref({
|
||||
fileId: lastFileId,
|
||||
fileType: lastFile.file_type,
|
||||
currentFile: lastFile
|
||||
});
|
||||
|
||||
console.log(href);
|
||||
if (href) {
|
||||
// onChangePage(href);
|
||||
|
||||
onSetSelectedFile({
|
||||
id: lastFileId,
|
||||
type: lastFile.file_type,
|
||||
versionNumber: lastFile.version_number
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isCompletedStream, chatId, hasReasoning, lastMessageId]);
|
||||
};
|
||||
|
||||
const useGetFileHref = ({ chatId }: { chatId: string | undefined }) => {
|
||||
const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber);
|
||||
const dashboardVersionNumber = useChatLayoutContextSelector((x) => x.dashboardVersionNumber);
|
||||
|
||||
const getFileHref = useMemoizedFn(
|
||||
({
|
||||
fileId,
|
||||
fileType,
|
||||
currentFile
|
||||
}: {
|
||||
fileId: string;
|
||||
fileType: FileType;
|
||||
currentFile: BusterChatResponseMessage_file | undefined;
|
||||
}) => {
|
||||
if (!currentFile || !chatId || metricVersionNumber || dashboardVersionNumber) return false;
|
||||
|
||||
if (fileType === 'metric') {
|
||||
if (metricVersionNumber) return false;
|
||||
return createBusterRoute({
|
||||
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_VERSION_NUMBER,
|
||||
chatId: chatId,
|
||||
metricId: fileId,
|
||||
versionNumber: currentFile.version_number
|
||||
});
|
||||
}
|
||||
|
||||
if (fileType === 'dashboard') {
|
||||
if (dashboardVersionNumber) return false;
|
||||
return createBusterRoute({
|
||||
route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID_VERSION_NUMBER,
|
||||
chatId: chatId,
|
||||
dashboardId: fileId,
|
||||
versionNumber: currentFile.version_number
|
||||
});
|
||||
}
|
||||
|
||||
if (fileType === 'reasoning') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const exhaustiveCheck: never = fileType;
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
return getFileHref;
|
||||
};
|
||||
|
|
|
@ -7,15 +7,12 @@ import { FileContainer } from '../FileContainer';
|
|||
import { ChatLayoutContextProvider, useChatLayoutContext } from '../ChatLayoutContext';
|
||||
import { ChatContextProvider } from '../ChatContext/ChatContext';
|
||||
import { DEFAULT_CHAT_OPTION_SIDEBAR_SIZE } from '../ChatLayoutContext/config';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
interface ChatSplitterProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ChatLayout: React.FC<ChatSplitterProps> = ({ children }) => {
|
||||
const params = useParams();
|
||||
|
||||
const appSplitterRef = useRef<AppSplitterRef>(null);
|
||||
const chatLayoutProps = useChatLayoutContext({ appSplitterRef });
|
||||
const { selectedLayout, selectedFile, onSetSelectedFile, chatId } = chatLayoutProps;
|
||||
|
@ -28,10 +25,7 @@ export const ChatLayout: React.FC<ChatSplitterProps> = ({ children }) => {
|
|||
|
||||
return (
|
||||
<ChatLayoutContextProvider chatLayoutProps={chatLayoutProps}>
|
||||
<ChatContextProvider
|
||||
chatId={chatId}
|
||||
selectedFile={selectedFile}
|
||||
onSetSelectedFile={onSetSelectedFile}>
|
||||
<ChatContextProvider>
|
||||
<AppSplitter
|
||||
ref={appSplitterRef}
|
||||
leftChildren={useMemo(
|
||||
|
|
|
@ -15,7 +15,6 @@ export const useGetChatParams = () => {
|
|||
};
|
||||
const searchParams = useSearchParams();
|
||||
const currentRoute = useAppLayoutContextSelector((state) => state.currentRoute);
|
||||
|
||||
const metricVersionNumber = searchParams.get('metric_version_number');
|
||||
const dashboardVersionNumber = searchParams.get('dashboard_version_number');
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import { createSelectedFile } from './createSelectedFile';
|
|||
import type { useGetChatParams } from '../useGetChatParams';
|
||||
import type { AppSplitterRef } from '@/components/ui/layouts/AppSplitter';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { createChatAssetRoute } from '../helpers';
|
||||
|
||||
export const useSelectedFile = ({
|
||||
|
@ -19,7 +18,7 @@ export const useSelectedFile = ({
|
|||
appSplitterRef: React.RefObject<AppSplitterRef | null>;
|
||||
chatParams: ReturnType<typeof useGetChatParams>;
|
||||
}) => {
|
||||
const { metricVersionNumber, dashboardVersionNumber } = chatParams;
|
||||
const { metricVersionNumber, dashboardVersionNumber, chatId } = chatParams;
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
|
||||
const selectedFile: SelectedFile | null = useMemo(() => {
|
||||
|
@ -27,10 +26,11 @@ export const useSelectedFile = ({
|
|||
}, [chatParams]);
|
||||
|
||||
const isVersionHistoryMode = useMemo(() => {
|
||||
if (chatId) return false; // we don't need to show the version history mode if we are in a chat
|
||||
if (selectedFile?.type === 'metric') return !!metricVersionNumber;
|
||||
if (selectedFile?.type === 'dashboard') return !!dashboardVersionNumber;
|
||||
return false;
|
||||
}, [selectedFile?.type, metricVersionNumber, dashboardVersionNumber]);
|
||||
}, [selectedFile?.type, metricVersionNumber, dashboardVersionNumber, chatId]);
|
||||
|
||||
/**
|
||||
* @description Opens the splitter if the file is not already open.
|
||||
|
|
|
@ -20,6 +20,7 @@ export const FileContainerHeader: React.FC = React.memo(() => {
|
|||
const onCollapseFileClick = useChatLayoutContextSelector((state) => state.onCollapseFileClick);
|
||||
const selectedLayout = useChatLayoutContextSelector((x) => x.selectedLayout);
|
||||
const showCollapseButton = selectedLayout === 'both';
|
||||
|
||||
const chatId = useChatLayoutContextSelector((x) => x.chatId);
|
||||
const SelectedFileSegment = React.useMemo(
|
||||
() =>
|
||||
|
|
|
@ -4,10 +4,10 @@ import React from 'react';
|
|||
import { AppAssetCheckLayout } from '../AppAssetCheckLayout';
|
||||
import { DashboardLayoutContainer } from './DashboardLayoutContainer';
|
||||
|
||||
export const DashboardLayout: React.FC<{ dashboardId: string; children: React.ReactNode }> = ({
|
||||
dashboardId,
|
||||
children
|
||||
}) => {
|
||||
export const DashboardLayout: React.FC<{
|
||||
dashboardId: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ dashboardId, children }) => {
|
||||
return (
|
||||
<AppAssetCheckLayout assetId={dashboardId} type="dashboard">
|
||||
<DashboardLayoutContainer dashboardId={dashboardId}>{children}</DashboardLayoutContainer>
|
||||
|
|
|
@ -0,0 +1,572 @@
|
|||
import { compareObjectsByKeys } from './objects';
|
||||
|
||||
describe('compareObjectsByKeys', () => {
|
||||
// Test basic equality
|
||||
it('should return true when objects are equal for specified keys', () => {
|
||||
const obj1 = { name: 'John', age: 30, city: 'New York' };
|
||||
const obj2 = { name: 'John', age: 30, country: 'USA' };
|
||||
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['name', 'age'])).toBe(true);
|
||||
});
|
||||
|
||||
// Test inequality
|
||||
it('should return false when objects are not equal for specified keys', () => {
|
||||
const obj1 = { name: 'John', age: 30 };
|
||||
const obj2 = { name: 'Jane', age: 25 };
|
||||
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['name', 'age'])).toBe(false);
|
||||
});
|
||||
|
||||
// Test with nested objects
|
||||
it('should correctly compare nested objects', () => {
|
||||
const obj1 = { user: { id: 1, name: 'John' }, status: 'active' };
|
||||
const obj2 = { user: { id: 1, name: 'John' }, status: 'inactive' };
|
||||
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['user'])).toBe(true);
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['status'])).toBe(false);
|
||||
});
|
||||
|
||||
// Test with null values
|
||||
it('should handle null values correctly', () => {
|
||||
const obj1 = { name: 'John', age: null };
|
||||
const obj2 = { name: 'John', age: null };
|
||||
const obj3 = { name: 'John', age: 30 };
|
||||
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['name', 'age'])).toBe(true);
|
||||
expect(compareObjectsByKeys(obj1, obj3, ['name', 'age'])).toBe(false);
|
||||
});
|
||||
|
||||
// Test with arrays
|
||||
it('should compare arrays correctly', () => {
|
||||
const obj1 = { tags: [1, 2, 3], name: 'John' };
|
||||
const obj2 = { tags: [1, 2, 3], name: 'Jane' };
|
||||
const obj3 = { tags: [1, 2, 4], name: 'John' };
|
||||
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['tags'])).toBe(true);
|
||||
expect(compareObjectsByKeys(obj1, obj3, ['tags'])).toBe(false);
|
||||
});
|
||||
|
||||
// Test error cases
|
||||
it('should throw error for null/undefined objects', () => {
|
||||
const obj1 = { name: 'John' };
|
||||
|
||||
expect(() => compareObjectsByKeys(null as any, obj1, ['name'])).toThrow(
|
||||
'Both objects must be defined'
|
||||
);
|
||||
expect(() => compareObjectsByKeys(obj1, undefined as any, ['name'])).toThrow(
|
||||
'Both objects must be defined'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for empty keys array', () => {
|
||||
const obj1 = { name: 'John' };
|
||||
const obj2 = { name: 'John' };
|
||||
|
||||
expect(() => compareObjectsByKeys(obj1, obj2, [])).toThrow('Keys array must be non-empty');
|
||||
});
|
||||
|
||||
// Test with different object shapes
|
||||
it('should handle objects with different shapes', () => {
|
||||
const obj1 = { name: 'John', age: 30 };
|
||||
const obj2 = { name: 'John', title: 'Developer' };
|
||||
|
||||
expect(compareObjectsByKeys(obj1, obj2, ['name'])).toBe(true);
|
||||
});
|
||||
|
||||
it('complex test with debug logging', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log');
|
||||
|
||||
const object1 = {
|
||||
colors: [
|
||||
'#B399FD',
|
||||
'#FC8497',
|
||||
'#FBBC30',
|
||||
'#279EFF',
|
||||
'#E83562',
|
||||
'#41F8FF',
|
||||
'#F3864F',
|
||||
'#C82184',
|
||||
'#31FCB4',
|
||||
'#E83562'
|
||||
]
|
||||
};
|
||||
|
||||
const object2 = {
|
||||
colors: [
|
||||
'#B399FD',
|
||||
'#FC8497',
|
||||
'#FBBC30',
|
||||
'#279EFF',
|
||||
'#E83562',
|
||||
'#41F8FF',
|
||||
'#F3864F',
|
||||
'#C82184',
|
||||
'#31FCB4',
|
||||
'#E83562'
|
||||
]
|
||||
};
|
||||
|
||||
const result = compareObjectsByKeys(object1, object2, ['colors']);
|
||||
|
||||
// Log what was actually compared
|
||||
if (!result) {
|
||||
console.log('Comparison failed. Console output:', consoleSpy.mock.calls);
|
||||
}
|
||||
|
||||
expect(result).toBe(true);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('complex test 2 with debug logging', () => {
|
||||
const object1 = {
|
||||
id: '84cbc2f3-a4e5-52c5-a784-c9cb14b55eab',
|
||||
type: 'metric',
|
||||
name: 'Orders vs Revenue Trend',
|
||||
version_number: 1,
|
||||
description:
|
||||
'Combines the monthly count of orders and sales revenue to show their trends side by side.',
|
||||
file_name: 'Orders vs Revenue Trend',
|
||||
time_frame: 'All time',
|
||||
datasets: [
|
||||
{
|
||||
name: 'entity_sales_order',
|
||||
id: '9fa460b4-1410-4e74-aa34-eb79027cd59c'
|
||||
}
|
||||
],
|
||||
data_source_id: 'cc3ef3bc-44ec-4a43-8dc4-681cae5c996a',
|
||||
error: null,
|
||||
chart_config: {
|
||||
colors: [
|
||||
'#B399FD',
|
||||
'#FC8497',
|
||||
'#FBBC30',
|
||||
'#279EFF',
|
||||
'#E83562',
|
||||
'#41F8FF',
|
||||
'#F3864F',
|
||||
'#C82184',
|
||||
'#31FCB4',
|
||||
'#E83562'
|
||||
],
|
||||
selectedChartType: 'combo',
|
||||
yAxisShowAxisLabel: true,
|
||||
yAxisShowAxisTitle: true,
|
||||
yAxisAxisTitle: null,
|
||||
yAxisStartAxisAtZero: null,
|
||||
yAxisScaleType: 'linear',
|
||||
y2AxisShowAxisLabel: true,
|
||||
y2AxisAxisTitle: null,
|
||||
y2AxisShowAxisTitle: true,
|
||||
y2AxisStartAxisAtZero: true,
|
||||
y2AxisScaleType: 'linear',
|
||||
xAxisTimeInterval: null,
|
||||
xAxisShowAxisLabel: true,
|
||||
xAxisShowAxisTitle: true,
|
||||
xAxisAxisTitle: null,
|
||||
xAxisLabelRotation: 'auto',
|
||||
xAxisDataZoom: false,
|
||||
categoryAxisTitle: null,
|
||||
showLegend: null,
|
||||
gridLines: true,
|
||||
goalLines: [],
|
||||
trendlines: [],
|
||||
showLegendHeadline: false,
|
||||
disableTooltip: false,
|
||||
barAndLineAxis: {
|
||||
x: ['month'],
|
||||
y: ['order_count'],
|
||||
category: [],
|
||||
tooltip: null
|
||||
},
|
||||
scatterAxis: {
|
||||
x: ['order_count'],
|
||||
y: ['total_revenue'],
|
||||
size: [],
|
||||
tooltip: null
|
||||
},
|
||||
comboChartAxis: {
|
||||
x: ['month'],
|
||||
y: ['order_count', 'total_revenue'],
|
||||
y2: [],
|
||||
tooltip: null
|
||||
},
|
||||
pieChartAxis: {
|
||||
x: ['month'],
|
||||
y: ['order_count'],
|
||||
tooltip: null
|
||||
},
|
||||
lineGroupType: null,
|
||||
scatterDotSize: [3, 15],
|
||||
barSortBy: [],
|
||||
barLayout: 'vertical',
|
||||
barGroupType: 'group',
|
||||
barShowTotalAtTop: false,
|
||||
pieShowInnerLabel: true,
|
||||
pieInnerLabelAggregate: 'sum',
|
||||
pieInnerLabelTitle: 'Total',
|
||||
pieLabelPosition: null,
|
||||
pieDonutWidth: 40,
|
||||
pieMinimumSlicePercentage: 0,
|
||||
pieDisplayLabelAs: 'number',
|
||||
metricColumnId: 'order_count',
|
||||
metricValueAggregate: 'sum',
|
||||
metricHeader: null,
|
||||
metricSubHeader: null,
|
||||
metricValueLabel: null,
|
||||
tableColumnOrder: null,
|
||||
tableColumnWidths: null,
|
||||
tableHeaderBackgroundColor: null,
|
||||
tableHeaderFontColor: null,
|
||||
tableColumnFontColor: null,
|
||||
columnSettings: {
|
||||
month: {
|
||||
showDataLabels: false,
|
||||
columnVisualization: 'bar',
|
||||
lineWidth: 2,
|
||||
lineStyle: 'line',
|
||||
lineType: 'normal',
|
||||
lineSymbolSize: 0,
|
||||
barRoundness: 8,
|
||||
showDataLabelsAsPercentage: false
|
||||
},
|
||||
order_count: {
|
||||
showDataLabels: false,
|
||||
columnVisualization: 'bar',
|
||||
lineWidth: 2,
|
||||
lineStyle: 'line',
|
||||
lineType: 'normal',
|
||||
lineSymbolSize: 0,
|
||||
barRoundness: 8,
|
||||
showDataLabelsAsPercentage: false
|
||||
},
|
||||
total_revenue: {
|
||||
showDataLabels: false,
|
||||
columnVisualization: 'bar',
|
||||
lineWidth: 2,
|
||||
lineStyle: 'line',
|
||||
lineType: 'normal',
|
||||
lineSymbolSize: 0,
|
||||
barRoundness: 8,
|
||||
showDataLabelsAsPercentage: false
|
||||
}
|
||||
},
|
||||
columnLabelFormats: {
|
||||
month: {
|
||||
style: 'date',
|
||||
compactNumbers: false,
|
||||
columnType: 'date',
|
||||
displayName: '',
|
||||
numberSeparatorStyle: ',',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
currency: 'USD',
|
||||
convertNumberTo: null,
|
||||
dateFormat: 'MMM YYYY',
|
||||
useRelativeTime: false,
|
||||
isUTC: false,
|
||||
multiplier: 1,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
replaceMissingDataWith: null,
|
||||
makeLabelHumanReadable: true
|
||||
},
|
||||
order_count: {
|
||||
style: 'number',
|
||||
compactNumbers: false,
|
||||
columnType: 'number',
|
||||
displayName: '',
|
||||
numberSeparatorStyle: ',',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
currency: 'USD',
|
||||
convertNumberTo: null,
|
||||
dateFormat: 'auto',
|
||||
useRelativeTime: false,
|
||||
isUTC: false,
|
||||
multiplier: 1,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
replaceMissingDataWith: 0,
|
||||
makeLabelHumanReadable: true
|
||||
},
|
||||
total_revenue: {
|
||||
style: 'currency',
|
||||
compactNumbers: false,
|
||||
columnType: 'number',
|
||||
displayName: '',
|
||||
numberSeparatorStyle: ',',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
currency: 'USD',
|
||||
convertNumberTo: null,
|
||||
dateFormat: 'auto',
|
||||
useRelativeTime: false,
|
||||
isUTC: false,
|
||||
multiplier: 1,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
replaceMissingDataWith: 0,
|
||||
makeLabelHumanReadable: true
|
||||
}
|
||||
}
|
||||
},
|
||||
data_metadata: {
|
||||
column_count: 3,
|
||||
row_count: 15,
|
||||
column_metadata: [
|
||||
{
|
||||
name: 'month',
|
||||
min_value: '2022-02-01',
|
||||
max_value: '2023-04-01',
|
||||
unique_values: 15,
|
||||
simple_type: 'date',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'order_count',
|
||||
min_value: 368,
|
||||
max_value: 3216,
|
||||
unique_values: 15,
|
||||
simple_type: 'number',
|
||||
type: 'int8'
|
||||
},
|
||||
{
|
||||
name: 'total_revenue',
|
||||
min_value: 539906.14,
|
||||
max_value: 4113794.05,
|
||||
unique_values: 15,
|
||||
simple_type: 'number',
|
||||
type: 'float8'
|
||||
}
|
||||
]
|
||||
},
|
||||
status: 'notRequested',
|
||||
evaluation_score: null,
|
||||
evaluation_summary: '',
|
||||
file: 'name: Orders vs Revenue Trend\ndescription: Combines the monthly count of orders and sales revenue to show their trends side by side.\ntimeFrame: All time\nsql: "SELECT \\n DATE_TRUNC(\'month\', order_date)::date AS month, \\n COUNT(sales_order_id) AS order_count, \\n SUM(line_total) AS total_revenue\\nFROM sem.entity_sales_order\\nGROUP BY 1\\nORDER BY 1\\n"\nchartConfig:\n selectedChartType: combo\n columnLabelFormats:\n month:\n columnType: date\n style: date\n dateFormat: MMM YYYY\n order_count:\n columnType: number\n style: number\n minimumFractionDigits: 0\n total_revenue:\n columnType: number\n style: currency\n minimumFractionDigits: 2\n currency: USD\n comboChartAxis:\n x:\n - month\n y:\n - order_count\n - total_revenue\ndatasetIds:\n- 9fa460b4-1410-4e74-aa34-eb79027cd59c\n',
|
||||
created_at: '2025-04-08T16:31:43.755232Z',
|
||||
updated_at: '2025-04-08T16:31:43.755237Z',
|
||||
sent_by_id: 'c2dd64cd-f7f3-4884-bc91-d46ae431901e',
|
||||
sent_by_name: '',
|
||||
sent_by_avatar_url: null,
|
||||
code: null,
|
||||
dashboards: [
|
||||
{
|
||||
id: '2057b640-3e98-56e0-a9af-093875a94f17',
|
||||
name: 'Sales Dashboard'
|
||||
}
|
||||
],
|
||||
collections: [],
|
||||
versions: [
|
||||
{
|
||||
version_number: 1,
|
||||
updated_at: '2025-04-08T16:31:43.755253Z'
|
||||
}
|
||||
],
|
||||
permission: 'owner',
|
||||
sql: "SELECT \n DATE_TRUNC('month', order_date)::date AS month, \n COUNT(sales_order_id) AS order_count, \n SUM(line_total) AS total_revenue\nFROM sem.entity_sales_order\nGROUP BY 1\nORDER BY 1\n",
|
||||
individual_permissions: [
|
||||
{
|
||||
email: 'chad@buster.so',
|
||||
role: 'owner',
|
||||
name: 'Chad 🇹🇩'
|
||||
}
|
||||
],
|
||||
public_expiry_date: null,
|
||||
public_enabled_by: null,
|
||||
publicly_accessible: false,
|
||||
public_password: null
|
||||
} as const;
|
||||
|
||||
const object2 = {
|
||||
name: 'Orders vs Revenue Trend',
|
||||
description:
|
||||
'Combines the monthly count of orders and sales revenue to show their trends side by side.',
|
||||
chart_config: {
|
||||
colors: [
|
||||
'#B399FD',
|
||||
'#FC8497',
|
||||
'#FBBC30',
|
||||
'#279EFF',
|
||||
'#E83562',
|
||||
'#41F8FF',
|
||||
'#F3864F',
|
||||
'#C82184',
|
||||
'#31FCB4',
|
||||
'#E83562'
|
||||
],
|
||||
selectedChartType: 'combo',
|
||||
yAxisShowAxisLabel: true,
|
||||
yAxisShowAxisTitle: true,
|
||||
yAxisAxisTitle: null,
|
||||
yAxisStartAxisAtZero: null,
|
||||
yAxisScaleType: 'linear',
|
||||
y2AxisShowAxisLabel: true,
|
||||
y2AxisAxisTitle: null,
|
||||
y2AxisShowAxisTitle: true,
|
||||
y2AxisStartAxisAtZero: true,
|
||||
y2AxisScaleType: 'linear',
|
||||
xAxisTimeInterval: null,
|
||||
xAxisShowAxisLabel: true,
|
||||
xAxisShowAxisTitle: true,
|
||||
xAxisAxisTitle: null,
|
||||
xAxisLabelRotation: 'auto',
|
||||
xAxisDataZoom: false,
|
||||
categoryAxisTitle: null,
|
||||
showLegend: null,
|
||||
gridLines: true,
|
||||
goalLines: [],
|
||||
trendlines: [],
|
||||
showLegendHeadline: false,
|
||||
disableTooltip: false,
|
||||
barAndLineAxis: {
|
||||
x: ['month'],
|
||||
y: ['order_count'],
|
||||
category: [],
|
||||
tooltip: null
|
||||
},
|
||||
scatterAxis: {
|
||||
x: ['order_count'],
|
||||
y: ['total_revenue'],
|
||||
size: [],
|
||||
tooltip: null
|
||||
},
|
||||
comboChartAxis: {
|
||||
x: ['month'],
|
||||
y: ['order_count', 'total_revenue'],
|
||||
y2: [],
|
||||
tooltip: null
|
||||
},
|
||||
pieChartAxis: {
|
||||
x: ['month'],
|
||||
y: ['order_count'],
|
||||
tooltip: null
|
||||
},
|
||||
lineGroupType: null,
|
||||
scatterDotSize: [3, 15],
|
||||
barSortBy: [],
|
||||
barLayout: 'vertical',
|
||||
barGroupType: 'group',
|
||||
barShowTotalAtTop: false,
|
||||
pieShowInnerLabel: true,
|
||||
pieInnerLabelAggregate: 'sum',
|
||||
pieInnerLabelTitle: 'Total',
|
||||
pieLabelPosition: null,
|
||||
pieDonutWidth: 40,
|
||||
pieMinimumSlicePercentage: 0,
|
||||
pieDisplayLabelAs: 'number',
|
||||
metricColumnId: 'order_count',
|
||||
metricValueAggregate: 'sum',
|
||||
metricHeader: null,
|
||||
metricSubHeader: null,
|
||||
metricValueLabel: null,
|
||||
tableColumnOrder: null,
|
||||
tableColumnWidths: null,
|
||||
tableHeaderBackgroundColor: null,
|
||||
tableHeaderFontColor: null,
|
||||
tableColumnFontColor: null,
|
||||
columnSettings: {
|
||||
month: {
|
||||
showDataLabels: false,
|
||||
columnVisualization: 'bar',
|
||||
lineWidth: 2,
|
||||
lineStyle: 'line',
|
||||
lineType: 'normal',
|
||||
lineSymbolSize: 0,
|
||||
barRoundness: 8,
|
||||
showDataLabelsAsPercentage: false
|
||||
},
|
||||
order_count: {
|
||||
showDataLabels: false,
|
||||
columnVisualization: 'bar',
|
||||
lineWidth: 2,
|
||||
lineStyle: 'line',
|
||||
lineType: 'normal',
|
||||
lineSymbolSize: 0,
|
||||
barRoundness: 8,
|
||||
showDataLabelsAsPercentage: false
|
||||
},
|
||||
total_revenue: {
|
||||
showDataLabels: false,
|
||||
columnVisualization: 'bar',
|
||||
lineWidth: 2,
|
||||
lineStyle: 'line',
|
||||
lineType: 'normal',
|
||||
lineSymbolSize: 0,
|
||||
barRoundness: 8,
|
||||
showDataLabelsAsPercentage: false
|
||||
}
|
||||
},
|
||||
columnLabelFormats: {
|
||||
month: {
|
||||
style: 'date',
|
||||
compactNumbers: false,
|
||||
columnType: 'date',
|
||||
displayName: '',
|
||||
numberSeparatorStyle: ',',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
currency: 'USD',
|
||||
convertNumberTo: null,
|
||||
dateFormat: 'MMM YYYY',
|
||||
useRelativeTime: false,
|
||||
isUTC: false,
|
||||
multiplier: 1,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
replaceMissingDataWith: null,
|
||||
makeLabelHumanReadable: true
|
||||
},
|
||||
order_count: {
|
||||
style: 'number',
|
||||
compactNumbers: false,
|
||||
columnType: 'number',
|
||||
displayName: '',
|
||||
numberSeparatorStyle: ',',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
currency: 'USD',
|
||||
convertNumberTo: null,
|
||||
dateFormat: 'auto',
|
||||
useRelativeTime: false,
|
||||
isUTC: false,
|
||||
multiplier: 1,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
replaceMissingDataWith: 0,
|
||||
makeLabelHumanReadable: true
|
||||
},
|
||||
total_revenue: {
|
||||
style: 'currency',
|
||||
compactNumbers: false,
|
||||
columnType: 'number',
|
||||
displayName: '',
|
||||
numberSeparatorStyle: ',',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
currency: 'USD',
|
||||
convertNumberTo: null,
|
||||
dateFormat: 'auto',
|
||||
useRelativeTime: false,
|
||||
isUTC: false,
|
||||
multiplier: 1,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
replaceMissingDataWith: 0,
|
||||
makeLabelHumanReadable: true
|
||||
}
|
||||
}
|
||||
},
|
||||
file: 'name: Orders vs Revenue Trend\ndescription: Combines the monthly count of orders and sales revenue to show their trends side by side.\ntimeFrame: All time\nsql: "SELECT \\n DATE_TRUNC(\'month\', order_date)::date AS month, \\n COUNT(sales_order_id) AS order_count, \\n SUM(line_total) AS total_revenue\\nFROM sem.entity_sales_order\\nGROUP BY 1\\nORDER BY 1\\n"\nchartConfig:\n selectedChartType: combo\n columnLabelFormats:\n month:\n columnType: date\n style: date\n dateFormat: MMM YYYY\n order_count:\n columnType: number\n style: number\n minimumFractionDigits: 0\n total_revenue:\n columnType: number\n style: currency\n minimumFractionDigits: 2\n currency: USD\n comboChartAxis:\n x:\n - month\n y:\n - order_count\n - total_revenue\ndatasetIds:\n- 9fa460b4-1410-4e74-aa34-eb79027cd59c\n'
|
||||
} as const;
|
||||
|
||||
const result = compareObjectsByKeys(object1, object2, [
|
||||
'name',
|
||||
'description',
|
||||
'chart_config',
|
||||
'file'
|
||||
]);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
|
@ -5,8 +5,77 @@ import pickBy from 'lodash/pickBy';
|
|||
type ObjectKeys<T> = keyof T;
|
||||
type CommonKeys<T, U> = ObjectKeys<T> & ObjectKeys<U>;
|
||||
|
||||
export const compareObjectsByKeys = <T, U>(obj1: T, obj2: U, keys: CommonKeys<T, U>[]) => {
|
||||
return isEqual(pick(obj1, keys), pick(obj2, keys));
|
||||
/**
|
||||
* Compares two objects based on specified keys
|
||||
* @param obj1 First object to compare
|
||||
* @param obj2 Second object to compare
|
||||
* @param keys Array of keys to compare
|
||||
* @returns boolean indicating if the objects are equal for the specified keys
|
||||
* @throws Error if either object is null/undefined or keys array is empty
|
||||
*/
|
||||
export const compareObjectsByKeys = <K extends string>(
|
||||
obj1: Record<K, unknown>,
|
||||
obj2: Record<K, unknown>,
|
||||
keys: K[]
|
||||
): boolean => {
|
||||
// Input validation
|
||||
if (!obj1 || !obj2) {
|
||||
throw new Error('Both objects must be defined');
|
||||
}
|
||||
|
||||
if (!Array.isArray(keys) || keys.length === 0) {
|
||||
throw new Error('Keys array must be non-empty');
|
||||
}
|
||||
|
||||
// Compare values for each key
|
||||
return keys.every((key) => {
|
||||
const val1 = obj1[key];
|
||||
const val2 = obj2[key];
|
||||
|
||||
// Handle special cases
|
||||
if (val1 === val2) return true;
|
||||
if (val1 === null || val2 === null) return val1 === val2;
|
||||
|
||||
// Handle arrays explicitly
|
||||
if (Array.isArray(val1) && Array.isArray(val2)) {
|
||||
const arrayEqual = isEqual(val1, val2);
|
||||
if (!arrayEqual) {
|
||||
console.log('Arrays not equal:', {
|
||||
key,
|
||||
array1: val1,
|
||||
array2: val2,
|
||||
length1: val1.length,
|
||||
length2: val2.length
|
||||
});
|
||||
}
|
||||
return arrayEqual;
|
||||
}
|
||||
|
||||
// Handle other objects
|
||||
if (typeof val1 === 'object' && typeof val2 === 'object') {
|
||||
const isWasEqual = isEqual(JSON.stringify(val1), JSON.stringify(val2));
|
||||
if (!isWasEqual) {
|
||||
console.log('Objects not equal:', {
|
||||
key,
|
||||
object1: val1,
|
||||
object2: val2
|
||||
});
|
||||
}
|
||||
return isWasEqual;
|
||||
}
|
||||
|
||||
const itWasEqual = isEqual(val1, val2);
|
||||
if (!itWasEqual) {
|
||||
console.log('Values not equal:', {
|
||||
key,
|
||||
value1: val1,
|
||||
value2: val2,
|
||||
type1: typeof val1,
|
||||
type2: typeof val2
|
||||
});
|
||||
}
|
||||
return itWasEqual;
|
||||
});
|
||||
};
|
||||
|
||||
export const isJsonParsed = (jsonString: string): boolean => {
|
||||
|
|
|
@ -59,7 +59,7 @@ export type BusterAppRoutesWithArgs = {
|
|||
[BusterAppRoutes.APP_METRIC_ID_VERSION_NUMBER]: {
|
||||
route: BusterAppRoutes.APP_METRIC_ID_VERSION_NUMBER;
|
||||
metricId: string;
|
||||
versionNumber: string;
|
||||
versionNumber: number;
|
||||
};
|
||||
[BusterAppRoutes.APP_METRIC_ID_FILE]: {
|
||||
route: BusterAppRoutes.APP_METRIC_ID_FILE;
|
||||
|
@ -77,7 +77,7 @@ export type BusterAppRoutesWithArgs = {
|
|||
[BusterAppRoutes.APP_DASHBOARD_ID_VERSION_NUMBER]: {
|
||||
route: BusterAppRoutes.APP_DASHBOARD_ID_VERSION_NUMBER;
|
||||
dashboardId: string;
|
||||
versionNumber: string;
|
||||
versionNumber: number;
|
||||
};
|
||||
[BusterAppRoutes.APP_DASHBOARD_ID_FILE]: {
|
||||
route: BusterAppRoutes.APP_DASHBOARD_ID_FILE;
|
||||
|
@ -134,7 +134,7 @@ export type BusterAppRoutesWithArgs = {
|
|||
route: BusterAppRoutes.APP_CHAT_ID_METRIC_ID_VERSION_NUMBER;
|
||||
chatId: string;
|
||||
metricId: string;
|
||||
versionNumber: string;
|
||||
versionNumber: number;
|
||||
};
|
||||
[BusterAppRoutes.APP_CHAT_ID_METRIC_ID_FILE]: {
|
||||
route: BusterAppRoutes.APP_CHAT_ID_METRIC_ID_FILE;
|
||||
|
@ -160,7 +160,7 @@ export type BusterAppRoutesWithArgs = {
|
|||
route: BusterAppRoutes.APP_CHAT_ID_DASHBOARD_ID_VERSION_NUMBER;
|
||||
chatId: string;
|
||||
dashboardId: string;
|
||||
versionNumber: string;
|
||||
versionNumber: number;
|
||||
};
|
||||
[BusterAppRoutes.APP_CHAT_ID_DASHBOARD_ID_FILE]: {
|
||||
route: BusterAppRoutes.APP_CHAT_ID_DASHBOARD_ID_FILE;
|
||||
|
|
Loading…
Reference in New Issue