Skip to content

Commit

Permalink
Merge pull request #287 from bookbrainz/auto-create-edition-groups
Browse files Browse the repository at this point in the history
Automatically create Edition Groups when creating Editions.
Warn and prompt if an EG with the same name already exists.
  • Loading branch information
MonkeyDo committed Jun 17, 2019
2 parents 163773d + 9921ab6 commit 294b3ff
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 80 deletions.
31 changes: 19 additions & 12 deletions src/client/components/pages/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ function HelpPage() {
</ul>
</ListGroupItem>
<ListGroupItem>
<b>{genEntityIconHTMLElement('EditionGroup')}Edition Group</b> – a logical grouping of similar Editions.
<ul><li>Example: paperback, hardcover and e-book editions</li></ul>
<b>{genEntityIconHTMLElement('EditionGroup')}Edition Group</b> – a logical grouping of different Editions of the same book.
<ul><li>Example: paperback, hardcover and e-book editions of a novel</li></ul>
</ListGroupItem>
<ListGroupItem><b>{genEntityIconHTMLElement('Publisher')}Publisher</b> – publishing company or imprint</ListGroupItem>
</ListGroup>
Expand Down Expand Up @@ -111,11 +111,15 @@ function HelpPage() {
<br/>
<ol>
<li>Find or add a new {genEntityIconHTMLElement('Author')}Author</li>
<li>Find or add a new {genEntityIconHTMLElement('Work')}Work with an &#x27;Author wrote Work&#x27; relationship</li>
<li>Find or add a new {genEntityIconHTMLElement('Publisher')}Publisher</li>
<li>Find or add a new {genEntityIconHTMLElement('EditionGroup')}Edition Group</li>
<li>Find or add a new {genEntityIconHTMLElement('Edition')}Edition with a &#x27;contains Work&#x27; relationship and fill in the Publisher</li>
<li>If another format of the edition from the same publisher exists (see below), go to the Edition Group and click the &#x27;Add Edition&#x27; button. Repeat step 5.</li>
<li>On the Author page, click on &#39;Add Work&#39; to create a {genEntityIconHTMLElement('Work')}Work with a relationship to the Author</li>
<li>On the Work page, click &#39;Add Edition&#39; to create an {genEntityIconHTMLElement('Edition')}Edition with a relationship to the Work</li>
<li style={{listStyleType: 'none'}}>
<ul>
<li>A new {genEntityIconHTMLElement('EditionGroup')}Edition Group will be created automatically, but you can select an existing one</li>
<li>Create a new {genEntityIconHTMLElement('Publisher')}Publisher if you cannot find an existing one</li>
</ul>
</li>
<li>To enter another format of the same book (see explanations below), go to the Edition Group and click the &#x27;Add Edition&#x27; button. Repeat step 4.</li>
</ol>
<br/>
<h4>When should I create a new Edition of a Work?</h4>
Expand All @@ -137,12 +141,15 @@ function HelpPage() {
</ul>
<br/>
<h4>When should two Editions be part of the same Edition Group?</h4>
Generally, when a publisher releases multiple different formats of the same content, meaning:
Edition Groups exist to group together all the variations of an edition (an identifiable set of works) in a given language.
Here are examples of Editions that should be part of the same Edition Group:
<br/>
<ul>
<li>No substantial textual changes to the Work (corrections aside)</li>
<li>Same editorial content (intro, foreword, etc.)</li>
<li>Same publisher</li>
<li>Same title and cover</li>
<li>Different formats of the same edition (paperback, hardcover and e-book by the same publisher)</li>
<li>Revised and updated editions</li>
<li>Reprints</li>
<li>Editions with different forewords/intros</li>
<li>Co-editions (same book published in different countries by different publishers)</li>
</ul>
</Col>

Expand Down
17 changes: 11 additions & 6 deletions src/client/entity-editor/common/entity-search-field-option.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,21 @@ class EntitySearchFieldOption extends React.Component {
};
}

fetchOptions(query) {
return request
async fetchOptions(query) {
if (!query) {
return {
options: []
};
}
const response = await request
.get('/search/autocomplete')
.query({
collection: this.props.type,
q: query
})
.then((response) => ({
options: response.body.map(this.entityToOption)
}));
});
return {
options: response.body.map(this.entityToOption)
};
}

render() {
Expand Down
1 change: 1 addition & 0 deletions src/client/entity-editor/edition-section/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const UPDATE_HEIGHT = 'UPDATE_HEIGHT';
export const UPDATE_DEPTH = 'UPDATE_DEPTH';
export const SHOW_PHYSICAL = 'SHOW_PHYSICAL';
export const SHOW_EDITION_GROUP = 'SHOW_EDITION_GROUP';
export const UPDATE_WARN_IF_EDITION_GROUP_EXISTS = 'UPDATE_WARN_IF_EDITION_GROUP_EXISTS';

/**
* Produces an action indicating that the edition status for the edition being
Expand Down
111 changes: 81 additions & 30 deletions src/client/entity-editor/edition-section/edition-section.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,27 @@ import {
updatePublisher,
updateStatus
} from './actions';
import {Button, Col, Row} from 'react-bootstrap';

import {Alert, Button, Col, Row} from 'react-bootstrap';
import type {List, Map} from 'immutable';
import {
validateEditionSectionDepth,
validateEditionSectionEditionGroup,
validateEditionSectionHeight,
validateEditionSectionPages,
validateEditionSectionReleaseDate,
validateEditionSectionWeight,
validateEditionSectionWidth
} from '../validators/edition';


import CustomInput from '../../input';
import DateField from '../common/new-date-field';
import EntitySearchFieldOption from '../common/entity-search-field-option';
import Icon from 'react-fontawesome';
import LanguageField from '../common/language-field';
import NumericField from '../common/numeric-field';
import React from 'react';
import SearchResults from '../../components/pages/parts/search-results';
import Select from 'react-select';
import _ from 'lodash';
import {connect} from 'react-redux';
Expand Down Expand Up @@ -100,8 +103,10 @@ type StateProps = {
pagesValue: ?number,
physicalVisible: ?boolean,
publisherValue: Map<string, any>,
editionGroupRequired: ?boolean,
editionGroupVisible: ?boolean,
editionGroupValue: Map<string, any>,
matchingNameEditionGroups: ?array,
releaseDateValue: ?object,
statusValue: ?number,
weightValue: ?number,
Expand Down Expand Up @@ -169,8 +174,10 @@ function EditionSection({
onWidthChange,
pagesValue,
physicalVisible,
editionGroupRequired,
editionGroupValue,
editionGroupVisible,
matchingNameEditionGroups,
publisherValue,
releaseDateValue,
statusValue,
Expand All @@ -194,28 +201,85 @@ function EditionSection({

const {isValid: isValidReleaseDate, errorMessage: dateErrorMessage} = validateEditionSectionReleaseDate(releaseDateValue);

return (
<form>
<h2>
What else do you know about the Edition?
</h2>
<p className="text-muted">
Edition Group is required — this cannot be blank. <a href="/edition-group/create" target="_blank">Click here</a> to create one if you did not find an existing one.
</p>
<Row>
const hasmatchingNameEditionGroups = Array.isArray(matchingNameEditionGroups) && matchingNameEditionGroups.length > 0;
const getEditionGroupSearchSelect = () => (
<React.Fragment>
<Row style={{marginBottom: '2em'}}>
<Col md={6} mdOffset={3}>
<EntitySearchFieldOption
help="Group with other Editions by the same publisher"
error={!validateEditionSectionEditionGroup(editionGroupValue, true)}
help="Group with other Editions of the same book"
instanceId="edition-group"
label="Edition Group"
tooltipText="Group together Editions with no substantial textual or editorial changes.
<br>For example, identical paperback, hardcover and e-book editions."
tooltipText="Group together different Editions of the same book.
<br>For example paperback, hardcover and e-book editions."
type="edition-group"
value={editionGroupValue}
onChange={onEditionGroupChange}
/>
{hasmatchingNameEditionGroups &&
<Alert bsStyle="warning">
{matchingNameEditionGroups.length > 1 ?
'Edition Groups with the same name as this Edition already exist' :
'An existing Edition Group with the same name as this Edition already exists'
}:
<br/>
<small>The first match has been selected automatically.<br/>
Please review the choice: click on an item to open it in a new tab:
</small>
<SearchResults condensed results={matchingNameEditionGroups}/>
</Alert>
}
</Col>
<Col md={3}>
<Button
block
bsStyle="success"
href="/edition-group/create"
style={{marginTop: '1.8em'}}
target="_blank"
>
<Icon name="plus"/>
&nbsp;New Edition Group
</Button>
</Col>
</Row>
</React.Fragment>
);

const alertAutoCreateEditionGroup =
!editionGroupValue &&
!editionGroupVisible &&
!editionGroupRequired &&
!hasmatchingNameEditionGroups;

return (
<form>
<h2>
What else do you know about the Edition?
</h2>
<p className="text-muted">
Edition Group is required — this cannot be blank
</p>
{
alertAutoCreateEditionGroup ?
<Row>
<Col md={6} mdOffset={3}>
<Alert>
A new Edition Group with the same name will be created automatically.
<br/>
<Button
block
bsStyle="primary"
onClick={onEditionGroupButtonClick}
>
Click here to search for an existing one instead
</Button>
</Alert>
</Col>
</Row> :
getEditionGroupSearchSelect()
}
<p className="text-muted">
Below fields are optional — leave something blank if you
don&rsquo;t know it
Expand Down Expand Up @@ -280,22 +344,6 @@ function EditionSection({
</CustomInput>
</Col>
</Row>

{
!editionGroupVisible &&
<Row>
<Col md={6} mdOffset={3}>
<Button
bsStyle="link"
className="text-center"
onClick={onEditionGroupButtonClick}
>
Group with other existing formats…
</Button>
</Col>
</Row>
}

{
physicalVisible &&
<Row>
Expand Down Expand Up @@ -366,14 +414,17 @@ EditionSection.displayName = 'EditionSection';
type RootState = Map<string, Map<string, any>>;
function mapStateToProps(rootState: RootState): StateProps {
const state: Map<string, any> = rootState.get('editionSection');
const matchingNameEditionGroups = state.get('matchingNameEditionGroups');

return {
depthValue: state.get('depth'),
editionGroupRequired: state.get('editionGroupRequired'),
editionGroupValue: state.get('editionGroup'),
editionGroupVisible: state.get('editionGroupVisible'),
formatValue: state.get('format'),
heightValue: state.get('height'),
languageValues: state.get('languages'),
matchingNameEditionGroups,
pagesValue: state.get('pages'),
physicalVisible: state.get('physicalVisible'),
publisherValue: state.get('publisher'),
Expand Down
14 changes: 14 additions & 0 deletions src/client/entity-editor/edition-section/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// @flow

import * as Immutable from 'immutable';
import * as _ from 'lodash';

import {
type Action,
Expand All @@ -33,6 +34,7 @@ import {
UPDATE_PUBLISHER,
UPDATE_RELEASE_DATE,
UPDATE_STATUS,
UPDATE_WARN_IF_EDITION_GROUP_EXISTS,
UPDATE_WEIGHT,
UPDATE_WIDTH
} from './actions';
Expand Down Expand Up @@ -78,6 +80,18 @@ function reducer(
return state.set('height', payload);
case UPDATE_DEPTH:
return state.set('depth', payload);
case UPDATE_WARN_IF_EDITION_GROUP_EXISTS:
if (!Array.isArray(payload) || !payload.length) {
return state.set('matchingNameEditionGroups', []);
}
return state.set('matchingNameEditionGroups', payload)
.set('editionGroup', Immutable.fromJS({
disambiguation: _.get(payload[0], ['disambiguation', 'comment']),
id: payload[0].bbid,
text: _.get(payload[0], ['defaultAlias', 'name']),
type: payload[0].type,
value: payload[0].bbid
}));
// no default
}
return state;
Expand Down
20 changes: 18 additions & 2 deletions src/client/entity-editor/name-section/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,33 @@ export function debouncedUpdateDisambiguationField(
*
* @param {string} name - The value to be checked if it already exists.
* @param {string} entityType - The entity type of the value to be checked.
* @param {string} action - An optional redux action to dispatch. Defaults to UPDATE_WARN_IF_EXISTS
* @returns {many_prompts~inner} The returned function.
*/
export function checkIfNameExists(
name: string,
entityType: string
entityType: string,
action: ?string
): ((Action) => mixed) => mixed {
/**
* @param {function} dispatch - The redux dispatch function.
*/
return (dispatch) => {
if (!name) {
dispatch({
payload: null,
type: action || UPDATE_WARN_IF_EXISTS
});
return;
}
request.get('/search/exists')
.query({
collection: _snakeCase(entityType),
q: name
})
.then(res => dispatch({
payload: JSON.parse(res.text) || null,
type: UPDATE_WARN_IF_EXISTS
type: action || UPDATE_WARN_IF_EXISTS
}))
.catch((error: {message: string}) => error);
};
Expand All @@ -148,6 +157,13 @@ export function searchName(
* @param {function} dispatch - The redux dispatch function.
*/
return (dispatch) => {
if (!name) {
dispatch({
payload: null,
type: UPDATE_SEARCH_RESULTS
});
return;
}
request.get('/search/autocomplete')
.query({
collection: type,
Expand Down
Loading

0 comments on commit 294b3ff

Please sign in to comment.