mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Edit Query Page: Fix UX for "Save as new" CTA (#4235)
This commit is contained in:
parent
689de41878
commit
f345446125
1
changes/issue-3202-improve-save-as-new-query
Normal file
1
changes/issue-3202-improve-save-as-new-query
Normal file
@ -0,0 +1 @@
|
||||
* Improved UX around "Save as new" query (Reroutes to new query on save as new, fixes duplicate name error)
|
@ -56,9 +56,18 @@ describe("Query flow (seeded)", () => {
|
||||
cy.getAttached(".ace_scroller")
|
||||
.click()
|
||||
.type("{selectall}SELECT datetime, username FROM windows_crashes;");
|
||||
cy.getAttached(".button--brand.query-form__save").click();
|
||||
cy.getAttached(".query-form__save").click();
|
||||
cy.findByText(/query updated/i).should("be.visible");
|
||||
});
|
||||
it("saves an existing query as new query", () => {
|
||||
cy.getAttached(".name__cell .button--text-link").eq(1).click();
|
||||
cy.findByText(/run query/i).should("exist");
|
||||
cy.getAttached(".ace_scroller")
|
||||
.click()
|
||||
.type("{selectall}SELECT datetime, username FROM windows_crashes;");
|
||||
cy.getAttached(".query-form__save-as-new").click();
|
||||
cy.findByText(/copy of/i).should("be.visible");
|
||||
});
|
||||
it("deletes an existing query", () => {
|
||||
cy.findByText(/detect linux hosts/i)
|
||||
.parent()
|
||||
|
@ -1,4 +1,11 @@
|
||||
import React, { useState, useContext, useEffect, KeyboardEvent } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { push } from "react-router-redux";
|
||||
// @ts-ignore
|
||||
import { renderFlash } from "redux/nodes/notifications/actions";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
|
||||
import { IAceEditor } from "react-ace/lib/types";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { size } from "lodash";
|
||||
@ -9,6 +16,7 @@ import { addGravatarUrlToResource } from "fleet/helpers";
|
||||
// @ts-ignore
|
||||
import { listCompatiblePlatforms, parseSqlTables } from "utilities/sql_tools";
|
||||
|
||||
import queryAPI from "services/entities/queries";
|
||||
import { AppContext } from "context/app";
|
||||
import { QueryContext } from "context/query";
|
||||
import { IQuery, IQueryFormData } from "interfaces/query";
|
||||
@ -68,6 +76,8 @@ const QueryForm = ({
|
||||
renderLiveQueryWarning,
|
||||
backendValidators,
|
||||
}: IQueryFormProps): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isEditMode = !!queryIdForEdit;
|
||||
const [errors, setErrors] = useState<{ [key: string]: any }>({});
|
||||
const [isSaveModalOpen, setIsSaveModalOpen] = useState<boolean>(false);
|
||||
@ -77,6 +87,7 @@ const QueryForm = ({
|
||||
const [isEditingDescription, setIsEditingDescription] = useState<boolean>(
|
||||
false
|
||||
);
|
||||
const [isSaveAsNewLoading, setIsSaveAsNewLoading] = useState<boolean>(false);
|
||||
|
||||
// Note: The QueryContext values should always be used for any mutable query data such as query name
|
||||
// The storedQuery prop should only be used to access immutable metadata such as author id
|
||||
@ -168,7 +179,7 @@ const QueryForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const promptSaveQuery = (forceNew = false) => (
|
||||
const promptSaveAsNewQuery = () => (
|
||||
evt: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
evt.preventDefault();
|
||||
@ -186,7 +197,81 @@ const QueryForm = ({
|
||||
valid = isValidated;
|
||||
|
||||
if (valid) {
|
||||
if (!isEditMode || forceNew) {
|
||||
setIsSaveAsNewLoading(true);
|
||||
|
||||
queryAPI
|
||||
.create({
|
||||
name: lastEditedQueryName,
|
||||
description: lastEditedQueryDescription,
|
||||
query: lastEditedQueryBody,
|
||||
observer_can_run: lastEditedQueryObserverCanRun,
|
||||
})
|
||||
.then((response: { query: IQuery }) => {
|
||||
setIsSaveAsNewLoading(false);
|
||||
dispatch(push(PATHS.EDIT_QUERY(response.query)));
|
||||
dispatch(renderFlash("success", `Successfully added query.`));
|
||||
})
|
||||
.catch((createError: any) => {
|
||||
if (createError.data.errors[0].reason.includes("already exists")) {
|
||||
queryAPI
|
||||
.create({
|
||||
name: `Copy of ${lastEditedQueryName}`,
|
||||
description: lastEditedQueryDescription,
|
||||
query: lastEditedQueryBody,
|
||||
observer_can_run: lastEditedQueryObserverCanRun,
|
||||
})
|
||||
.then((response: { query: IQuery }) => {
|
||||
setIsSaveAsNewLoading(false);
|
||||
dispatch(push(PATHS.EDIT_QUERY(response.query)));
|
||||
dispatch(
|
||||
renderFlash(
|
||||
"success",
|
||||
`Successfully added query as "Copy of ${lastEditedQueryName}".`
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((createCopyError: any) => {
|
||||
if (
|
||||
createCopyError.data.errors[0].reason.includes(
|
||||
"already exists"
|
||||
)
|
||||
) {
|
||||
dispatch(
|
||||
renderFlash(
|
||||
"error",
|
||||
`"Copy of ${lastEditedQueryName}" already exists. Please rename your query and try again.`
|
||||
)
|
||||
);
|
||||
}
|
||||
setIsSaveAsNewLoading(false);
|
||||
});
|
||||
} else {
|
||||
setIsSaveAsNewLoading(false);
|
||||
dispatch(
|
||||
renderFlash("error", "Could not create query. Please try again.")
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const promptSaveQuery = () => (evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
evt.preventDefault();
|
||||
|
||||
if (isEditMode && !lastEditedQueryName) {
|
||||
return setErrors({
|
||||
...errors,
|
||||
name: "Query name must be present",
|
||||
});
|
||||
}
|
||||
|
||||
let valid = true;
|
||||
const { valid: isValidated } = validateQuerySQL(lastEditedQueryBody);
|
||||
|
||||
valid = isValidated;
|
||||
|
||||
if (valid) {
|
||||
if (!isEditMode) {
|
||||
setIsSaveModalOpen(true);
|
||||
} else {
|
||||
onUpdate({
|
||||
@ -374,6 +459,11 @@ const QueryForm = ({
|
||||
const renderForGlobalAdminOrAnyMaintainer = (
|
||||
<>
|
||||
<form className={`${baseClass}__wrapper`} autoComplete="off">
|
||||
{isSaveAsNewLoading && (
|
||||
<div className={`${baseClass}__loading-overlay`}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
<div className={`${baseClass}__title-bar`}>
|
||||
<div className="name-description">
|
||||
{renderName()}
|
||||
@ -420,9 +510,9 @@ const QueryForm = ({
|
||||
<>
|
||||
{isEditMode && (
|
||||
<Button
|
||||
className={`${baseClass}__save`}
|
||||
className={`${baseClass}__save-as-new`}
|
||||
variant="text-link"
|
||||
onClick={promptSaveQuery(true)}
|
||||
onClick={promptSaveAsNewQuery()}
|
||||
disabled={false}
|
||||
>
|
||||
Save as new
|
||||
|
@ -207,4 +207,17 @@
|
||||
display: inline-block;
|
||||
font-size: $large;
|
||||
}
|
||||
|
||||
&__loading-overlay {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ const QueryEditor = ({
|
||||
dispatch(renderFlash("success", "Query updated!"));
|
||||
} catch (updateError: any) {
|
||||
console.error(updateError);
|
||||
if (updateError.errors[0].reason.includes("Duplicate")) {
|
||||
if (updateError.data.errors[0].reason.includes("Duplicate")) {
|
||||
dispatch(
|
||||
renderFlash("error", "A query with this name already exists.")
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user