Skip to content

Commit e94a62e

Browse files
authored
feat: Correlation CRUD operations (#417)
1 parent fd51eee commit e94a62e

19 files changed

+1287
-102
lines changed
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export type Correlation = {
2+
version: string;
3+
id: string;
4+
title: string;
5+
tableConfigs: Array<{
6+
selectedFields: string[];
7+
tableName: string;
8+
}>;
9+
joinConfig: {
10+
joinConditions: Array<{
11+
tableName: string;
12+
field: string;
13+
}>;
14+
};
15+
filter: null;
16+
startTime: string;
17+
endTime: string;
18+
};

src/api/constants.ts

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export const LOG_QUERY_URL = (params?: Params, resourcePath = 'query') =>
2626
export const LOG_STREAMS_ALERTS_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/alert`;
2727
export const LIST_SAVED_FILTERS_URL = `${API_V1}/filters`;
2828
export const LIST_DASHBOARDS = `${API_V1}/dashboards`;
29+
export const LIST_CORRELATIONS = `${API_V1}/correlation`;
30+
export const UPDATE_CORRELATION_URL = (correlationId: string) => `${API_V1}/correlation/${correlationId}`;
31+
export const DELETE_SAVED_CORRELATION_URL = (correlationId: string) => `${API_V1}/correlation/${correlationId}`;
32+
export const GET_SAVED_CORRELATION_URL = (correlationId: string) => `${API_V1}/correlation/${correlationId}`;
2933
export const UPDATE_SAVED_FILTERS_URL = (filterId: string) => `${API_V1}/filters/${filterId}`;
3034
export const UPDATE_DASHBOARDS_URL = (dashboardId: string) => `${API_V1}/dashboards/${dashboardId}`;
3135
export const DELETE_DASHBOARDS_URL = (dashboardId: string) => `${API_V1}/dashboards/${dashboardId}`;

src/api/correlations.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Correlation } from '@/@types/parseable/api/correlation';
2+
import { Axios } from './axios';
3+
import {
4+
DELETE_SAVED_CORRELATION_URL,
5+
GET_SAVED_CORRELATION_URL,
6+
LIST_CORRELATIONS,
7+
UPDATE_CORRELATION_URL,
8+
} from './constants';
9+
10+
export const getCorrelations = () => {
11+
return Axios().get<Correlation[]>(LIST_CORRELATIONS);
12+
};
13+
14+
export const getCorrelationById = (correlationId: string) => {
15+
return Axios().get(GET_SAVED_CORRELATION_URL(correlationId));
16+
};
17+
18+
export const deleteSavedCorrelation = (correlationId: string) => {
19+
return Axios().delete(DELETE_SAVED_CORRELATION_URL(correlationId));
20+
};
21+
22+
export const saveCorrelation = (correlationData: Correlation) => {
23+
return Axios().post(LIST_CORRELATIONS, correlationData);
24+
};
25+
26+
export const updateCorrelation = (correlationData: Correlation) => {
27+
return Axios().put(UPDATE_CORRELATION_URL(correlationData.id), correlationData);
28+
};

src/hooks/useCorrelationQueryLogs.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { getCorrelationQueryLogsWithHeaders } from '@/api/query';
2-
import { StatusCodes } from 'http-status-codes';
32
import useMountedState from './useMountedState';
43
import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider';
54
import _ from 'lodash';
@@ -55,9 +54,9 @@ export const useCorrelationQueryLogs = () => {
5554
enabled: false,
5655
refetchOnWindowFocus: false,
5756
onSuccess: async (responses) => {
58-
responses.map((data: { data: LogsResponseWithHeaders; status: StatusCodes }) => {
57+
responses.map((data: { data: LogsResponseWithHeaders }) => {
5958
const logs = data.data;
60-
const isInvalidResponse = _.isEmpty(logs) || _.isNil(logs) || data.status !== StatusCodes.OK;
59+
const isInvalidResponse = _.isEmpty(logs) || _.isNil(logs);
6160
if (isInvalidResponse) return setError('Failed to query logs');
6261

6362
const { records, fields } = logs;

src/hooks/useCorrelations.tsx

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { useMutation, useQuery } from 'react-query';
2+
import _ from 'lodash';
3+
4+
import {
5+
deleteSavedCorrelation,
6+
getCorrelationById,
7+
getCorrelations,
8+
saveCorrelation,
9+
updateCorrelation,
10+
} from '@/api/correlations';
11+
import { correlationStoreReducers, useCorrelationStore } from '@/pages/Correlation/providers/CorrelationProvider';
12+
import { notifyError, notifySuccess } from '@/utils/notification';
13+
import { AxiosError, isAxiosError } from 'axios';
14+
import { appStoreReducers, useAppStore } from '@/layouts/MainLayout/providers/AppProvider';
15+
import dayjs from 'dayjs';
16+
17+
const {
18+
setCorrelations,
19+
setActiveCorrelation,
20+
setCorrelationId,
21+
setSavedCorrelationId,
22+
cleanCorrelationStore,
23+
toggleSavedCorrelationsModal,
24+
} = correlationStoreReducers;
25+
const { setTimeRange, syncTimeRange } = appStoreReducers;
26+
export const useCorrelationsQuery = () => {
27+
const [{ correlationId }, setCorrelatedStore] = useCorrelationStore((store) => store);
28+
const [, setAppStore] = useAppStore((store) => store);
29+
const {
30+
isError: fetchCorrelationsError,
31+
isSuccess: fetchCorrelationsSuccess,
32+
isLoading: fetchCorrelationsLoading,
33+
refetch: fetchCorrelations,
34+
} = useQuery(['correlations'], () => getCorrelations(), {
35+
retry: false,
36+
enabled: false,
37+
refetchOnWindowFocus: false,
38+
onSuccess: (data) => {
39+
setCorrelatedStore((store) => setCorrelations(store, data.data || []));
40+
},
41+
onError: () => {
42+
setCorrelatedStore((store) => setCorrelations(store, []));
43+
notifyError({ message: 'Failed to fetch correlations' });
44+
},
45+
});
46+
47+
const {
48+
mutate: getCorrelationByIdMutation,
49+
isError: fetchCorrelationIdError,
50+
isSuccess: fetchCorrelationIdSuccess,
51+
isLoading: fetchCorrelationIdLoading,
52+
} = useMutation((correlationId: string) => getCorrelationById(correlationId), {
53+
onSuccess: (data: any) => {
54+
data.data.startTime &&
55+
data.data.endTime &&
56+
setAppStore((store) =>
57+
setTimeRange(store, {
58+
startTime: dayjs(data.data.startTime),
59+
endTime: dayjs(data.data.endTime),
60+
type: 'custom',
61+
}),
62+
);
63+
setCorrelatedStore((store) => setCorrelationId(store, data.data.id));
64+
setCorrelatedStore((store) => setActiveCorrelation(store, data.data));
65+
},
66+
onError: () => {
67+
notifyError({ message: 'Failed to fetch correlation' });
68+
},
69+
});
70+
71+
const { mutate: deleteSavedCorrelationMutation, isLoading: isDeleting } = useMutation(
72+
(data: { correlationId: string; onSuccess?: () => void }) => deleteSavedCorrelation(data.correlationId),
73+
{
74+
onSuccess: (_data, variables) => {
75+
variables.onSuccess && variables.onSuccess();
76+
if (variables.correlationId === correlationId) {
77+
setCorrelatedStore(cleanCorrelationStore);
78+
setAppStore(syncTimeRange);
79+
}
80+
fetchCorrelations();
81+
setCorrelatedStore((store) => toggleSavedCorrelationsModal(store, false));
82+
notifySuccess({ message: 'Deleted Successfully' });
83+
},
84+
onError: (data: AxiosError) => {
85+
if (isAxiosError(data) && data.response) {
86+
const error = data.response?.data as string;
87+
typeof error === 'string' && notifyError({ message: error });
88+
} else if (data.message && typeof data.message === 'string') {
89+
notifyError({ message: data.message });
90+
}
91+
},
92+
},
93+
);
94+
95+
const { mutate: saveCorrelationMutation, isLoading: isCorrelationSaving } = useMutation(
96+
(data: { correlationData: any; onSuccess?: () => void }) => saveCorrelation(data.correlationData),
97+
{
98+
onSuccess: (data, variables) => {
99+
variables.onSuccess && variables.onSuccess();
100+
setCorrelatedStore((store) => setCorrelationId(store, data.data.id));
101+
setCorrelatedStore((store) => setSavedCorrelationId(store, data.data.id));
102+
fetchCorrelations();
103+
notifySuccess({ message: 'Correlation saved successfully' });
104+
},
105+
onError: (data: AxiosError) => {
106+
if (isAxiosError(data) && data.response) {
107+
const error = data.response?.data as string;
108+
typeof error === 'string' && notifyError({ message: error });
109+
} else if (data.message && typeof data.message === 'string') {
110+
notifyError({ message: data.message });
111+
}
112+
},
113+
},
114+
);
115+
116+
const { mutate: updateCorrelationMutation, isLoading: isCorrelationUpdating } = useMutation(
117+
(data: { correlationData: any; onSuccess?: () => void }) => updateCorrelation(data.correlationData),
118+
{
119+
onSuccess: (data, variables) => {
120+
variables.onSuccess && variables.onSuccess();
121+
setCorrelatedStore((store) => setCorrelationId(store, data.data.id));
122+
setCorrelatedStore((store) => setActiveCorrelation(store, data.data));
123+
fetchCorrelations();
124+
notifySuccess({ message: 'Correlation updated successfully' });
125+
},
126+
onError: (data: AxiosError) => {
127+
if (isAxiosError(data) && data.response) {
128+
const error = data.response?.data as string;
129+
typeof error === 'string' && notifyError({ message: error });
130+
} else if (data.message && typeof data.message === 'string') {
131+
notifyError({ message: data.message });
132+
}
133+
},
134+
},
135+
);
136+
137+
return {
138+
fetchCorrelationsError,
139+
fetchCorrelationsSuccess,
140+
fetchCorrelationsLoading,
141+
fetchCorrelations,
142+
143+
deleteSavedCorrelationMutation,
144+
isDeleting,
145+
146+
fetchCorrelationIdError,
147+
fetchCorrelationIdSuccess,
148+
fetchCorrelationIdLoading,
149+
getCorrelationByIdMutation,
150+
151+
saveCorrelationMutation,
152+
isCorrelationSaving,
153+
154+
updateCorrelationMutation,
155+
isCorrelationUpdating,
156+
};
157+
};

src/hooks/useFetchStreamData.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
} from '@/pages/Correlation/providers/CorrelationProvider';
1313
import { notifyError } from '@/utils/notification';
1414
import { useQuery } from 'react-query';
15-
import { LogsResponseWithHeaders } from '@/@types/parseable/api/query';
1615
import { useRef, useEffect } from 'react';
1716

1817
const { setStreamData } = correlationStoreReducers;
@@ -23,7 +22,6 @@ export const useFetchStreamData = () => {
2322
(store) => store,
2423
);
2524
const [streamInfo] = useStreamStore((store) => store.info);
26-
const [currentStream] = useAppStore((store) => store.currentStream);
2725
const timePartitionColumn = _.get(streamInfo, 'time_partition', 'p_timestamp');
2826
const [timeRange] = useAppStore((store) => store.timeRange);
2927
const [
@@ -66,26 +64,29 @@ export const useFetchStreamData = () => {
6664

6765
const fetchPromises = streamsToFetch.map((streamName) => {
6866
const queryOpts = { ...defaultQueryOpts, streamNames: [streamName] };
69-
return getStreamDataWithHeaders(queryOpts);
67+
return getStreamDataWithHeaders(queryOpts).then((data) => ({ streamName, data }));
7068
});
7169
return Promise.all(fetchPromises);
7270
},
7371
{
7472
enabled: false,
7573
refetchOnWindowFocus: false,
7674
onSuccess: async (responses) => {
77-
responses.map((data: { data: LogsResponseWithHeaders; status: StatusCodes }) => {
75+
responses.forEach(({ streamName, data }) => {
7876
const logs = data.data;
7977
const isInvalidResponse = _.isEmpty(logs) || _.isNil(logs) || data.status !== StatusCodes.OK;
80-
if (isInvalidResponse) return setError('Failed to query logs');
78+
if (isInvalidResponse) {
79+
setError('Failed to query logs');
80+
return;
81+
}
8182

8283
const { records, fields } = logs;
8384
if (fields.length > 0 && !correlationCondition) {
84-
return setCorrelationStore((store) => setStreamData(store, currentStream || '', records));
85+
setCorrelationStore((store) => setStreamData(store, streamName, records));
8586
} else if (fields.length > 0 && correlationCondition) {
86-
return setCorrelationStore((store) => setStreamData(store, 'correlatedStream', records));
87+
setCorrelationStore((store) => setStreamData(store, 'correlatedStream', records));
8788
} else {
88-
notifyError({ message: `${currentStream} doesn't have any fields` });
89+
notifyError({ message: `${streamName} doesn't have any fields` });
8990
}
9091
});
9192
},

src/hooks/useGetCorrelationStreamSchema.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getLogStreamSchema } from '@/api/logStream';
22
import { AxiosError, isAxiosError } from 'axios';
33
import _ from 'lodash';
4-
import { useQuery } from 'react-query';
4+
import { useQueries, useQuery } from 'react-query';
55
import { useState } from 'react';
66
import { correlationStoreReducers, useCorrelationStore } from '@/pages/Correlation/providers/CorrelationProvider';
77

@@ -43,3 +43,46 @@ export const useGetStreamSchema = (opts: { streamName: string }) => {
4343
isRefetching,
4444
};
4545
};
46+
47+
// Multiple stream schemas hook
48+
export const useGetMultipleStreamSchemas = (streams: string[]) => {
49+
const [, setCorrelationStore] = useCorrelationStore((_store) => null);
50+
const [errors, setErrors] = useState<Record<string, string>>({});
51+
52+
const queries = useQueries(
53+
streams.map((streamName) => ({
54+
queryKey: ['stream-schema', streamName],
55+
queryFn: () => getLogStreamSchema(streamName),
56+
retry: false,
57+
enabled: streamName !== '' && streamName !== 'correlatedStream',
58+
refetchOnWindowFocus: false,
59+
onSuccess: (data: any) => {
60+
setErrors((prev) => _.omit(prev, streamName));
61+
setCorrelationStore((store) => setStreamSchema(store, data.data, streamName));
62+
},
63+
onError: (error: AxiosError) => {
64+
let errorMessage = 'An unknown error occurred';
65+
if (isAxiosError(error) && error.response?.data) {
66+
errorMessage = typeof error.response.data === 'string' ? error.response.data : error.message;
67+
}
68+
setErrors((prev) => ({
69+
...prev,
70+
[streamName]: errorMessage,
71+
}));
72+
},
73+
})),
74+
);
75+
76+
const isLoading = queries.some((query) => query.isLoading);
77+
const isError = queries.some((query) => query.isError);
78+
const isSuccess = queries.every((query) => query.isSuccess);
79+
const isRefetching = queries.some((query) => query.isRefetching);
80+
81+
return {
82+
isLoading,
83+
isError,
84+
isSuccess,
85+
isRefetching,
86+
errors,
87+
};
88+
};

0 commit comments

Comments
 (0)