Error reporting on failed tag changes, deletion is now recursive

pull/24/head
Sander Vocke 5 years ago
parent 06044c5a51
commit 6041ba2f6a
  1. 52
      client/src/components/windows/manage_tags/ManageTagsWindow.tsx
  2. 38
      server/endpoints/DeleteTagEndpointHandler.ts

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState, ReactFragment } from 'react';
import { WindowState, newWindowReducer, WindowType } from '../Windows'; import { WindowState, newWindowReducer, WindowType } from '../Windows';
import { Box, Typography, Chip, IconButton, useTheme, Button } from '@material-ui/core'; import { Box, Typography, Chip, IconButton, useTheme, Button } from '@material-ui/core';
import LoyaltyIcon from '@material-ui/icons/Loyalty'; import LoyaltyIcon from '@material-ui/icons/Loyalty';
@ -12,6 +12,7 @@ import { v4 as genUuid } from 'uuid';
import { MainWindowStateActions } from '../../MainWindow'; import { MainWindowStateActions } from '../../MainWindow';
import LocalOfferIcon from '@material-ui/icons/LocalOffer'; import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import { songGetters } from '../../../lib/songGetters'; import { songGetters } from '../../../lib/songGetters';
import Alert from '@material-ui/lab/Alert';
var _ = require('lodash'); var _ = require('lodash');
export interface ManageTagsWindowState extends WindowState { export interface ManageTagsWindowState extends WindowState {
@ -20,12 +21,14 @@ export interface ManageTagsWindowState extends WindowState {
// to the database. // to the database.
fetchedTags: Record<string, any> | null, fetchedTags: Record<string, any> | null,
pendingChanges: TagChange[], pendingChanges: TagChange[],
alert: ReactFragment | null, // For notifications such as errors
} }
export enum ManageTagsWindowActions { export enum ManageTagsWindowActions {
SetFetchedTags = "SetFetchedTags", SetFetchedTags = "SetFetchedTags",
SetPendingChanges = "SetPendingChanges", SetPendingChanges = "SetPendingChanges",
Reset = "Reset", Reset = "Reset",
SetAlert = "SetAlert",
} }
export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: any) { export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: any) {
@ -45,6 +48,12 @@ export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: an
...state, ...state,
pendingChanges: [], pendingChanges: [],
fetchedTags: null, fetchedTags: null,
alert: null,
}
case ManageTagsWindowActions.SetAlert:
return {
...state,
alert: action.value,
} }
default: default:
throw new Error("Unimplemented ManageTagsWindow state update.") throw new Error("Unimplemented ManageTagsWindow state update.")
@ -194,6 +203,10 @@ export function SingleTag(props: {
} }
] ]
}) })
props.dispatch({
type: ManageTagsWindowActions.SetAlert,
value: null,
})
}} }}
onDelete={() => { onDelete={() => {
props.dispatch({ props.dispatch({
@ -206,6 +219,10 @@ export function SingleTag(props: {
} }
] ]
}) })
props.dispatch({
type: ManageTagsWindowActions.SetAlert,
value: null,
})
}} }}
onMove={(to: string | null) => { onMove={(to: string | null) => {
props.dispatch({ props.dispatch({
@ -219,6 +236,10 @@ export function SingleTag(props: {
} }
] ]
}) })
props.dispatch({
type: ManageTagsWindowActions.SetAlert,
value: null,
})
}} }}
onMergeInto={(into: string) => { onMergeInto={(into: string) => {
props.dispatch({ props.dispatch({
@ -232,6 +253,10 @@ export function SingleTag(props: {
} }
] ]
}) })
props.dispatch({
type: ManageTagsWindowActions.SetAlert,
value: null,
})
}} }}
tag={tag} tag={tag}
changedTags={props.changedTags} changedTags={props.changedTags}
@ -382,21 +407,36 @@ export default function ManageTagsWindow(props: {
> >
<ControlTagChanges <ControlTagChanges
changes={props.state.pendingChanges} changes={props.state.pendingChanges}
onDiscard={() => props.dispatch({ onDiscard={() => {
type: ManageTagsWindowActions.SetPendingChanges, props.dispatch({
value: [], type: ManageTagsWindowActions.SetPendingChanges,
})} value: [],
})
props.dispatch({
type: ManageTagsWindowActions.SetAlert,
value: null,
})
}}
onSave={() => { onSave={() => {
submitTagChanges(props.state.pendingChanges).then(() => { submitTagChanges(props.state.pendingChanges).then(() => {
console.log("reset!")
props.dispatch({ props.dispatch({
type: ManageTagsWindowActions.Reset type: ManageTagsWindowActions.Reset
}); });
}).catch((e: Error) => {
props.dispatch({
type: ManageTagsWindowActions.SetAlert,
value: <Alert severity="error">Failed to save changes: {e.message}</Alert>,
})
}) })
}} }}
getTagDetails={(id: string) => tagsWithChanges[id]} getTagDetails={(id: string) => tagsWithChanges[id]}
/> />
</Box>} </Box>}
{props.state.alert && <Box
m={1}
mt={4}
width="80%"
>{props.state.alert}</Box>}
<Box <Box
m={1} m={1}
mt={4} mt={4}

@ -2,6 +2,23 @@ import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex'; import Knex from 'knex';
async function getChildrenRecursive(id: number, trx: any) {
const directChildren = (await trx.select('id')
.from('tags')
.where({ 'parentId': id })).map((r: any) => 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) => { export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkDeleteTagRequest(req)) { if (!api.checkDeleteTagRequest(req)) {
const e: EndpointError = { const e: EndpointError = {
@ -17,9 +34,9 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a
await knex.transaction(async (trx) => { await knex.transaction(async (trx) => {
try { try {
// Start retrieving any child tags. // Start retrieving any child tags.
const childTagsPromise = trx.select('id')
.from('tags') const childTagsPromise =
.where({ 'parentId': req.params.id }); getChildrenRecursive(req.params.id, trx);
// Start retrieving the tag itself. // Start retrieving the tag itself.
const tagPromise = trx.select('id') const tagPromise = trx.select('id')
@ -30,14 +47,9 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a
// Wait for the requests to finish. // Wait for the requests to finish.
var [tag, children] = await Promise.all([tagPromise, childTagsPromise]); var [tag, children] = await Promise.all([tagPromise, childTagsPromise]);
// If there are any children, refuse the deletion // Merge all IDs.
if(children && children.length > 0) { const toDelete = [ tag, ...children ];
const e: EndpointError = { console.log ("deleting tags: ", toDelete);
internalMessage: 'Invalid DeleteTag request: tag ' + req.params.id.toString() + " has children",
httpStatus: 400
};
throw e;
}
// Check that we found all objects we need. // Check that we found all objects we need.
if (!tag) { if (!tag) {
@ -48,9 +60,9 @@ export const DeleteTagEndpointHandler: EndpointHandler = async (req: any, res: a
throw e; throw e;
} }
// Delete the tag. // Delete the tag and its children.
await trx('tags') await trx('tags')
.where({ 'id': req.params.id }) .whereIn('id', toDelete)
.del(); .del();
// Respond to the request. // Respond to the request.

Loading…
Cancel
Save