From 8be0a2ecc3e9e84ae9e534c47f97a3d3c6d22fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Tamargo?= Date: Thu, 23 Feb 2023 13:07:45 +0200 Subject: [PATCH] MBS-12622: Show if URL rel has pending edits in links editor We now show a warning icon on the (non-URL) relationship editor if a relationship that can be changed has (previous) pending edits. This adds the same warning icon to the URL editor, where nothing was being shown currently. This should help avoid unnecessary URL removals when someone is already setting it as ended, for example. The externalLinks code does slightly things to entity/relationship data to put them into state, so flow typing here is pretty messy - unsure if it can be improved without a proper refactoring of the externalLinks component though. --- .../RelationshipPendingEditsWarning.js | 58 +++++++++++++++++++ root/static/scripts/edit/externalLinks.js | 42 +++++++++++--- .../components/RelationshipItem.js | 28 +-------- .../utility/getOpenEditsLink.js | 21 +++++-- 4 files changed, 112 insertions(+), 37 deletions(-) create mode 100644 root/static/scripts/edit/components/RelationshipPendingEditsWarning.js diff --git a/root/static/scripts/edit/components/RelationshipPendingEditsWarning.js b/root/static/scripts/edit/components/RelationshipPendingEditsWarning.js new file mode 100644 index 00000000000..34194954fcd --- /dev/null +++ b/root/static/scripts/edit/components/RelationshipPendingEditsWarning.js @@ -0,0 +1,58 @@ +/* + * @flow strict-local + * Copyright (C) 2023 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import * as React from 'react'; + +import openEditsForRelIconUrl + from '../../../images/icons/open_edits_for_rel.svg'; +import type { + RelationshipStateT, +} from '../../relationship-editor/types.js'; +import getOpenEditsLink + from '../../relationship-editor/utility/getOpenEditsLink.js'; +import type { + LinkRelationshipT, +} from '../externalLinks.js'; + +import Tooltip from './Tooltip.js'; + + +type PropsT = { + +relationship: LinkRelationshipT | RelationshipStateT, +}; + +const RelationshipPendingEditsWarning = ({ + relationship, +}: PropsT): React$Element | null => { + const hasPendingEdits = relationship.editsPending; + const openEditsLink = getOpenEditsLink(relationship); + + return hasPendingEdits && nonEmpty(openEditsLink) ? ( + <> + {' '} + + } + /> + + ) : null; +}; + +export default RelationshipPendingEditsWarning; diff --git a/root/static/scripts/edit/externalLinks.js b/root/static/scripts/edit/externalLinks.js index f9b8f21dd5d..82aebc89c0a 100644 --- a/root/static/scripts/edit/externalLinks.js +++ b/root/static/scripts/edit/externalLinks.js @@ -42,6 +42,8 @@ import {isMalware} from '../url/utility/isGreyedOut.js'; import ExternalLinkAttributeDialog from './components/ExternalLinkAttributeDialog.js'; import HelpIcon from './components/HelpIcon.js'; +import RelationshipPendingEditsWarning + from './components/RelationshipPendingEditsWarning.js'; import RemoveButton from './components/RemoveButton.js'; import URLInputPopover from './components/URLInputPopover.js'; import withLoadedTypeInfo from './components/withLoadedTypeInfo.js'; @@ -79,6 +81,18 @@ type LinkTypeOptionT = { export type LinkStateT = $ReadOnly<{ ...DatePeriodRoleT, +deleted: boolean, + +editsPending: boolean, + +entity0?: + | CoreEntityT + | { + +entityType: CoreEntityTypeT, + +id?: void, + +isNewEntity?: true, + +name?: string, + +orderingTypeID?: number, + +relationships?: void, + }, + +entity1?: CoreEntityT, +pendingTypes?: $ReadOnlyArray, +rawUrl: string, // New relationships will use a unique string ID like "new-1". @@ -139,7 +153,7 @@ export class _ExternalLinksEditor const sourceData = props.sourceData; const sourceType = sourceData.entityType; const entityTypes = [sourceType, 'url'].sort().join('-'); - let initialLinks = parseRelationships(sourceData.relationships); + let initialLinks = parseRelationships(sourceData); initialLinks.sort(function (a, b) { const typeA = a.type && linkedEntities.link_type[a.type]; @@ -1228,6 +1242,7 @@ const ExternalLinkRelationship = {link.url && !link.error && !hasUrlError ? : null} + {hasDate ? ( {' '} @@ -1541,8 +1556,11 @@ export const ExternalLinksEditor: const defaultLinkState: LinkStateT = { begin_date: EMPTY_PARTIAL_DATE, deleted: false, + editsPending: false, end_date: EMPTY_PARTIAL_DATE, ended: false, + entity0: undefined, + entity1: undefined, rawUrl: '', relationship: null, submitted: false, @@ -1599,15 +1617,18 @@ const isVideoAttribute = (attr: LinkAttrT) => attr.type.gid === VIDEO_ATTRIBUTE_GID; export function parseRelationships( - relationships?: $ReadOnlyArray, ): Array { + const relationships = sourceData?.relationships; if (!relationships) { return []; } @@ -1617,8 +1638,11 @@ export function parseRelationships( accum.push({ begin_date: data.begin_date || EMPTY_PARTIAL_DATE, deleted: false, + editsPending: data.editsPending, end_date: data.end_date || EMPTY_PARTIAL_DATE, ended: data.ended || false, + entity0: sourceData || undefined, + entity1: target, rawUrl: target.name, relationship: data.id, submitted: true, @@ -1736,6 +1760,8 @@ type InitialOptionsT = { +entityType: CoreEntityTypeT, +id?: void, +isNewEntity?: true, + +name?: string, + +orderingTypeID?: number, +relationships?: void, }, }; diff --git a/root/static/scripts/relationship-editor/components/RelationshipItem.js b/root/static/scripts/relationship-editor/components/RelationshipItem.js index 1c12a1db38a..8523a798a91 100644 --- a/root/static/scripts/relationship-editor/components/RelationshipItem.js +++ b/root/static/scripts/relationship-editor/components/RelationshipItem.js @@ -12,8 +12,6 @@ import * as tree from 'weight-balanced-tree'; import openEditsForEntityIconUrl from '../../../images/icons/open_edits_for_entity.svg'; -import openEditsForRelIconUrl - from '../../../images/icons/open_edits_for_rel.svg'; import ButtonPopover from '../../common/components/ButtonPopover.js'; import DescriptiveLink from '../../common/components/DescriptiveLink.js'; import {bracketedText} from '../../common/utility/bracketed.js'; @@ -29,6 +27,8 @@ import { } from '../../common/utility/isLinkTypeDirectionOrderable.js'; import relationshipDateText from '../../common/utility/relationshipDateText.js'; +import RelationshipPendingEditsWarning + from '../../edit/components/RelationshipPendingEditsWarning.js'; import Tooltip from '../../edit/components/Tooltip.js'; import { getPhraseAndExtraAttributesText, @@ -46,7 +46,6 @@ import type { RelationshipEditorActionT, } from '../types/actions.js'; import getLinkPhrase from '../utility/getLinkPhrase.js'; -import getOpenEditsLink from '../utility/getOpenEditsLink.js'; import getRelationshipKey from '../utility/getRelationshipKey.js'; import getRelationshipLinkType from '../utility/getRelationshipLinkType.js'; import getRelationshipStatusName @@ -83,9 +82,7 @@ const RelationshipItem = (React.memo(({ const [sourceCredit, targetCredit] = backward ? [relationship.entity1_credit, relationship.entity0_credit] : [relationship.entity0_credit, relationship.entity1_credit]; - const relHasPendingEdits = relationship.editsPending; const targetHasPendingEdits = Boolean(target.editsPending); - const openEditsLink = getOpenEditsLink(relationship); const isRemoved = relationship._status === REL_STATUS_REMOVE; const removeButtonId = 'remove-relationship-' + getRelationshipKey(relationship); @@ -304,26 +301,7 @@ const RelationshipItem = (React.memo(({ /> ) : null} - {relHasPendingEdits && nonEmpty(openEditsLink) ? ( - <> - {' '} - - } - /> - - ) : null} + {datesAndAttributes} diff --git a/root/static/scripts/relationship-editor/utility/getOpenEditsLink.js b/root/static/scripts/relationship-editor/utility/getOpenEditsLink.js index 9e6119780c9..9aa7b41f8ac 100644 --- a/root/static/scripts/relationship-editor/utility/getOpenEditsLink.js +++ b/root/static/scripts/relationship-editor/utility/getOpenEditsLink.js @@ -1,5 +1,5 @@ /* - * @flow strict + * @flow strict-local * Copyright (C) 2022 MetaBrainz Foundation * * This file is part of MusicBrainz, the open internet music database, @@ -8,15 +8,28 @@ */ import isDatabaseRowId from '../../common/utility/isDatabaseRowId.js'; +import type { + LinkRelationshipT, +} from '../../edit/externalLinks.js'; import type { RelationshipStateT, } from '../types.js'; + export default function getOpenEditsLink( - relationship: RelationshipStateT, + relationship: LinkRelationshipT | RelationshipStateT, ): string | null { const entity0 = relationship.entity0; const entity1 = relationship.entity1; + if (!entity0 || !entity1) { + return null; + } + + const entity0Name = entity0.name; + const entity1Name = entity1.name; + if (empty(entity0Name) || empty(entity1Name)) { + return null; + } if (!isDatabaseRowId(entity0.id) || !isDatabaseRowId(entity1.id)) { return null; @@ -26,11 +39,11 @@ export default function getOpenEditsLink( '/search/edits?auto_edit_filter=&order=desc&negation=0&combinator=and' + `&conditions.0.field=${encodeURIComponent(entity0.entityType)}` + '&conditions.0.operator=%3D' + - `&conditions.0.name=${encodeURIComponent(entity0.name)}` + + `&conditions.0.name=${encodeURIComponent(entity0Name)}` + `&conditions.0.args.0=${encodeURIComponent(String(entity0.id))}` + `&conditions.1.field=${encodeURIComponent(entity1.entityType)}` + '&conditions.1.operator=%3D' + - `&conditions.1.name=${encodeURIComponent(entity1.name)}` + + `&conditions.1.name=${encodeURIComponent(entity1Name)}` + `&conditions.1.args.0=${encodeURIComponent(String(entity1.id))}` + '&conditions.2.field=type' + '&conditions.2.operator=%3D&conditions.2.args=90%2C233' +