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 { 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<string, any> | 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: {
>
<ControlTagChanges
changes={props.state.pendingChanges}
onDiscard={() => 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: <Alert severity="error">Failed to save changes: {e.message}</Alert>,
})
})
}}
getTagDetails={(id: string) => tagsWithChanges[id]}
/>
</Box>}
{props.state.alert && <Box
m={1}
mt={4}
width="80%"
>{props.state.alert}</Box>}
<Box
m={1}
mt={4}

@ -2,6 +2,23 @@ import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
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) => {
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.

Loading…
Cancel
Save