From 404624712aa3373f18af8cbfe9e929fc26245f22 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 special icon on the (non-URL) relationship editor if a relationship that can be changed has (previous) pending edits. This adds the same 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 odd 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..b124af506d1 --- /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.png'; +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 0603953a10c..53212407adb 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?: + | RelatableEntityT + | { + +entityType: RelatableEntityTypeT, + +id?: void, + +isNewEntity?: true, + +name?: string, + +orderingTypeID?: number, + +relationships?: void, + }, + +entity1?: RelatableEntityT, +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]; @@ -1237,6 +1251,7 @@ const ExternalLinkRelationship = {link.url && !link.error && !hasUrlError ? : null} + {hasDate ? ( {' '} @@ -1550,8 +1565,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, @@ -1608,15 +1626,18 @@ const isVideoAttribute = (attr: LinkAttrT) => attr.type.gid === VIDEO_ATTRIBUTE_GID; export function parseRelationships( - relationships?: $ReadOnlyArray, ): Array { + const relationships = sourceData?.relationships; if (!relationships) { return []; } @@ -1626,8 +1647,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, @@ -1749,6 +1773,8 @@ type InitialOptionsT = { +entityType: RelatableEntityTypeT, +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 5f75b5d364a..001e19f48bf 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.png'; -import openEditsForRelIconUrl - from '../../../images/icons/open_edits_for_rel.png'; 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' +