GSOC 2021: New URL Relationship Editor: yyoung

Tags: #<Tag:0x00007f510a5716a8> #<Tag:0x00007f510a5715e0>

Personal information

Nickname: yyoung

IRC nick: yyoung

GitHub: y-young

MusicBrainz: yyoung

Timezone: UTC+08:00

E-mail: yyoung2001 AT gmail DOT com

Proposal

Project Overview

On MusicBrainz, URLs are external links attached to entities; relationships define their role and include metadata like date period, they provide extra information about the entity and inspire users to explore further.

Currently, the URL relationship editor on MusicBrainz has many shortcomes. Though it is a relationship, the URL editor has a different UI from other editors. Automatic rules were built to ease the effort, but outdated and buggy rules make them an obstacle in some situations. For better user experience, we would like to enhance the URL relationship editor, to support multiple relationship types for a single link, to provide a unified appearance, better user experience and more flexibility.

Features

  • Allow selecting multiple relationship types for an entered URL, the editor will validate the selections

  • Support auto-selection of multiple relationship types

  • Enable date period and ended flag editing from editing pages of other entities, such as releases and artists

  • A separate field for clean URL to avoid confusion

UI Prototype

On editing pages of entities other than URL, such as ‘Add a release’ page, ‘External Links’ field will be changed to a relationship list similar to that on URL editing page, as follows:

release-edit-2

Each link is shown as an ordered list item, with relationships displayed below. Ended relationships are shown in gray color.

With such a list we’re able to add multiple relationships to a link. (MBS-9902)

Users may input a new URL in the empty box below to add a new link, after adding a new link, an empty box is automatically appended.

When the input box is focused, a bubble is displayed on the right:

This bubble will notify the user of the clean URL produced by URLCleanup.js, original input won’t be overwritten; auto-selected relationship type will be automatically added to the relationship list. (MBS-11391)

When the URL is changed, clean URL will be updated accordingly. To avoid conflicts, if this link already had relationships, auto-selected relationship type won’t be appended.

By clicking the ‘x’ icon next to the input box, this link is removed along with its relationships.

When the user click ‘Add another relationship’ button or the edit icon besides each relationship item, the following dialog will pop up:

relationship-dialog

The help text indicated by ‘?’ icon is identical to current one.

The ‘Type’ select is the same as current relationship type selector: (This prototype is for mock-up purposes so only a subset of items are shown)

As for some relationship types with extra attributes, for example, ‘stream for free’, ‘video’ checkbox will be shown below the select.

If selected relationship type is invalid for this link, an error message will be shown:

relationship-type-error

Unlike current version, in which link type will be displayed as text when automatically determined, the new ‘Type’ select and the checkbox under the error message provides an approach to bypass outdated and bugged rules. More details will be discussed later since it’s moved to stretch goals. (MBS-4684, MBS-9040)

Also, conflicts between relationships are indicated in the list:

Date period and ended flag are associated with each relationship type, they work just the same as other relationships, and since they are shown in the dialog, users will be able to set these attributes from editing page of linked entity, and they can preview the result on the list, just like the URL editing page. (MBS-3774)

Implementations

I will implement a new ExternalLinksEditor in root/static/scripts/edit/externalLinks.js, resembling that on URL editing page in appearance, for displaying links.

Here’s the code for UI prototype:

import * as React from "react";

const ItunesIcon = require("../../images/external-favicons/itunes-16.png");

class RelationshipItem extends React.Component {
  render() {
    return (
      <tr>
        <td />
        <td style={{padding: "0 30px"}}>
          <div
            className={this.props.ended ? "deleted" : undefined}
            style={{ display: "flex" }}
          >
            <span
              className="favicon itunes-favicon"
              style={{
                backgroundImage: ItunesIcon,
                backgroundSize: "16px",
                display: "inline-block",
                width: "16px",
                height: "16px",
                verticalAlign: "middle",
                marginRight: "4px",
              }}
            />
            <div style={{ flex: "3 1" }}>{this.props.name}</div>
            <div style={{ flex: "1 1", textAlign: "right" }}>
              {`(${this.props.beginYear}-${this.props.endYear})`}
            </div>
          </div>
          {this.props.error && (
            <div className="error field-error">
              {"This relationship has conflicts."}
            </div>
          )}
        </td>
        <td>
          <div
            style={{
              position: "relative",
              display: "inline-block",
              minWidth: "30px",
            }}
          >
            <div className="img icon help" />
            <button
              className="icon edit-item"
              title="Edit Relationship"
              type="button"
            />
            <button
              className="nobutton icon remove-item"
              title="Remove Relationship"
              type="button"
            />
          </div>
        </td>
      </tr>
    );
  }
}

RelationshipItem.defaultProps = {
  endYear: "",
  ended: false,
};

class RelationshipList extends React.Component {
  render() {
    return (
      <>
        <RelationshipItem
          beginYear="2016"
          endYear="2020"
          ended
          error="This relationship has conflicts."
          name="purchase for download"
        />
        <RelationshipItem beginYear="2016" endYear="" name="show notes" />
        <tr>
          <td />
          <td>
            <button
              className="add-item with-label"
              style={{ margin: "5px 0", paddingLeft: "30px" }}
              type="button"
            >
              {l("Add another relationship")}
            </button>
          </td>
        </tr>
      </>
    );
  }
}

class ExternalLinkItem extends React.Component {
  render() {
    return (
      <>
        <tr>
          <td>{1}</td>
          <td>
            <div style={{ marginBottm: "10px" }}>
              <input
                style={{width: "100%"}}
                value="http://itunes.apple.com/au/preorder/the-last-of-the-tourists/id499465357"
              />
            </div>
          </td>
          <td>
            <div
              style={{
                position: "relative",
                display: "inline-block",
                minWidth: "50px",
                verticalAlign: "top",
              }}
            >
              <button
                className="nobutton icon remove-item"
                title="Remove Link"
                type="button"
              />
            </div>
          </td>
          <div
            className="bubble left-tail"
            style={{
              display: "block",
              width: "651px",
              top: "820.938px",
              left: "560px",
            }}
          >
            <p>
              {"This URL will be cleaned up to “"}
              <a>{"https://itunes.apple.com/au/preorder/id499465357"}</a>
              {"”."}
            </p>
          </div>
        </tr>
        <RelationshipList />
      </>
    );
  }
}

class ExternalLinksEditor extends React.Component {
  render() {
    return (
      <>
        <table className="row-form">
          <tbody>
            <ExternalLinkItem />
            <tr>
              <td>{2}</td>
              <td>
                <input style={{width: "100%"}} />
              </td>
            </tr>
          </tbody>
        </table>
      </>
    );
  }
}

export default ExternalLinksEditor;

I’ll also use React to build a ExternalLinkEditDialog for adding and editing external links, this dialog will be based on root/components/relationship-editor.tt.

Here’s the code:

/* eslint-disable react/jsx-no-literals */
/* eslint-disable react/jsx-sort-props */
import * as React from "react";

class DateInput extends React.Component {
  render() {
    return (
      <>
        <input type="text" maxLength="4" placeholder="YYYY" size="4" />
        -
        <input type="text" maxLength="2" placeholder="MM" size="2" />
        -
        <input type="text" maxLength="2" placeholder="DD" size="2" />
      </>
    );
  }
}

class ExternalLinkEditDialog extends React.Component {
  render() {
    return (
      <div
        className="ui-dialog ui-widget ui-widget-content
        ui-corner-all ui-front rel-editor-dialog"
        tabIndex="-1"
        role="dialog"
        aria-describedby="dialog"
        style={{
          display: "block",
          top: "113.739px",
          left: "0px",
          height: "auto",
          width: "auto",
        }}
        aria-labelledby="ui-id-1"
      >
        <div
          id="dialog"
          className="rel-editor-dialog ui-dialog-content ui-widget-content"
          style={{
            display: "block",
            width: "auto",
            minHeight: "0px",
            maxHeight: "none",
            height: "auto",
          }}
        >
          <div>
            <table>
              <tbody>
                <tr>
                  <td className="section">URL:</td>
                  <td>
                    <a>
                      {
                        "http://itunes.apple.com/au/preorder/the-last-of-the-tourists/id499465357"
                      }
                    </a>
                  </td>
                </tr>
                <tr>
                  <td className="section">Type:</td>
                  <td>
                    <div>
                      <select className="link-type">
                        <option value="171">discography entry</option>
                        <option disabled>get the music</option>
                        <option value="1">
                          &nbsp;&nbsp;purchase for mail order
                        </option>
                        <option value="2">
                          &nbsp;&nbsp;purchase for download
                        </option>
                        <option value="3">show notes</option>
                        <option value="4">cover art</option>
                      </select>
                      {" ("}
                      <a href="#">help</a>
                      {")"}
                    </div>
                    <div className="error field-error">
                      {'Enter a valid url e.g. "http://google.com/"'}
                    </div>
                  </td>
                </tr>
                <tr>
                  <td />
                  <td>
                    <input type="checkbox" />
                    <label>video</label>
                  </td>
                </tr>
                <tr>
                  <td className="section">Begin date:</td>
                  <td className="partial-date">
                    <DateInput />
                    {" "}
                    <button
                      className="icon copy-date"
                      type="button"
                      title="Copy to end date"
                    />
                  </td>
                </tr>
                <tr>
                  <td className="section">End date:</td>
                  <td className="partial-date">
                    <DateInput />
                  </td>
                </tr>
                <tr>
                  <td />
                  <td>
                    <label>
                      <input type="checkbox" />
                      This relationship has ended.
                    </label>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <div
            className="buttons ui-helper-clearfix"
            style={{ marginTop: "1em" }}
          >
            <button type="button" className="negative">
              Cancel
            </button>
            <div
              className="buttons-right"
              style={{ float: "right", textAalign: "right" }}
            >
              <button type="button" className="positive">
                Done
              </button>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default ExternalLinkEditDialog;

Since the whole form is moved into the dialog, we’ll have to modify root/static/scripts/edit/URLCleanup.js to make it compatible with the new UI.

Then URLCleanup.js will be refactored to support auto-selection and validation of multiple relationship types.

Finally we’ll make sure the new interface can interact with backend server correctly, basically the backend will receive multiple relationships and add each of them respectively.

Pre-Community Bonding

My pull requests: https://github.com/metabrainz/musicbrainz-server/pulls?q=is%3Apr+author%3Ay-young

Schedule

Every coding task in the schedule will include writing documentation and tests.

Before & During Community Bonding Period

I plan to continue exploring the codebase for a better view of the whole structure and architecture, and learn the related skills.

In the meantime, I’ll try to submit some bug fixes and features to get myself familiar with the code, workflow and coding styles.

Also we’ll review, discuss and finalize the UI prototype.

Then I’ll complete prerequisite tasks such as refactoring related components, if there’s any.

Week 1

Review and finalize design of the table for displaying external links and their relationship types, implement UI based on current ExternalLinksEditor, ensure correct display of existing data

Week 2

Review, finalize and implement ExternalLinkBubble UI, add support for independent display of clean URL

Milestone: A separate field for clean URL to avoid confusion

Week 3

Review, finalize and implement ExternalLinkEditDialog UI based on current relationship editor dialog

Week 4

Support date period and ended flag editing from other pages in the dialog, modify backend code for compatibility

Milestone: Enable date period and ended flag editing from editing pages of other entities, such as releases and artists

Week 5

Support adding multiple relationship types for a single link in the backend

Week 6

Refactor URLCleanup to support validation of multiple relationship types

Week 7

Enable the new ExternalLinksEditor to validate multiple relationship types

Milestone: Allow selecting multiple relationship types for an entered URL, the editor will validate the selections

Week 8

Refactor URLCleanup to support auto-selection of multiple relationship types

Week 9

Enable the new ExternalLinksEditor to automatically select multiple relationship types

Milestone: Support auto-selection of multiple relationship types

Week 10

Review and optimize the code, fix possible bugs and complete this project

Stretch Goal

  • Support bypassing validation rules in some cases

  • Support getting WikiData item from Wikipedia link

Detailed information about yourself

  • Tell us about the computer(s) you have available for working on your SoC project!

    I have a ThinkPad L480 with Intel i7 8th gen CPU and 16GB memory.

  • When did you first start programming?

    In 7th grade, with C++.

  • What type of music do you listen to? (Please list a series of MBIDs as examples.)

    American/European Pop, Anime/Game soundtracks, JPop and Classical

  • What aspects of the project you’re applying for (e.g., MusicBrainz, AcousticBrainz, etc.) interest you the most?
    MusicBrainz provides an open and free database for music lovers, researchers and many others, it enables music lovers like me to organize and make full use of our collections, and dive into the long history of music.

  • Have you ever used MusicBrainz to tag your files?

    Yes, with Mp3Tag.

  • Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?

    react-component/collapse, hexo-theme-icarus, hexo-component-inferno
    The above commits are small bug fixes, I also build my own project with React, see this music voting system.

  • What sorts of programming projects have you done on your own time?
    Information systems, along with tools that will help me collect and organize information. In general, projects that will benefit others and myself.

  • How much time do you have available, and how would you plan to use it?
    I’ll have at least 5-hour free time on weekdays and 10-hour on weekends. I plan to invest about 30 hours a week in this project.

  • Do you plan to have a job or study during the summer in conjunction with Summer of Code?
    Yes, in July I will have to take a course in the university.

5 Likes

Thank you for your interesting proposition and for being so proactive by submitting pull requests! :+1:

The set of features seem reasonable but the UI prototypes exhibit a few issues. (That is what prototypes are made for after all.)

  • In the fieldset “External Links”: How will an URL with multiple relationships be shown?
  • In both fieldset and editing bubble: The date should be attached to each relationship type not simply to each URL.
  • In the editing bubble: How can one set the “video” attribute for the free streaming relationship type? (This is neither in our initial list of ideas nor in your proposed features, but it seems it would make sense to plan for supporting it too.)
  • In the editing bubble: As discussed on IRC, it would probably be more versatile and more friendly to have a more kind elaborate of type select field with features such as: list display of selected relationship types, autocomplete (that is quite similar to tags editing in Discourse), plus tree display of allowed relationship types. That would also avoid confusion of using checkboxes as they are already used for attributes.
  • It does not provide any detail about how editor could ask to bypass validation rules. To be honest, you might have better to postpone that to stretch goals as it requires more work on the backend.
  • About stretch goals, documentation should not be a stretch goal, it should be part of every task.

IMHO, the fiedlset “External Links” should be redesigned to group relationship types by URL, with options to edit each relationship type separately and an option to edit all relationship types for a given URL at once. But I did not make any prototype like you did either. You might want to look at the release’s relationships editor for inspiration, and to anything (beyond MetaBrainz) made with React. Trying to design from a clean sheet can also bring better ideas than trying to adapt the outdated existing forms.

After updating the content of your applications, please try to detail a bit more your schedule too.

More generally, your project is very welcome as more and more online releases are being entered in the MusicBrainz database, so any of these improvements will likely be very appreciated by the community.

Do not hesitate to ask more questions on IRC, I will try to be more responsive this time. :slight_smile:

3 Likes

I’ve updated the UI prototype, please have a look.

After a thought into it, I think a tag-like input might not be an ideal choice, because we have separate attributes for each relationship, that would be hard to display.
Now I reuse the current relationship type selector and I think that’s enough, auto complete can be a feature so we can discuss later. :slight_smile:
As for tree display of allowed relationship types, we can also discuss on IRC since there’re many unknown factors. :slight_smile:

1 Like