From e8c043b08d7da08731fa996b215d24c9dbb56112 Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Sun, 24 Oct 2021 20:33:13 +0200 Subject: [PATCH] Fix tag queries. --- client/src/api/endpoints/data.ts | 6 +-- client/src/api/endpoints/query.ts | 4 +- client/src/api/endpoints/resources.ts | 12 ++--- client/src/api/types/resources.ts | 9 +++- .../components/querybuilder/QBAddElemMenu.tsx | 38 ++++++++------ .../components/querybuilder/QBNodeElem.tsx | 4 +- .../components/querybuilder/QBPlaceholder.tsx | 1 + .../components/querybuilder/QueryBuilder.tsx | 10 ++-- .../components/windows/query/QueryWindow.tsx | 19 +++++-- client/src/lib/query/Query.tsx | 50 ++++++++++--------- server/db/Album.ts | 8 +-- server/db/Artist.ts | 8 +-- server/db/Data.ts | 4 +- server/db/Tag.ts | 12 ++--- server/db/Track.ts | 8 +-- 15 files changed, 108 insertions(+), 85 deletions(-) diff --git a/client/src/api/endpoints/data.ts b/client/src/api/endpoints/data.ts index 87ffa5f..5e733f4 100644 --- a/client/src/api/endpoints/data.ts +++ b/client/src/api/endpoints/data.ts @@ -7,7 +7,7 @@ // Upon import, they might be replaced, and upon export, they might be randomly // generated. -import { Album, Id, AlbumRefs, Artist, ArtistRefs, Tag, TagRefs, Track, TrackRefs, isTrackRefs, isAlbumRefs, isArtistRefs, isTagRefs } from "../types/resources"; +import { Album, Id, AlbumRefs, Artist, ArtistRefs, Tag, TagParentId, Track, TrackRefs, isTrackRefs, isAlbumRefs, isArtistRefs, isTagParentId } from "../types/resources"; // The import/export DB format is just a set of lists of objects. // Each object has an ID and references others by ID. @@ -17,7 +17,7 @@ export interface DBDataFormat { tracks: (Track & Id & TrackRefs)[], albums: (Album & Id & AlbumRefs)[], artists: (Artist & Id & ArtistRefs)[], - tags: (Tag & Id & TagRefs)[], + tags: (Tag & Id & TagParentId)[], } // Get a full export of a user's database (GET). @@ -49,7 +49,7 @@ export const checkDBImportRequest: (v: any) => boolean = (v: any) => { return prev && isArtistRefs(cur); }, true) && v.tags.reduce((prev: boolean, cur: any) => { - return prev && isTagRefs(cur); + return prev && isTagParentId(cur); }, true); } diff --git a/client/src/api/endpoints/query.ts b/client/src/api/endpoints/query.ts index f7b2efa..702938e 100644 --- a/client/src/api/endpoints/query.ts +++ b/client/src/api/endpoints/query.ts @@ -1,6 +1,6 @@ // Query for items (POST). -import { Album, Id, Artist, Tag, Track, Name, StoreLinks, TagRefs, AlbumRefs, TrackDetails, ArtistDetails } from "../types/resources"; +import { Album, Id, Artist, Tag, Track, Name, StoreLinks, TagParentId, AlbumRefs, TrackDetails, ArtistDetails } from "../types/resources"; export const QueryEndpoint = '/query'; @@ -79,7 +79,7 @@ export interface QueryRequest { // Query response structure export type QueryResponseTrackDetails = (Track & Name & StoreLinks & TrackDetails & Id); export type QueryResponseArtistDetails = (Artist & Name & StoreLinks & Id); -export type QueryResponseTagDetails = (Tag & Name & TagRefs & Id); +export type QueryResponseTagDetails = (Tag & Name & TagParentId & Id); export type QueryResponseAlbumDetails = (Album & Name & StoreLinks & Id); export interface QueryResponse { tracks: QueryResponseTrackDetails[] | number[] | number, // Details | IDs | count, depending on QueryResponseType diff --git a/client/src/api/endpoints/resources.ts b/client/src/api/endpoints/resources.ts index 79259e3..1062244 100644 --- a/client/src/api/endpoints/resources.ts +++ b/client/src/api/endpoints/resources.ts @@ -12,11 +12,11 @@ import { TrackRefs, ArtistRefs, AlbumRefs, - TagRefs, + TagParentId, isTrackRefs, isAlbumRefs, isArtistRefs, - isTagRefs, + isTagParentId, isName, Name, isTrack, @@ -83,9 +83,9 @@ export const checkPostAlbumRequest: (v: any) => boolean = (v: any) => isAlbumRef // Post new tag (POST). export const PostTagEndpoint = "/tag"; -export type PostTagRequest = (Tag & TagRefs & Name); +export type PostTagRequest = (Tag & TagParentId & Name); export interface PostTagResponse { id: number }; -export const checkPostTagRequest: (v: any) => boolean = (v: any) => isTagRefs(v) && isName(v); +export const checkPostTagRequest: (v: any) => boolean = (v: any) => isTagParentId(v) && isName(v); // Post new integration (POST). export const PostIntegrationEndpoint = "/integration"; @@ -113,9 +113,9 @@ export const checkPutAlbumRequest: (v: any) => boolean = (v: any) => isAlbumRefs // Replace tag (PUT). export const PutTagEndpoint = "/tag/:id"; -export type PutTagRequest = (Tag & TagRefs); +export type PutTagRequest = (Tag & TagParentId); export type PutTagResponse = void; -export const checkPutTagRequest: (v: any) => boolean = (v: any) => isTagRefs(v) && isName(v);; +export const checkPutTagRequest: (v: any) => boolean = (v: any) => isTagParentId(v) && isName(v);; // Replace integration (PUT). export const PutIntegrationEndpoint = "/integration/:id"; diff --git a/client/src/api/types/resources.ts b/client/src/api/types/resources.ts index e4c11fe..d9f0a1f 100644 --- a/client/src/api/types/resources.ts +++ b/client/src/api/types/resources.ts @@ -147,12 +147,17 @@ export interface Tag { id?: number, parentId?: number | null, parent?: (Tag & Id) | null, + childIds?: number[], } -export interface TagRefs { +export interface TagParentId { parentId: number | null, } +export interface TagChildIds { + childIds: number[], +} + export interface TagDetails { parent: (Tag & Id) | null, } @@ -161,7 +166,7 @@ export function isTag(q: any): q is Tag { return q.mbApi_typename && q.mbApi_typename === "tag"; } -export function isTagRefs(q: any): q is TagRefs { +export function isTagParentId(q: any): q is TagParentId { return isTag(q) && 'parentId' in q; } diff --git a/client/src/components/querybuilder/QBAddElemMenu.tsx b/client/src/components/querybuilder/QBAddElemMenu.tsx index 8e0118f..c5ba8f9 100644 --- a/client/src/components/querybuilder/QBAddElemMenu.tsx +++ b/client/src/components/querybuilder/QBAddElemMenu.tsx @@ -3,7 +3,7 @@ import { Menu, MenuItem } from '@material-ui/core'; import NestedMenuItem from "material-ui-nested-menu-item"; import { QueryElem, QueryLeafBy, QueryLeafOp, TagQueryInfo } from '../../lib/query/Query'; import QBSelectWithRequest from './QBSelectWithRequest'; -import { Requests } from './QueryBuilder'; +import { Requests, QueryBuilderTag } from './QueryBuilder'; export interface MenuProps { anchorEl: null | HTMLElement, @@ -12,19 +12,19 @@ export interface MenuProps { requestFunctions: Requests, } -export function createTagInfo(tag: any, allTags: any[]): TagQueryInfo { - const resolveName: (t: any) => string[] = (t: any) => { +export function createTagInfo(tag: QueryBuilderTag, allTags: QueryBuilderTag[]): TagQueryInfo { + const resolveName: (t: QueryBuilderTag) => string[] = (t: QueryBuilderTag) => { if (t.parentId) { - const parent = allTags.filter((o: any) => o.tagId === t.parentId)[0]; - return [resolveName(parent), t.name]; + const parent = allTags.filter((o: QueryBuilderTag) => o.id === t.parentId)[0]; + return resolveName(parent).concat(t.name); } return [t.name]; } - const resolveChildren: (t: any) => Set = (t: any) => { + const resolveChildren: (t: QueryBuilderTag) => Set = (t: QueryBuilderTag) => { if (t.childIds.length > 0) { - const childSets: Set[] = allTags.filter((o: any) => t.childIds.includes(o.tagId)) - .map((child: any) => resolveChildren(child)); + const childSets: Set[] = allTags.filter((o: QueryBuilderTag) => t.childIds.includes(o.id)) + .map((child: QueryBuilderTag) => resolveChildren(child)); var r = new Set(); childSets.forEach((c: any) => { @@ -33,7 +33,7 @@ export function createTagInfo(tag: any, allTags: any[]): TagQueryInfo { return r; } - return new Set([t.tagId]); + return new Set([t.id]); } return { @@ -47,13 +47,14 @@ export function QBAddElemMenu(props: MenuProps) { let onClose = props.onClose; interface TagItemProps { - tag: any, - allTags: any[], + tag: QueryBuilderTag, + allTags: QueryBuilderTag[], } const TagItem = (_props: TagItemProps) => { if (_props.tag.childIds.length > 0) { const children = _props.allTags.filter( - (tag: any) => _props.tag.childIds.includes(tag.tagId) + (tag: QueryBuilderTag) => + _props.tag.childIds.includes(tag.id) ); return - {children.map((child: any) => )} + {children.map((child: QueryBuilderTag) => )} } return { + + console.log("onCreateQuery: adding:",{ + a: QueryLeafBy.TagInfo, + leafOp: QueryLeafOp.Equals, + b: createTagInfo(_props.tag, _props.allTags), + } ); + onClose(); props.onCreateQuery({ a: QueryLeafBy.TagInfo, @@ -87,7 +95,7 @@ export function QBAddElemMenu(props: MenuProps) { } const BaseTagsItem = (_props: any) => { - const [tags, setTags] = useState(null); + const [tags, setTags] = useState(null); useEffect(() => { (async () => { @@ -97,7 +105,7 @@ export function QBAddElemMenu(props: MenuProps) { return tags ? <> - {tags.filter((tag: any) => !tag.parentId).map((tag: any) => { + {tags.filter((tag: QueryBuilderTag) => !tag.parentId).map((tag: QueryBuilderTag) => { return })} diff --git a/client/src/components/querybuilder/QBNodeElem.tsx b/client/src/components/querybuilder/QBNodeElem.tsx index c922799..248c53c 100644 --- a/client/src/components/querybuilder/QBNodeElem.tsx +++ b/client/src/components/querybuilder/QBNodeElem.tsx @@ -22,7 +22,9 @@ export function QBNodeElem(props: NodeProps) { } else { ops.splice(idx, 1); } - let newNode = simplify({ operands: ops, nodeOp: e.nodeOp }, null); + let newq = { operands: ops, nodeOp: e.nodeOp }; + console.log("onReplace:", newq, simplify(newq, null)); + let newNode = simplify(newq, null); props.onReplace(newNode); } diff --git a/client/src/components/querybuilder/QBPlaceholder.tsx b/client/src/components/querybuilder/QBPlaceholder.tsx index 00847c0..1d8ab58 100644 --- a/client/src/components/querybuilder/QBPlaceholder.tsx +++ b/client/src/components/querybuilder/QBPlaceholder.tsx @@ -19,6 +19,7 @@ export function QBPlaceholder(props: IProps & any) { setAnchorEl(null); }; const onCreate = (q: QueryElem) => { + console.log("Replacing placeholder by:", q); props.onReplace(q); }; diff --git a/client/src/components/querybuilder/QueryBuilder.tsx b/client/src/components/querybuilder/QueryBuilder.tsx index de49e44..aceffd3 100644 --- a/client/src/components/querybuilder/QueryBuilder.tsx +++ b/client/src/components/querybuilder/QueryBuilder.tsx @@ -3,19 +3,15 @@ import { Box } from '@material-ui/core'; import QBQueryButton from './QBEditButton'; import { QBQueryElem } from './QBQueryElem'; import { QueryElem, addPlaceholders, removePlaceholders, simplify } from '../../lib/query/Query'; +import { Tag, TagChildIds, TagParentId, Name, Id } from '../../api/api'; -export interface TagItem { - name: string, - id: number, - childIds: number[], - parentId?: number, -} +export type QueryBuilderTag = (Tag & TagChildIds & TagParentId & Name & Id); export interface Requests { getArtists: (filter: string) => Promise, getAlbums: (filter: string) => Promise, getTrackNames: (filter: string) => Promise, - getTags: () => Promise, + getTags: () => Promise, } export interface IProps { diff --git a/client/src/components/windows/query/QueryWindow.tsx b/client/src/components/windows/query/QueryWindow.tsx index 6e56618..a077409 100644 --- a/client/src/components/windows/query/QueryWindow.tsx +++ b/client/src/components/windows/query/QueryWindow.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useReducer, useCallback } from 'react'; import { Box, LinearProgress, Typography } from '@material-ui/core'; import { QueryElem, QueryLeafBy, QueryLeafElem, QueryLeafOp } from '../../../lib/query/Query'; -import QueryBuilder from '../../querybuilder/QueryBuilder'; +import QueryBuilder, { QueryBuilderTag } from '../../querybuilder/QueryBuilder'; import { AlbumsTable, ArtistsTable, ColumnType, ItemsTable, TracksTable } from '../../tables/ResultsTable'; import { queryArtists, queryTracks, queryAlbums, queryTags } from '../../../lib/backend/queries'; import { WindowState } from '../Windows'; @@ -87,13 +87,22 @@ async function getTrackNames(filter: string) { return [...(new Set([...(tracks.map((s: any) => s.name))]))]; } -async function getTagItems(): Promise { - let tags: any = await queryTags( +async function getTagItems(): Promise { + let tags: QueryResponseTagDetails[] = (await queryTags( undefined, 0, -1, QueryResponseType.Details - ); + )) as QueryResponseTagDetails[]; + + // We need to resolve the child ids. + let tags_with_children : QueryBuilderTag[] = tags.map((t: QueryResponseTagDetails) => { + return { + ...t, + childIds: tags.filter((t2: QueryResponseTagDetails) => t2.parentId === t.id) + .map((t2: QueryResponseTagDetails) => t2.id) + } + }) - return tags; + return tags_with_children; } export interface FireNewQueriesData { diff --git a/client/src/lib/query/Query.tsx b/client/src/lib/query/Query.tsx index 401daad..4db416f 100644 --- a/client/src/lib/query/Query.tsx +++ b/client/src/lib/query/Query.tsx @@ -1,28 +1,29 @@ import * as serverApi from '../../api/api'; export enum QueryFor { - Artists = 0, - Albums, - Tags, - Tracks, + Artists = "artists", + Albums = "albums", + Tags = "tags", + Tracks = "tracks", } export enum QueryLeafBy { - ArtistName = 0, - ArtistId, - AlbumName, - AlbumId, - TagInfo, - TagId, - TrackName, - TrackId, - StoreLinks, + ArtistName = "artistName", + ArtistId = "artistId", + AlbumName = "albumName", + AlbumId = "albumId", + TagInfo = "tagInfo", + TagId = "tagId", + TrackName = "trackName", + TrackId = "trackId", + StoreLinks = "storeLinks", + NotApplicable = "n/a", // Some query nodes don't need an operand. } export enum QueryLeafOp { - Equals = 0, - Like, - Placeholder, // Special op which indicates that this leaf is not filled in yet. + Equals = "equals", + Like = "like", + Placeholder = "placeholder", // Special op which indicates that this leaf is not filled in yet. } export interface TagQueryInfo { @@ -98,6 +99,7 @@ function mapToServerProperty(l: QueryLeafBy, queryFor: QueryFor | null) : (queryFor == QueryFor.Tracks) ? serverApi.QueryElemProperty.trackStoreLinks : null, [QueryLeafBy.TagInfo]: null, + [QueryLeafBy.NotApplicable]: null, }[l]; } @@ -129,9 +131,9 @@ export function addPlaceholders( inNode: null | QueryNodeOp, ): QueryElem { - const makePlaceholder = () => { + const makePlaceholder : () => QueryElem = () => { return { - a: 0, + a: QueryLeafBy.NotApplicable, leafOp: QueryLeafOp.Placeholder, b: "" } @@ -226,12 +228,12 @@ export function simplify(q: QueryElem | null, queryFor: QueryFor | null): QueryE } // This shouldn't be part of simplification. - if (q && isLeafElem(q)) { - if (mapToServerLeafOp(q.leafOp, queryFor) === null || - mapToServerProperty(q.a, queryFor) === null) { - return null; - } - } + // if (q && isLeafElem(q)) { + // if (mapToServerLeafOp(q.leafOp, queryFor) === null || + // mapToServerProperty(q.a, queryFor) === null) { + // return null; + // } + // } return q; } diff --git a/server/db/Album.ts b/server/db/Album.ts index a5c18d2..565e578 100644 --- a/server/db/Album.ts +++ b/server/db/Album.ts @@ -1,5 +1,5 @@ import Knex from "knex"; -import { Album, AlbumRefs, Id, Name, AlbumDetails, StoreLinks, Tag, TagRefs, Track, Artist } from "../../client/src/api/api"; +import { Album, AlbumRefs, Id, Name, AlbumDetails, StoreLinks, Tag, TagParentId, Track, Artist } from "../../client/src/api/api"; import * as api from '../../client/src/api/api'; import asJson from "../lib/asJson"; import { DBError, DBErrorKind } from "../endpoints/types"; @@ -14,7 +14,7 @@ export async function getAlbum(id: number, userId: number, knex: Knex): // Start transfers for tracks, tags and artists. // Also request the album itself. - const tagsPromise: Promise<(Tag & Id & Name & TagRefs)[]> = + const tagsPromise: Promise<(Tag & Id & Name & TagParentId)[]> = knex.select('tagId') .from('albums_tags') .where({ 'albumId': id }) @@ -23,8 +23,8 @@ export async function getAlbum(id: number, userId: number, knex: Knex): knex.select(['id', 'name', 'parentId']) .from('tags') .whereIn('id', ids) - .then((tags: (Id & Name & TagRefs)[]) => - tags.map((tag : (Id & Name & TagRefs)) => + .then((tags: (Id & Name & TagParentId)[]) => + tags.map((tag : (Id & Name & TagParentId)) => { return {...tag, mbApi_typename: "tag"}} )) ); diff --git a/server/db/Artist.ts b/server/db/Artist.ts index 58b67e8..567c3b5 100644 --- a/server/db/Artist.ts +++ b/server/db/Artist.ts @@ -1,5 +1,5 @@ import Knex from "knex"; -import { Artist, ArtistDetails, Tag, Track, TagRefs, Id, Name, StoreLinks, Album, ArtistRefs } from "../../client/src/api/api"; +import { Artist, ArtistDetails, Tag, Track, TagParentId, Id, Name, StoreLinks, Album, ArtistRefs } from "../../client/src/api/api"; import * as api from '../../client/src/api/api'; import asJson from "../lib/asJson"; import { DBError, DBErrorKind } from "../endpoints/types"; @@ -11,7 +11,7 @@ export async function getArtist(id: number, userId: number, knex: Knex): Promise<(Artist & ArtistDetails & Name & StoreLinks)> { // Start transfers for tags and albums. // Also request the artist itself. - const tagsPromise: Promise<(Tag & Name & Id & TagRefs)[]> = + const tagsPromise: Promise<(Tag & Name & Id & TagParentId)[]> = knex.select('tagId') .from('artists_tags') .where({ 'artistId': id }) @@ -20,8 +20,8 @@ export async function getArtist(id: number, userId: number, knex: Knex): knex.select(['id', 'name', 'parentId']) .from('tags') .whereIn('id', ids) - .then((tags: (Id & Name & TagRefs)[]) => - tags.map((tag : (Id & Name & TagRefs)) => + .then((tags: (Id & Name & TagParentId)[]) => + tags.map((tag : (Id & Name & TagParentId)) => { return {...tag, mbApi_typename: "tag"}} )) ); diff --git a/server/db/Data.ts b/server/db/Data.ts index ce342a5..9bb7c37 100644 --- a/server/db/Data.ts +++ b/server/db/Data.ts @@ -1,5 +1,5 @@ import Knex from "knex"; -import { Track, TrackRefs, Id, Name, StoreLinks, Album, AlbumRefs, Artist, ArtistRefs, Tag, TagRefs, isTrackRefs, isAlbumRefs, DBImportResponse, IDMappings } from "../../client/src/api/api"; +import { Track, TrackRefs, Id, Name, StoreLinks, Album, AlbumRefs, Artist, ArtistRefs, Tag, TagParentId, isTrackRefs, isAlbumRefs, DBImportResponse, IDMappings } from "../../client/src/api/api"; import * as api from '../../client/src/api/api'; import asJson from "../lib/asJson"; import { createArtist } from "./Artist"; @@ -60,7 +60,7 @@ export async function exportDB(userId: number, knex: Knex): Promise = + let tagsPromise: Promise<(Tag & Id & Name & TagParentId)[]> = knex.select('name', 'parentId', 'id') .from('tags') .where({ 'user': userId }) diff --git a/server/db/Tag.ts b/server/db/Tag.ts index f513288..a1ad25c 100644 --- a/server/db/Tag.ts +++ b/server/db/Tag.ts @@ -1,7 +1,7 @@ import Knex from "knex"; import { isConstructorDeclaration } from "typescript"; import * as api from '../../client/src/api/api'; -import { Tag, TagRefs, TagDetails, Id, Name } from "../../client/src/api/api"; +import { Tag, TagParentId, TagDetails, Id, Name } from "../../client/src/api/api"; import { DBError, DBErrorKind } from "../endpoints/types"; import { makeNotFoundError } from "./common"; @@ -35,7 +35,7 @@ export async function getTagChildrenRecursive(id: number, } // Returns the id of the created tag. -export async function createTag(userId: number, tag: (Tag & Name & TagRefs), knex: Knex): Promise { +export async function createTag(userId: number, tag: (Tag & Name & TagParentId), knex: Knex): Promise { return await knex.transaction(async (trx) => { // If applicable, retrieve the parent tag. const maybeMatches: any[] | null = @@ -124,13 +124,13 @@ export async function deleteTag(userId: number, tagId: number, knex: Knex) { } export async function getTag(userId: number, tagId: number, knex: Knex): Promise<(Tag & TagDetails & Name)> { - const tagPromise: Promise<(Tag & Id & Name & TagRefs) | null> = + const tagPromise: Promise<(Tag & Id & Name & TagParentId) | null> = knex.select(['id', 'name', 'parentId']) .from('tags') .where({ 'user': userId }) .where({ 'id': tagId }) - .then((r: (Id & Name & TagRefs)[] | undefined) => r ? r[0] : null) - .then((r: (Id & Name & TagRefs) | null) => { + .then((r: (Id & Name & TagParentId)[] | undefined) => r ? r[0] : null) + .then((r: (Id & Name & TagParentId) | null) => { if (r) { return { ...r, mbApi_typename: 'tag'}; } @@ -139,7 +139,7 @@ export async function getTag(userId: number, tagId: number, knex: Knex): Promise const parentPromise: Promise<(Tag & Id & Name & TagDetails) | null> = tagPromise - .then((r: (Tag & Id & Name & TagRefs) | null) => + .then((r: (Tag & Id & Name & TagParentId) | null) => (r && r.parentId) ? ( getTag(userId, r.parentId, knex) .then((rr: (Tag & Name & TagDetails) | null) => diff --git a/server/db/Track.ts b/server/db/Track.ts index d131c4f..76f6231 100644 --- a/server/db/Track.ts +++ b/server/db/Track.ts @@ -1,5 +1,5 @@ import Knex from "knex"; -import { Track, TrackRefs, TrackDetails, Id, Name, StoreLinks, Tag, Album, Artist, TagRefs } from "../../client/src/api/api"; +import { Track, TrackRefs, TrackDetails, Id, Name, StoreLinks, Tag, Album, Artist, TagParentId } from "../../client/src/api/api"; import * as api from '../../client/src/api/api'; import asJson from "../lib/asJson"; import { DBError, DBErrorKind } from "../endpoints/types"; @@ -11,7 +11,7 @@ export async function getTrack(id: number, userId: number, knex: Knex): Promise { // Start transfers for tracks, tags and artists. // Also request the track itself. - const tagsPromise: Promise<(Tag & Id & Name & TagRefs)[]> = + const tagsPromise: Promise<(Tag & Id & Name & TagParentId)[]> = knex.select('tagId') .from('tracks_tags') .where({ 'trackId': id }) @@ -20,8 +20,8 @@ export async function getTrack(id: number, userId: number, knex: Knex): knex.select(['id', 'name', 'parentId']) .from('tags') .whereIn('id', ids) - .then((tags: (Id & Name & TagRefs)[]) => - tags.map((tag : (Id & Name & TagRefs)) => + .then((tags: (Id & Name & TagParentId)[]) => + tags.map((tag : (Id & Name & TagParentId)) => { return {...tag, mbApi_typename: "tag"}} )) );