mirror of
https://github.com/fleetdm/fleet
synced 2026-05-21 07:58:31 +00:00
## #14801 ### [Demo video](https://drive.google.com/file/d/1Lovk7iwvgUv1NpfsqSt-Is0yTBt0SZ5O/view?usp=sharing) <img width="1624" alt="Screenshot 2025-07-02 at 4 58 33 PM" src="https://github.com/user-attachments/assets/86c7b214-e8e4-4e58-9969-b1373ed97691" /> * **New Features** * Added the ability to select a team and update the name when saving a query as a new copy, using a dedicated modal dialog. * **Improvements** * Enhanced the team selection dropdown with new styling options and clarified prop names. * Updated query editing workflow to use a modal for "Save as new" actions. * Improved type safety and clarity in several interfaces and utility functions. * **Bug Fixes** * Fixed inconsistencies in prop naming for team dropdown components. * Ensured "Discard data" setting is maintained when "Save as new"ing a query - it was previously not maintained correctly * **Tests** * Updated and removed tests to align with the new "Save as new" query workflow and prop changes. * Added utilities for creating mock location objects in tests. * **Style** * Added a new light grey color to the UI color palette. - [x] Changes file added for user-visible changes in `changes/` - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
142 lines
3.6 KiB
TypeScript
142 lines
3.6 KiB
TypeScript
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
import sendRequest from "services";
|
|
import endpoints from "utilities/endpoints";
|
|
import { getErrorReason } from "interfaces/errors";
|
|
import { ISelectedTargetsForApi } from "interfaces/target";
|
|
import {
|
|
ICreateQueryRequestBody,
|
|
IModifyQueryRequestBody,
|
|
IQueryKeyQueriesLoadAll,
|
|
ISchedulableQuery,
|
|
} from "interfaces/schedulable_query";
|
|
import {
|
|
buildQueryStringFromParams,
|
|
convertParamsToSnakeCase,
|
|
} from "utilities/url";
|
|
import { SelectedPlatform } from "interfaces/platform";
|
|
|
|
export interface ILoadQueriesParams {
|
|
teamId?: number;
|
|
page?: number;
|
|
perPage?: number;
|
|
query?: string;
|
|
orderDirection?: "asc" | "desc";
|
|
orderKey?: string;
|
|
mergeInherited?: boolean;
|
|
targetedPlatform?: SelectedPlatform;
|
|
}
|
|
export interface IQueryKeyLoadQueries extends ILoadQueriesParams {
|
|
scope: "queries";
|
|
}
|
|
|
|
interface ICreateQueryResponse {
|
|
query: ISchedulableQuery;
|
|
}
|
|
export interface IQueriesResponse {
|
|
queries: ISchedulableQuery[];
|
|
count: number;
|
|
meta: {
|
|
has_next_results: boolean;
|
|
has_previous_results: boolean;
|
|
};
|
|
}
|
|
|
|
export default {
|
|
create: (
|
|
createQueryRequestBody: ICreateQueryRequestBody
|
|
): Promise<ICreateQueryResponse> => {
|
|
const { QUERIES } = endpoints;
|
|
if (createQueryRequestBody.name) {
|
|
createQueryRequestBody.name = createQueryRequestBody.name.trim();
|
|
}
|
|
|
|
return sendRequest("POST", QUERIES, createQueryRequestBody);
|
|
},
|
|
destroy: (id: string | number) => {
|
|
const { QUERIES } = endpoints;
|
|
const path = `${QUERIES}/id/${id}`;
|
|
|
|
return sendRequest("DELETE", path);
|
|
},
|
|
bulkDestroy: (ids: number[]) => {
|
|
const { QUERIES } = endpoints;
|
|
const path = `${QUERIES}/delete`;
|
|
return sendRequest("POST", path, { ids });
|
|
},
|
|
load: (id: number) => {
|
|
const { QUERIES } = endpoints;
|
|
const path = `${QUERIES}/${id}`;
|
|
|
|
return sendRequest("GET", path);
|
|
},
|
|
loadAll: ({
|
|
teamId,
|
|
page,
|
|
perPage,
|
|
query,
|
|
orderDirection,
|
|
orderKey,
|
|
mergeInherited,
|
|
// FE logic uses less ambiguous `targetedPlatform`, while API expects `platform` for alignment
|
|
// with other API conventions and database `queries.platform` column
|
|
targetedPlatform: platform,
|
|
}: IQueryKeyQueriesLoadAll): Promise<IQueriesResponse> => {
|
|
const { QUERIES } = endpoints;
|
|
|
|
const snakeCaseParams = convertParamsToSnakeCase({
|
|
teamId,
|
|
page,
|
|
perPage,
|
|
query,
|
|
orderDirection,
|
|
orderKey,
|
|
mergeInherited,
|
|
platform,
|
|
});
|
|
|
|
// API expects "macos" instead of "darwin"
|
|
if (snakeCaseParams.platform === "darwin") {
|
|
snakeCaseParams.platform = "macos";
|
|
}
|
|
|
|
const queryString = buildQueryStringFromParams(snakeCaseParams);
|
|
|
|
return sendRequest(
|
|
"GET",
|
|
queryString ? QUERIES.concat(`?${queryString}`) : QUERIES
|
|
);
|
|
},
|
|
run: async ({
|
|
query,
|
|
queryId,
|
|
selected,
|
|
}: {
|
|
query: string;
|
|
queryId: number | null;
|
|
selected: ISelectedTargetsForApi;
|
|
}) => {
|
|
const { LIVE_QUERY } = endpoints;
|
|
|
|
try {
|
|
const { campaign } = await sendRequest("POST", LIVE_QUERY, {
|
|
query,
|
|
query_id: queryId,
|
|
selected,
|
|
});
|
|
return campaign;
|
|
} catch (e) {
|
|
throw new Error(
|
|
getErrorReason(e) || `run query: parse server error ${e}`
|
|
);
|
|
}
|
|
},
|
|
update: (id: number, updateParams: IModifyQueryRequestBody) => {
|
|
const { QUERIES } = endpoints;
|
|
const path = `${QUERIES}/${id}`;
|
|
if (updateParams.name) {
|
|
updateParams.name = updateParams.name.trim();
|
|
}
|
|
|
|
return sendRequest("PATCH", path, updateParams);
|
|
},
|
|
};
|