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); + }, +};