fleet/frontend/pages/queries/edit/components/SaveAsNewQueryModal/SaveAsNewQueryModal.tsx

234 lines
6.3 KiB
TypeScript

import React, { useCallback, useContext, useState } from "react";
import { InjectedRouter } from "react-router";
import { Location } from "history";
import { AppContext } from "context/app";
import PATHS from "router/paths";
import { getPathWithQueryParams } from "utilities/url";
import { ICreateQueryRequestBody } from "interfaces/schedulable_query";
import queryAPI from "services/entities/queries";
import { NotificationContext } from "context/notification";
import { getErrorReason } from "interfaces/errors";
import {
INVALID_PLATFORMS_FLASH_MESSAGE,
INVALID_PLATFORMS_REASON,
} from "utilities/constants";
import {
API_ALL_TEAMS_ID,
APP_CONTEXT_ALL_TEAMS_ID,
ITeamSummary,
} from "interfaces/team";
import Modal from "components/Modal";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import TeamsDropdown from "components/TeamsDropdown";
import { useTeamIdParam } from "hooks/useTeamIdParam";
const baseClass = "save-as-new-query-modal";
interface ISaveAsNewQueryModal {
router: InjectedRouter;
location: Location;
initialQueryData: ICreateQueryRequestBody;
hostId?: number;
onExit: () => void;
}
interface ISANQFormData {
queryName: string;
team: Partial<ITeamSummary>;
}
interface ISANQFormErrors {
queryName?: string;
team?: string;
}
const validateFormData = (formData: ISANQFormData): ISANQFormErrors => {
const errors: ISANQFormErrors = {};
if (!formData.queryName || formData.queryName.trim() === "") {
errors.queryName = "Name must be present";
}
return errors;
};
const SaveAsNewQueryModal = ({
router,
location,
initialQueryData,
hostId,
onExit,
}: ISaveAsNewQueryModal) => {
const { renderFlash } = useContext(NotificationContext);
const { isPremiumTier } = useContext(AppContext);
const [formData, setFormData] = useState<ISANQFormData>({
queryName: `Copy of ${initialQueryData.name}`,
team: {
id: initialQueryData.team_id,
name: undefined,
},
});
const [isSaving, setIsSaving] = useState(false);
const [formErrors, setFormErrors] = useState<ISANQFormErrors>({});
const { userTeams } = useTeamIdParam({
router,
location,
includeAllTeams: true,
includeNoTeam: false,
permittedAccessByTeamRole: {
admin: true,
maintainer: true,
observer: false,
observer_plus: false,
technician: false,
},
});
const onInputChange = useCallback(
({
name,
value,
}: {
name: string;
value: string | Partial<ITeamSummary>;
}) => {
const newFormData = { ...formData, [name]: value };
setFormData(newFormData);
const newErrors = validateFormData(newFormData);
const errsToSet: ISANQFormErrors = {};
Object.keys(formErrors).forEach((k) => {
if (k in newErrors) {
errsToSet[k as keyof ISANQFormErrors] =
newErrors[k as keyof ISANQFormErrors];
}
});
setFormErrors(errsToSet);
},
[formData, formErrors]
);
const onInputBlur = () => {
setFormErrors(validateFormData(formData));
};
const onTeamChange = useCallback(
(teamId: number) => {
const selectedTeam = userTeams?.find((team) => team.id === teamId);
setFormData((prevData) => ({
...prevData,
team: {
id: teamId,
name: selectedTeam ? selectedTeam.name : undefined,
},
}));
},
[userTeams]
);
// take all existing data for query from parent, allow editing name and team
const handleSave = async (evt: React.FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const errors = validateFormData(formData);
if (Object.keys(errors).length > 0) {
setFormErrors(errors);
return;
}
setIsSaving(true);
const {
queryName,
team: { id: teamId, name: teamName },
} = formData;
const createBody = {
...initialQueryData,
name: queryName,
team_id: teamId === APP_CONTEXT_ALL_TEAMS_ID ? API_ALL_TEAMS_ID : teamId,
};
try {
const { query: newQuery } = await queryAPI.create(createBody);
setIsSaving(false);
renderFlash("success", `Successfully added report ${newQuery.name}.`);
router.push(
getPathWithQueryParams(PATHS.QUERY_DETAILS(newQuery.id), {
fleet_id: newQuery.team_id,
host_id: hostId,
})
);
} catch (createError: unknown) {
let errFlash = "Could not create report. Please try again.";
const reason = getErrorReason(createError);
if (reason.includes("already exists")) {
let teamText;
if (teamId !== APP_CONTEXT_ALL_TEAMS_ID) {
teamText = teamName ? `the ${teamName} fleet` : "this fleet";
} else {
teamText = "all fleets";
}
errFlash = `A report called "${queryName}" already exists for ${teamText}.`;
} else if (reason.includes(INVALID_PLATFORMS_REASON)) {
errFlash = INVALID_PLATFORMS_FLASH_MESSAGE;
}
setIsSaving(false);
renderFlash("error", errFlash);
}
};
return (
<Modal title="Save as new" onExit={onExit}>
<form onSubmit={handleSave} className={baseClass}>
<InputField
name="queryName"
onChange={onInputChange}
onBlur={onInputBlur}
value={formData.queryName}
error={formErrors.queryName}
inputClassName={`${baseClass}__name`}
label="Name"
autofocus
ignore1password
parseTarget
/>
{isPremiumTier && (userTeams?.length || 0) > 1 && (
<div className="form-field">
<div className="form-field__label">Fleet</div>
<TeamsDropdown
asFormField
currentUserTeams={userTeams || []}
selectedTeamId={formData.team.id}
onChange={onTeamChange}
/>
</div>
)}
<div className="modal-cta-wrap">
<Button
type="submit"
className="save-as-new-query"
isLoading={isSaving}
// empty SQL error handled by parent
disabled={Object.keys(formErrors).length > 0 || isSaving}
>
Save
</Button>
<Button onClick={onExit} variant="inverse">
Cancel
</Button>
</div>
</form>
</Modal>
);
};
export default SaveAsNewQueryModal;