From 6041ba2f6a447ece852f1bc21c2a431507e017f8 Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Sun, 8 Nov 2020 09:09:54 +0100 Subject: [PATCH] Error reporting on failed tag changes, deletion is now recursive --- .../windows/manage_tags/ManageTagsWindow.tsx | 52 ++++++++++++++++--- server/endpoints/DeleteTagEndpointHandler.ts | 38 +++++++++----- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/client/src/components/windows/manage_tags/ManageTagsWindow.tsx b/client/src/components/windows/manage_tags/ManageTagsWindow.tsx index 87dbb4e..d9e48ea 100644 --- a/client/src/components/windows/manage_tags/ManageTagsWindow.tsx +++ b/client/src/components/windows/manage_tags/ManageTagsWindow.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, ReactFragment } from 'react'; import { WindowState, newWindowReducer, WindowType } from '../Windows'; import { Box, Typography, Chip, IconButton, useTheme, Button } from '@material-ui/core'; import LoyaltyIcon from '@material-ui/icons/Loyalty'; @@ -12,6 +12,7 @@ import { v4 as genUuid } from 'uuid'; import { MainWindowStateActions } from '../../MainWindow'; import LocalOfferIcon from '@material-ui/icons/LocalOffer'; import { songGetters } from '../../../lib/songGetters'; +import Alert from '@material-ui/lab/Alert'; var _ = require('lodash'); export interface ManageTagsWindowState extends WindowState { @@ -20,12 +21,14 @@ export interface ManageTagsWindowState extends WindowState { // to the database. fetchedTags: Record | null, pendingChanges: TagChange[], + alert: ReactFragment | null, // For notifications such as errors } export enum ManageTagsWindowActions { SetFetchedTags = "SetFetchedTags", SetPendingChanges = "SetPendingChanges", Reset = "Reset", + SetAlert = "SetAlert", } export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: any) { @@ -45,6 +48,12 @@ export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: an ...state, pendingChanges: [], fetchedTags: null, + alert: null, + } + case ManageTagsWindowActions.SetAlert: + return { + ...state, + alert: action.value, } default: throw new Error("Unimplemented ManageTagsWindow state update.") @@ -194,6 +203,10 @@ export function SingleTag(props: { } ] }) + props.dispatch({ + type: ManageTagsWindowActions.SetAlert, + value: null, + }) }} onDelete={() => { props.dispatch({ @@ -206,6 +219,10 @@ export function SingleTag(props: { } ] }) + props.dispatch({ + type: ManageTagsWindowActions.SetAlert, + value: null, + }) }} onMove={(to: string | null) => { props.dispatch({ @@ -219,6 +236,10 @@ export function SingleTag(props: { } ] }) + props.dispatch({ + type: ManageTagsWindowActions.SetAlert, + value: null, + }) }} onMergeInto={(into: string) => { props.dispatch({ @@ -232,6 +253,10 @@ export function SingleTag(props: { } ] }) + props.dispatch({ + type: ManageTagsWindowActions.SetAlert, + value: null, + }) }} tag={tag} changedTags={props.changedTags} @@ -382,21 +407,36 @@ export default function ManageTagsWindow(props: { > props.dispatch({ - type: ManageTagsWindowActions.SetPendingChanges, - value: [], - })} + onDiscard={() => { + props.dispatch({ + type: ManageTagsWindowActions.SetPendingChanges, + value: [], + }) + props.dispatch({ + type: ManageTagsWindowActions.SetAlert, + value: null, + }) + }} onSave={() => { submitTagChanges(props.state.pendingChanges).then(() => { - console.log("reset!") props.dispatch({ type: ManageTagsWindowActions.Reset }); + }).catch((e: Error) => { + props.dispatch({ + type: ManageTagsWindowActions.SetAlert, + value: Failed to save changes: {e.message}, + }) }) }} getTagDetails={(id: string) => tagsWithChanges[id]} /> } + {props.state.alert && {props.state.alert}} r.id); + + const indirectChildrenPromises = directChildren.map( + (child: number) => getChildrenRecursive(child, trx) + ); + const indirectChildrenNested = await Promise.all(indirectChildrenPromises); + const indirectChildren = indirectChildrenNested.flat(); + + return [ + ...directChildren, + ...indirectChildren, + ] +} + export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { if (!api.checkDeleteTagRequest(req)) { const e: EndpointError = { @@ -17,9 +34,9 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a await knex.transaction(async (trx) => { try { // Start retrieving any child tags. - const childTagsPromise = trx.select('id') - .from('tags') - .where({ 'parentId': req.params.id }); + + const childTagsPromise = + getChildrenRecursive(req.params.id, trx); // Start retrieving the tag itself. const tagPromise = trx.select('id') @@ -30,14 +47,9 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a // Wait for the requests to finish. var [tag, children] = await Promise.all([tagPromise, childTagsPromise]); - // If there are any children, refuse the deletion - if(children && children.length > 0) { - const e: EndpointError = { - internalMessage: 'Invalid DeleteTag request: tag ' + req.params.id.toString() + " has children", - httpStatus: 400 - }; - throw e; - } + // Merge all IDs. + const toDelete = [ tag, ...children ]; + console.log ("deleting tags: ", toDelete); // Check that we found all objects we need. if (!tag) { @@ -48,9 +60,9 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a throw e; } - // Delete the tag. + // Delete the tag and its children. await trx('tags') - .where({ 'id': req.params.id }) + .whereIn('id', toDelete) .del(); // Respond to the request.