diff --git a/changes/issue-3854-fix-schedules-api-calls b/changes/issue-3854-fix-schedules-api-calls
new file mode 100644
index 0000000000..df0cd6330d
--- /dev/null
+++ b/changes/issue-3854-fix-schedules-api-calls
@@ -0,0 +1,2 @@
+* Bug fix: Team schedules properly render
+* Rplace redux in global and team schedules with new patterns
\ No newline at end of file
diff --git a/frontend/interfaces/global_scheduled_query.ts b/frontend/interfaces/global_scheduled_query.ts
index 9d9ac27725..932f60de5d 100644
--- a/frontend/interfaces/global_scheduled_query.ts
+++ b/frontend/interfaces/global_scheduled_query.ts
@@ -41,3 +41,7 @@ export interface IGlobalScheduledQuery {
denylist?: boolean;
stats?: IScheduledQueryStats;
}
+
+export interface ILoadAllGlobalScheduledQueriesResponse {
+ global_schedule: IGlobalScheduledQuery[];
+}
diff --git a/frontend/interfaces/team_scheduled_query.ts b/frontend/interfaces/team_scheduled_query.ts
index 8c8f1b4ece..52ebf39776 100644
--- a/frontend/interfaces/team_scheduled_query.ts
+++ b/frontend/interfaces/team_scheduled_query.ts
@@ -41,3 +41,7 @@ export interface ITeamScheduledQuery {
denylist?: boolean;
stats?: IScheduledQueryStats;
}
+
+export interface ILoadAllTeamScheduledQueriesResponse {
+ scheduled: ITeamScheduledQuery[];
+}
diff --git a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx
index ce5e02c050..f3fbb0e0a1 100644
--- a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx
+++ b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx
@@ -1,23 +1,28 @@
/* Conditionally renders global schedule and team schedules */
-import React, { useState, useCallback, useEffect, useContext } from "react";
+import React, { useState, useCallback, useContext } from "react";
import { useQuery } from "react-query";
-import { useDispatch, useSelector } from "react-redux";
+import { useDispatch } from "react-redux";
import { AppContext } from "context/app";
-import { push } from "react-router-redux";
+import { InjectedRouter } from "react-router/lib/Router";
import { find } from "lodash";
// @ts-ignore
import deepDifference from "utilities/deep_difference";
import { ITeam } from "interfaces/team";
-import { IGlobalScheduledQuery } from "interfaces/global_scheduled_query";
-import { ITeamScheduledQuery } from "interfaces/team_scheduled_query";
-// @ts-ignore
-import globalScheduledQueryActions from "redux/nodes/entities/global_scheduled_queries/actions";
-// @ts-ignore
-import teamScheduledQueryActions from "redux/nodes/entities/team_scheduled_queries/actions";
+import {
+ IGlobalScheduledQuery,
+ ILoadAllGlobalScheduledQueriesResponse,
+} from "interfaces/global_scheduled_query";
+import {
+ ITeamScheduledQuery,
+ ILoadAllTeamScheduledQueriesResponse,
+} from "interfaces/team_scheduled_query";
import fleetQueriesAPI from "services/entities/queries";
+import globalScheduledQueriesAPI from "services/entities/global_scheduled_queries";
+import teamScheduledQueriesAPI from "services/entities/team_scheduled_queries";
import teamsAPI from "services/entities/teams";
+import usersAPI, { IGetMeResponse } from "services/entities/users";
// @ts-ignore
import { renderFlash } from "redux/nodes/notifications/actions";
@@ -40,16 +45,16 @@ const renderTable = (
selectedQuery: IGlobalScheduledQuery | ITeamScheduledQuery
) => void,
allScheduledQueriesList: IGlobalScheduledQuery[] | ITeamScheduledQuery[],
- allScheduledQueriesError: { name: string; reason: string }[],
+ allScheduledQueriesError: Error | null,
toggleScheduleEditorModal: () => void,
isOnGlobalTeam: boolean,
- selectedTeamData: ITeam | undefined
+ selectedTeamData: ITeam | undefined,
+ isLoadingGlobalScheduledQueries: boolean,
+ isLoadingTeamScheduledQueries: boolean
): JSX.Element => {
- if (Object.keys(allScheduledQueriesError).length !== 0) {
- return ;
- }
-
- return (
+ return allScheduledQueriesError ? (
+
+ ) : (
);
};
const renderAllTeamsTable = (
allTeamsScheduledQueriesList: IGlobalScheduledQuery[],
- allTeamsScheduledQueriesError: { name: string; reason: string }[],
+ allTeamsScheduledQueriesError: Error | null,
isOnGlobalTeam: boolean,
- selectedTeamData: ITeam | undefined
+ selectedTeamData: ITeam | undefined,
+ isLoadingGlobalScheduledQueries: boolean,
+ isLoadingTeamScheduledQueries: boolean
): JSX.Element => {
- if (Object.keys(allTeamsScheduledQueriesError).length > 0) {
- return ;
- }
-
- return (
+ return allTeamsScheduledQueriesError ? (
+
+ ) : (
);
@@ -87,23 +96,7 @@ interface ITeamSchedulesPageProps {
params: {
team_id: string;
};
- location: any; // no type in react-router v3
-}
-
-// TODO: move team scheduled queries and global scheduled queries into services entities, remove redux
-interface IRootState {
- entities: {
- global_scheduled_queries: {
- isLoading: boolean;
- data: IGlobalScheduledQuery[];
- errors: { name: string; reason: string }[];
- };
- team_scheduled_queries: {
- isLoading: boolean;
- data: ITeamScheduledQuery[];
- errors: { name: string; reason: string }[];
- };
- };
+ router: InjectedRouter; // v3
}
interface IFormData {
interval: number;
@@ -119,17 +112,21 @@ interface IFormData {
const ManageSchedulePage = ({
params: { team_id },
+ router,
}: ITeamSchedulesPageProps): JSX.Element => {
const dispatch = useDispatch();
const { MANAGE_PACKS, MANAGE_SCHEDULE, MANAGE_TEAM_SCHEDULE } = paths;
- const handleAdvanced = () => dispatch(push(MANAGE_PACKS));
+ const handleAdvanced = () => router.push(MANAGE_PACKS);
const {
+ availableTeams,
currentUser,
isOnGlobalTeam,
isPremiumTier,
isFreeTier,
currentTeam,
+ setAvailableTeams,
+ setCurrentUser,
setCurrentTeam,
} = useContext(AppContext);
@@ -148,6 +145,13 @@ const ManageSchedulePage = ({
return filteredSortedTeams;
};
+ useQuery(["me"], () => usersAPI.me(), {
+ onSuccess: ({ user, available_teams }: IGetMeResponse) => {
+ setCurrentUser(user);
+ setAvailableTeams(available_teams);
+ },
+ });
+
const { data: teams, isLoading: isLoadingTeams } = useQuery(
["teams"],
() => teamsAPI.loadAll({}),
@@ -163,7 +167,7 @@ const ManageSchedulePage = ({
}
);
- const { data: fleetQueries } = useQuery(
+ const { data: fleetQueries, isLoading: isLoadingFleetQueries } = useQuery(
["fleetQueries"],
() => fleetQueriesAPI.loadAll(),
{
@@ -173,6 +177,38 @@ const ManageSchedulePage = ({
}
);
+ const {
+ data: globalScheduledQueries,
+ error: globalScheduledQueriesError,
+ isLoading: isLoadingGlobalScheduledQueries,
+ refetch: refetchGlobalScheduledQueries,
+ } = useQuery<
+ ILoadAllGlobalScheduledQueriesResponse,
+ Error,
+ IGlobalScheduledQuery[]
+ >(["globalScheduledQueries"], () => globalScheduledQueriesAPI.loadAll(), {
+ enabled: !!availableTeams,
+ select: (data) => data.global_schedule,
+ });
+
+ const {
+ data: teamScheduledQueries,
+ error: teamScheduledQueriesError,
+ isLoading: isLoadingTeamScheduledQueries,
+ refetch: refetchTeamScheduledQueries,
+ } = useQuery<
+ ILoadAllTeamScheduledQueriesResponse,
+ Error,
+ ITeamScheduledQuery[]
+ >(
+ ["teamScheduledQueries", team_id],
+ () => teamScheduledQueriesAPI.loadAll(parseInt(team_id, 10)),
+ {
+ enabled: !!availableTeams && isPremiumTier && !!team_id,
+ select: (data) => data.scheduled,
+ }
+ );
+
let selectedTeamId: number;
if (currentTeam) {
@@ -181,11 +217,18 @@ const ManageSchedulePage = ({
selectedTeamId = team_id ? parseInt(team_id, 10) : 0;
}
+ const refetchScheduledQueries = () => {
+ refetchGlobalScheduledQueries();
+ if (selectedTeamId !== 0) {
+ refetchTeamScheduledQueries();
+ }
+ };
+
const handleTeamSelect = (teamId: number) => {
if (teamId) {
- dispatch(push(MANAGE_TEAM_SCHEDULE(teamId)));
+ router.push(MANAGE_TEAM_SCHEDULE(teamId));
} else {
- dispatch(push(MANAGE_SCHEDULE));
+ router.push(MANAGE_SCHEDULE);
}
const selectedTeam = find(teams, ["id", teamId]);
setCurrentTeam(selectedTeam);
@@ -195,27 +238,17 @@ const ManageSchedulePage = ({
handleTeamSelect(teams[0].id);
}
- const allScheduledQueries = useSelector((state: IRootState) => {
- if (selectedTeamId) {
- return state.entities.team_scheduled_queries;
- }
- return state.entities.global_scheduled_queries;
- });
+ const allScheduledQueriesList =
+ (team_id ? teamScheduledQueries : globalScheduledQueries) || [];
+ const allScheduledQueriesError = team_id
+ ? teamScheduledQueriesError
+ : globalScheduledQueriesError;
- const allScheduledQueriesList = Object.values(allScheduledQueries.data);
- const allScheduledQueriesError = allScheduledQueries.errors;
-
- const allTeamsScheduledQueries = useSelector((state: IRootState) => {
- return state.entities.global_scheduled_queries;
- });
-
- const allTeamsScheduledQueriesList = Object.values(
- allTeamsScheduledQueries.data
- );
- const allTeamsScheduledQueriesError = allTeamsScheduledQueries.errors;
+ const inheritedScheduledQueriesList = globalScheduledQueries;
+ const inheritedScheduledQueriesError = globalScheduledQueriesError;
const inheritedQueryOrQueries =
- allTeamsScheduledQueriesList.length === 1 ? "query" : "queries";
+ inheritedScheduledQueriesList?.length === 1 ? "query" : "queries";
const selectedTeam = !selectedTeamId ? "global" : selectedTeamId;
@@ -271,11 +304,9 @@ const ManageSchedulePage = ({
const onRemoveScheduledQuerySubmit = useCallback(() => {
const promises = selectedQueryIds.map((id: number) => {
- return dispatch(
- selectedTeamId
- ? teamScheduledQueryActions.destroy(selectedTeamId, id)
- : globalScheduledQueryActions.destroy({ id })
- );
+ return selectedTeamId
+ ? teamScheduledQueriesAPI.destroy(selectedTeamId, id)
+ : globalScheduledQueriesAPI.destroy({ id });
});
const queryOrQueries = selectedQueryIds.length === 1 ? "query" : "queries";
return Promise.all(promises)
@@ -287,11 +318,7 @@ const ManageSchedulePage = ({
)
);
toggleRemoveScheduledQueryModal();
- dispatch(
- selectedTeamId
- ? teamScheduledQueryActions.loadAll(selectedTeamId)
- : globalScheduledQueryActions.loadAll()
- );
+ refetchScheduledQueries();
})
.catch(() => {
dispatch(
@@ -307,6 +334,7 @@ const ManageSchedulePage = ({
selectedTeamId,
selectedQueryIds,
toggleRemoveScheduledQueryModal,
+ refetchScheduledQueries,
]);
const onAddScheduledQuerySubmit = useCallback(
@@ -316,11 +344,12 @@ const ManageSchedulePage = ({
) => {
if (editQuery) {
const updatedAttributes = deepDifference(formData, editQuery);
- dispatch(
- selectedTeamId
- ? teamScheduledQueryActions.update(editQuery, updatedAttributes)
- : globalScheduledQueryActions.update(editQuery, updatedAttributes)
- )
+
+ const editResponse = selectedTeamId
+ ? teamScheduledQueriesAPI.update(editQuery, updatedAttributes)
+ : globalScheduledQueriesAPI.update(editQuery, updatedAttributes);
+
+ editResponse
.then(() => {
dispatch(
renderFlash(
@@ -328,11 +357,7 @@ const ManageSchedulePage = ({
`Successfully updated ${formData.name} in the schedule.`
)
);
- dispatch(
- selectedTeamId
- ? teamScheduledQueryActions.loadAll(selectedTeamId)
- : globalScheduledQueryActions.loadAll()
- );
+ refetchScheduledQueries();
})
.catch(() => {
dispatch(
@@ -343,11 +368,11 @@ const ManageSchedulePage = ({
);
});
} else {
- dispatch(
- selectedTeamId
- ? teamScheduledQueryActions.create({ ...formData })
- : globalScheduledQueryActions.create({ ...formData })
- )
+ const createResponse = selectedTeamId
+ ? teamScheduledQueriesAPI.create({ ...formData })
+ : globalScheduledQueriesAPI.create({ ...formData });
+
+ createResponse
.then(() => {
dispatch(
renderFlash(
@@ -355,11 +380,7 @@ const ManageSchedulePage = ({
`Successfully added ${formData.name} to the schedule.`
)
);
- dispatch(
- selectedTeamId
- ? teamScheduledQueryActions.loadAll(selectedTeamId)
- : globalScheduledQueryActions.loadAll()
- );
+ refetchScheduledQueries();
})
.catch(() => {
dispatch(
@@ -401,27 +422,26 @@ const ManageSchedulePage = ({
- {allScheduledQueriesList.length !== 0 &&
- allScheduledQueriesError.length !== 0 && (
-
- {isOnGlobalTeam && (
-
- )}
+ {allScheduledQueriesList?.length !== 0 && !allScheduledQueriesError && (
+
+ {isOnGlobalTeam && (
-
- )}
+ )}
+
+
+ )}
{!isLoadingTeams && (
@@ -441,7 +461,10 @@ const ManageSchedulePage = ({
)}
- {isLoadingTeams ? (
+ {isLoadingTeams ||
+ isLoadingFleetQueries ||
+ isLoadingGlobalScheduledQueries ||
+ isLoadingTeamScheduledQueries ? (
) : (
renderTable(
@@ -451,12 +474,16 @@ const ManageSchedulePage = ({
allScheduledQueriesError,
toggleScheduleEditorModal,
isOnGlobalTeam || false,
- selectedTeamData
+ selectedTeamData,
+ isLoadingGlobalScheduledQueries,
+ isLoadingTeamScheduledQueries
)
)}
{/* must use ternary for NaN */}
- {selectedTeamId && allTeamsScheduledQueriesList.length > 0 ? (
+ {selectedTeamId &&
+ inheritedScheduledQueriesList &&
+ inheritedScheduledQueriesList.length > 0 ? (
<>
@@ -483,11 +510,14 @@ const ManageSchedulePage = ({
>
) : null}
{showInheritedQueries &&
+ inheritedScheduledQueriesList &&
renderAllTeamsTable(
- allTeamsScheduledQueriesList,
- allTeamsScheduledQueriesError,
+ inheritedScheduledQueriesList,
+ inheritedScheduledQueriesError,
isOnGlobalTeam || false,
- selectedTeamData
+ selectedTeamData,
+ isLoadingGlobalScheduledQueries,
+ isLoadingTeamScheduledQueries
)}
{showScheduleEditorModal && (
{
const dispatch = useDispatch();
const { MANAGE_PACKS, MANAGE_HOSTS } = paths;
@@ -153,31 +155,9 @@ const ScheduleListWrapper = ({
};
const tableHeaders = generateTableHeaders(onActionSelection);
- const loadingTableData = useSelector((state: IRootState) => {
- if (selectedTeamData?.id) {
- return state.entities.team_scheduled_queries.isLoading;
- }
- return state.entities.global_scheduled_queries.isLoading;
- });
-
- // Search functionality disabled, needed if enabled
- const onQueryChange = useCallback(
- (queryData) => {
- const { pageIndex, pageSize, searchQuery } = queryData;
- dispatch(
- globalScheduledQueryActions.loadAll({
- page: pageIndex,
- perPage: pageSize,
- globalFilter: searchQuery,
- })
- );
- },
- [dispatch]
- );
-
- const loadingInheritedQueriesTableData = useSelector((state: IRootState) => {
- return state.entities.global_scheduled_queries.isLoading;
- });
+ const loadingTableData = selectedTeamData?.id
+ ? loadingTeamQueriesTableData
+ : loadingInheritedQueriesTableData;
if (inheritedQueries) {
const inheritedQueriesTableHeaders = generateInheritedQueriesTableHeaders();
@@ -213,7 +193,6 @@ const ScheduleListWrapper = ({
defaultSortDirection={"desc"}
showMarkAllPages={false}
isAllPagesSelected={false}
- onQueryChange={onQueryChange}
inputPlaceHolder="Search"
searchable={false}
disablePagination
diff --git a/frontend/services/entities/global_scheduled_queries.ts b/frontend/services/entities/global_scheduled_queries.ts
new file mode 100644
index 0000000000..595e171088
--- /dev/null
+++ b/frontend/services/entities/global_scheduled_queries.ts
@@ -0,0 +1,59 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import sendRequest from "services";
+import { omit } from "lodash";
+
+import endpoints from "fleet/endpoints";
+import { IGlobalScheduledQuery } from "interfaces/global_scheduled_query";
+import helpers from "fleet/helpers";
+
+export default {
+ create: (formData: any) => {
+ const { GLOBAL_SCHEDULE } = endpoints;
+
+ const {
+ interval,
+ logging_type: loggingType,
+ platform,
+ query_id: queryID,
+ shard,
+ version,
+ } = formData;
+
+ const removed = loggingType === "differential";
+ const snapshot = loggingType === "snapshot";
+
+ const params = {
+ interval: Number(interval),
+ platform,
+ query_id: Number(queryID),
+ removed,
+ snapshot,
+ shard: Number(shard),
+ version,
+ };
+
+ return sendRequest("POST", GLOBAL_SCHEDULE, params);
+ },
+ destroy: ({ id }: { id: number }) => {
+ const { GLOBAL_SCHEDULE } = endpoints;
+ const path = `${GLOBAL_SCHEDULE}/${id}`;
+
+ return sendRequest("DELETE", path);
+ },
+ loadAll: () => {
+ const { GLOBAL_SCHEDULE } = endpoints;
+ const path = GLOBAL_SCHEDULE;
+
+ return sendRequest("GET", path);
+ },
+ update: (
+ globalScheduledQuery: IGlobalScheduledQuery,
+ updatedAttributes: any
+ ) => {
+ const { GLOBAL_SCHEDULE } = endpoints;
+ const path = `${GLOBAL_SCHEDULE}/${globalScheduledQuery.id}`;
+ const params = helpers.formatScheduledQueryForServer(updatedAttributes);
+
+ return sendRequest("PATCH", path, params);
+ },
+};
diff --git a/frontend/services/entities/team_scheduled_queries.ts b/frontend/services/entities/team_scheduled_queries.ts
new file mode 100644
index 0000000000..47807d26e8
--- /dev/null
+++ b/frontend/services/entities/team_scheduled_queries.ts
@@ -0,0 +1,59 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import sendRequest from "services";
+import { omit } from "lodash";
+
+import endpoints from "fleet/endpoints";
+import { ITeamScheduledQuery } from "interfaces/team_scheduled_query";
+import helpers from "fleet/helpers";
+
+export default {
+ create: (formData: any) => {
+ const { TEAM_SCHEDULE } = endpoints;
+
+ const {
+ interval,
+ logging_type: loggingType,
+ platform,
+ query_id: queryID,
+ shard,
+ version,
+ team_id: teamID,
+ } = formData;
+
+ const removed = loggingType === "differential";
+ const snapshot = loggingType === "snapshot";
+
+ const params = {
+ interval: Number(interval),
+ platform,
+ query_id: Number(queryID),
+ removed,
+ snapshot,
+ shard: Number(shard),
+ version,
+ team_id: Number(teamID),
+ };
+
+ return sendRequest("POST", TEAM_SCHEDULE(teamID), params);
+ },
+ destroy: (teamID: number, queryID: number) => {
+ const { TEAM_SCHEDULE } = endpoints;
+ const path = `${TEAM_SCHEDULE(teamID)}/${queryID}`;
+
+ return sendRequest("DELETE", path);
+ },
+ loadAll: (teamID: number) => {
+ const { TEAM_SCHEDULE } = endpoints;
+ const path = TEAM_SCHEDULE(teamID);
+
+ return sendRequest("GET", path);
+ },
+ update: (teamScheduledQuery: ITeamScheduledQuery, updatedAttributes: any) => {
+ const { team_id } = updatedAttributes;
+ const { TEAM_SCHEDULE } = endpoints;
+ const path = `${TEAM_SCHEDULE(team_id)}/${teamScheduledQuery.id}`;
+ const params = helpers.formatScheduledQueryForServer(updatedAttributes);
+
+ return sendRequest("PATCH", path, params);
+ },
+};