Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MBS-12622: Show if URL / URL relationship has pending edits in links editor #2863

Merged
merged 2 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions root/static/scripts/edit/components/EntityPendingEditsWarning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* @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 openEditsForEntityIconUrl
from '../../../images/icons/open_edits_for_entity.svg';
import entityHref from '../../common/utility/entityHref.js';

import Tooltip from './Tooltip.js';

type PropsT = {
+entity: RelatableEntityT,
};

const EntityPendingEditsWarning = ({
entity,
}: PropsT): React$Element<typeof React.Fragment> | null => {
const hasPendingEdits = Boolean(entity.editsPending);
const openEditsLink = entityHref(entity, '/open_edits');

return hasPendingEdits && nonEmpty(openEditsLink) ? (
<>
{' '}
<Tooltip
content={exp.l(
'This entity has {edits_link|pending edits}.',
{edits_link: openEditsLink},
)}
target={
<img
alt={l('This entity has pending edits.')}
className="info"
height={16}
src={openEditsForEntityIconUrl}
style={{verticalAlign: 'middle'}}
/>
}
/>
</>
) : null;
};

export default EntityPendingEditsWarning;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* @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<typeof React.Fragment> | null => {
const hasPendingEdits = relationship.editsPending;
const openEditsLink = getOpenEditsLink(relationship);

return hasPendingEdits && nonEmpty(openEditsLink) ? (
<>
{' '}
<Tooltip
content={exp.l(
'This relationship has {edit_search|pending edits}.',
{edit_search: openEditsLink},
)}
target={
<img
alt={l('This relationship has pending edits.')}
className="info"
height={16}
src={openEditsForRelIconUrl}
style={{verticalAlign: 'middle'}}
/>
}
/>
</>
) : null;
};

export default RelationshipPendingEditsWarning;
51 changes: 42 additions & 9 deletions root/static/scripts/edit/externalLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ import {
} from '../relationship-editor/utility/prepareHtmlFormSubmission.js';
import {isMalware} from '../url/utility/isGreyedOut.js';

import EntityPendingEditsWarning
from './components/EntityPendingEditsWarning.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';
Expand Down Expand Up @@ -79,6 +83,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<number>,
+rawUrl: string,
// New relationships will use a unique string ID like "new-1".
Expand Down Expand Up @@ -139,7 +155,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];
Expand Down Expand Up @@ -923,7 +939,7 @@ export class _ExternalLinksEditor
* The first element of tuple `item` is not the URL
* when the URL is not submitted therefore isn't grouped.
*/
const {url, rawUrl} = relationships[0];
const {url, rawUrl, entity1} = relationships[0];
const isLastLink = index === linksByUrl.length - 1;
const links = [...relationships];
const linkIndexes = [];
Expand Down Expand Up @@ -1058,6 +1074,7 @@ export class _ExternalLinksEditor
relationships={links}
typeOptions={typeOptions}
url={url}
urlEntity={entity1}
urlMatchesType={urlMatchesType}
validateLink={(link) => this.validateLink(link)}
/>
Expand Down Expand Up @@ -1237,6 +1254,7 @@ const ExternalLinkRelationship =
{link.url && !link.error && !hasUrlError
? <TypeDescription type={link.type} url={link.url} />
: null}
<RelationshipPendingEditsWarning relationship={link} />
{hasDate ? (
<span className="date-period">
{' '}
Expand Down Expand Up @@ -1300,6 +1318,7 @@ type LinkProps = {
+relationships: $ReadOnlyArray<LinkRelationshipT>,
+typeOptions: $ReadOnlyArray<LinkTypeOptionT>,
+url: string,
+urlEntity?: RelatableEntityT,
+urlMatchesType: boolean,
+validateLink: (LinkRelationshipT | LinkStateT) => ErrorT | null,
};
Expand Down Expand Up @@ -1442,6 +1461,9 @@ export class ExternalLink extends React.Component<LinkProps> {
{props.url}
</a>
)}
{props.urlEntity ? (
<EntityPendingEditsWarning entity={props.urlEntity} />
) : null}
{props.url && props.duplicate !== null ? (
<div
className="error field-error"
Expand Down Expand Up @@ -1550,8 +1572,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,
Expand Down Expand Up @@ -1608,15 +1633,18 @@ const isVideoAttribute =
(attr: LinkAttrT) => attr.type.gid === VIDEO_ATTRIBUTE_GID;

export function parseRelationships(
relationships?: $ReadOnlyArray<RelationshipT | {
+id: null,
+linkTypeID?: number,
+target: {
+entityType: 'url',
+name: string,
sourceData?:
| RelatableEntityT
| {
+entityType: RelatableEntityTypeT,
+id?: void,
+isNewEntity?: true,
+name?: string,
+orderingTypeID?: number,
+relationships?: void,
},
}>,
): Array<LinkStateT> {
const relationships = sourceData?.relationships;
if (!relationships) {
return [];
}
Expand All @@ -1626,8 +1654,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,
Expand Down Expand Up @@ -1749,6 +1780,8 @@ type InitialOptionsT = {
+entityType: RelatableEntityTypeT,
+id?: void,
+isNewEntity?: true,
+name?: string,
+orderingTypeID?: number,
+relationships?: void,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@
import * as React from 'react';
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';
import {displayLinkAttributesText}
from '../../common/utility/displayLinkAttribute.js';
import entityHref from '../../common/utility/entityHref.js';
import {
performReactUpdateAndMaintainFocus,
} from '../../common/utility/focusManagement.js';
Expand All @@ -29,7 +24,10 @@ import {
} from '../../common/utility/isLinkTypeDirectionOrderable.js';
import relationshipDateText
from '../../common/utility/relationshipDateText.js';
import Tooltip from '../../edit/components/Tooltip.js';
import EntityPendingEditsWarning
from '../../edit/components/EntityPendingEditsWarning.js';
import RelationshipPendingEditsWarning
from '../../edit/components/RelationshipPendingEditsWarning.js';
import {
getPhraseAndExtraAttributesText,
} from '../../edit/utility/linkPhrase.js';
Expand All @@ -46,7 +44,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
Expand Down Expand Up @@ -83,9 +80,6 @@ const RelationshipItem = (React.memo<PropsT>(({
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);
Expand Down Expand Up @@ -285,46 +279,8 @@ const RelationshipItem = (React.memo<PropsT>(({
})
)
: targetDisplay}
{targetHasPendingEdits ? (
<>
{' '}
<Tooltip
content={exp.l(
'This entity has {edits_link|pending edits}.',
{edits_link: entityHref(target, '/open_edits')},
)}
target={
<img
alt={l('This entity has pending edits.')}
className="info"
height={16}
src={openEditsForEntityIconUrl}
style={{verticalAlign: 'middle'}}
/>
}
/>
</>
) : null}
{relHasPendingEdits && nonEmpty(openEditsLink) ? (
<>
{' '}
<Tooltip
content={exp.l(
'This relationship has {edit_search|pending edits}.',
{edit_search: openEditsLink},
)}
target={
<img
alt={l('This relationship has pending edits.')}
className="info"
height={16}
src={openEditsForRelIconUrl}
style={{verticalAlign: 'middle'}}
/>
}
/>
</>
) : null}
<EntityPendingEditsWarning entity={target} />
<RelationshipPendingEditsWarning relationship={relationship} />
{datesAndAttributes}
</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -8,15 +8,27 @@
*/

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;
Expand All @@ -26,11 +38,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' +
Expand Down