client/app/pages/queries/hooks/useUpdateQuery.jsx (116 lines of code) (raw):

import { isNil, isObject, extend, keys, map, omit, pick, uniq, get } from "lodash"; import React, { useCallback } from "react"; import Modal from "antd/lib/modal"; import { Query } from "@/services/query"; import notification from "@/services/notification"; import useImmutableCallback from "@/lib/hooks/useImmutableCallback"; class SaveQueryError extends Error { constructor(message, detailedMessage = null) { super(message); this.detailedMessage = detailedMessage; } } class SaveQueryConflictError extends SaveQueryError { constructor() { super( "Changes not saved", <React.Fragment> <div className="m-b-5">It seems like the query has been modified by another user.</div> <div>Please copy/backup your changes and reload this page.</div> </React.Fragment> ); } } function confirmOverwrite() { return new Promise((resolve, reject) => { Modal.confirm({ title: "Overwrite Query", content: ( <React.Fragment> <div className="m-b-5">It seems like the query has been modified by another user.</div> <div>Are you sure you want to overwrite the query with your version?</div> </React.Fragment> ), okText: "Overwrite", okType: "danger", onOk: () => { resolve(); }, onCancel: () => { reject(); }, maskClosable: true, autoFocusButton: null, }); }); } function doSaveQuery(data, { canOverwrite = false } = {}) { // omit parameter properties that don't need to be stored if (isObject(data.options) && data.options.parameters) { data.options = { ...data.options, parameters: map(data.options.parameters, p => p.toSaveableObject()), }; } return Query.save(data).catch(error => { if (get(error, "response.status") === 409) { if (canOverwrite) { return confirmOverwrite() .then(() => Query.save(omit(data, ["version"]))) .catch(() => Promise.reject(new SaveQueryConflictError())); } return Promise.reject(new SaveQueryConflictError()); } return Promise.reject(new SaveQueryError("Query could not be saved")); }); } export default function useUpdateQuery(query, onChange) { const handleChange = useImmutableCallback(onChange); return useCallback( (data = null, { successMessage = "Query saved" } = {}) => { if (isObject(data)) { // Don't save new query with partial data if (query.isNew()) { handleChange(extend(query.clone(), data)); return; } data = { ...data, id: query.id, version: query.version }; } else { data = pick(query, [ "id", "version", "schedule", "query", "description", "name", "data_source_id", "options", "latest_query_data_id", "is_draft", ]); } return doSaveQuery(data, { canOverwrite: query.can_edit }) .then(updatedQuery => { if (!isNil(successMessage)) { notification.success(successMessage); } handleChange( extend( query.clone(), // if server returned completely new object (currently possible only when saving new query) - // update all fields; otherwise pick only changed fields updatedQuery.id !== query.id ? updatedQuery : pick(updatedQuery, uniq(["id", "version", ...keys(data)])) ) ); }) .catch(error => { const notificationOptions = {}; if (error instanceof SaveQueryConflictError) { notificationOptions.duration = null; } notification.error(error.message, error.detailedMessage, notificationOptions); }); }, [query, handleChange] ); }