Skip to content

Commit

Permalink
DRYD-1351: Rework Create Page (#246)
Browse files Browse the repository at this point in the history
* Add redux action and reducer for service tags
* Group procedures by tags in the create page 
* Add tag configuration object
  • Loading branch information
mikejritter authored Nov 23, 2024
1 parent 685656f commit 83ab7b4
Show file tree
Hide file tree
Showing 14 changed files with 636 additions and 71 deletions.
2 changes: 2 additions & 0 deletions src/actions/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { readAuthVocabs } from './authority';
import { createSession, setSession } from './cspace';
import { loadPrefs, savePrefs } from './prefs';
import { readAccountRoles } from './account';
import readServiceTags from './tags';
import { getUserUsername } from '../reducers';

import {
Expand Down Expand Up @@ -207,6 +208,7 @@ export const login = (config, authCode, authCodeRequestData = {}) => (dispatch,
return Promise.resolve();
})
.then(() => dispatch(loadPrefs(config, username)))
.then(() => dispatch(readServiceTags()))
.then(() => dispatch(readAuthVocabs(config)))
.then(() => dispatch({
type: LOGIN_FULFILLED,
Expand Down
80 changes: 80 additions & 0 deletions src/actions/tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { get } from 'lodash';
import getSession from '../helpers/session';

import {
SERVICE_TAGS_READ_STARTED,
SERVICE_TAGS_READ_FULFILLED,
SERVICE_TAGS_READ_REJECTED,
PROCEDURE_BY_TAG_READ_STARTED,
PROCEDURE_BY_TAG_READ_FULFILLED,
PROCEDURE_BY_TAG_READ_REJECTED,
} from '../constants/actionCodes';

const doRead = (tag, dispatch) => {
dispatch({
type: PROCEDURE_BY_TAG_READ_STARTED,
meta: {
tag: tag.name,
},
});

const session = getSession();
const requestConfig = {
params: {
servicetag: tag.name,
},
};

return session.read('servicegroups/procedure', requestConfig)
.then((response) => dispatch({
type: PROCEDURE_BY_TAG_READ_FULFILLED,
payload: response,
meta: {
tag: tag.name,
},
}))
.catch((error) => {
dispatch({
type: PROCEDURE_BY_TAG_READ_REJECTED,
meta: {
tag: tag.name,
},
});
return Promise.reject(error);
});
};

const readProcedures = (response, dispatch) => {
let tags = get(response, ['data', 'ns2:abstract-common-list', 'list-item']);
if (!tags) {
return Promise.resolve();
}

if (!Array.isArray(tags)) {
tags = [tags];
}

const promises = tags.map((tag) => doRead(tag, dispatch));
return Promise.all(promises)
.catch((error) => Promise.reject(error));
};

export default () => (dispatch) => {
dispatch({ type: SERVICE_TAGS_READ_STARTED });

const session = getSession();

return session.read('servicegroups/procedure/tags')
.then((response) => {
dispatch({ type: SERVICE_TAGS_READ_FULFILLED });
return readProcedures(response, dispatch);
})
.catch((error) => {
dispatch({
type: SERVICE_TAGS_READ_REJECTED,
payload: error,
});

return Promise.reject(error);
});
};
230 changes: 167 additions & 63 deletions src/components/pages/CreatePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ const messages = defineMessages({
},
});

const tagMessages = defineMessages({
nagpra: {
id: 'createPage.tag.nagpra',
defaultMessage: 'NAGPRA',
},
legacy: {
id: 'createPage.tag.legacy',
defaultMessage: 'Legacy',
},
});

const getRecordTypesByServiceType = (recordTypes, perms, intl) => {
const recordTypesByServiceType = {};

Expand Down Expand Up @@ -135,6 +146,144 @@ const getVocabularies = (recordTypeConfig, intl, getAuthorityVocabWorkflowState)
return vocabularyNames;
};

const renderListItem = (recordType, config) => {
const recordConfig = config[recordType];
const recordDisplayName = <FormattedMessage {...recordConfig.messages.record.name} />;
const recordLink = <Link id={recordType} to={`/record/${recordType}`}>{recordDisplayName}</Link>;
return (
<li key={recordType}>
{recordLink}
</li>
);
};

/**
* Render the panel for a group of Procedures
*
* @param {String} serviceType the name of the service type (object, procedure, authority)
* @param {Array} items the array of list items to display for the service
* @returns
*/
const renderPanel = (serviceType, items) => (
items && items.length > 0 ? (
<div className={panelStyles[serviceType]} key={serviceType}>
<h2><FormattedMessage {...messages[serviceType]} /></h2>
<ul>
{items}
</ul>
</div>
) : null
);

/**
* Render the div for Object records
*
* @param {Array} recordTypes the object records
* @param {Object} config the cspace config
* @returns the div
*/
const renderObjects = (recordTypes = [], config) => {
const serviceType = 'object';
const items = recordTypes.map((recordType) => renderListItem(recordType, config));
return renderPanel(serviceType, items);
};

/**
* Render the div for procedure records. The procedures are grouped together by their service tags
* in order to display procedures in a workflow together. Each tag has its own header in order to
* act as a delimiter within the div. Procedures without a tag do not have a header and are part
* of a default group.
*
* @param {Array} recordTypes the procedure record types
* @param {Object} config the cspace config
* @param {Function} getTagsForRecord function to query the service tag of a record
* @param {Object} tagConfig the configuration for the service tags containing their sortOrder
* @returns
*/
const renderProcedures = (recordTypes = [], config, getTagsForRecord, tagConfig) => {
const serviceType = 'procedure';

const grouped = Object.groupBy(recordTypes, (recordType) => getTagsForRecord(recordType) || 'defaultGroup');
const {
defaultGroup: defaultRecordTypes = [],
...taggedRecordTypes
} = grouped;

const defaultItems = defaultRecordTypes.map((recordType) => renderListItem(recordType, config));

const taggedItems = Object.keys(taggedRecordTypes).sort((lhs, rhs) => {
const lhsConfig = tagConfig[lhs] || {};
const rhsConfig = tagConfig[rhs] || {};

const {
sortOrder: lhsOrder = Number.MAX_SAFE_INTEGER,
} = lhsConfig;

const {
sortOrder: rhsOrder = Number.MAX_SAFE_INTEGER,
} = rhsConfig;

return lhsOrder > rhsOrder ? 1 : -1;
}).map((tag) => {
const tagRecordTypes = taggedRecordTypes[tag];
const items = tagRecordTypes.map((recordType) => renderListItem(recordType, config));

return (
<li className={panelStyles.tag}>
<h3 id={tag}><FormattedMessage {...tagMessages[tag]} /></h3>
<ul>
{items}
</ul>
</li>
);
});

return renderPanel(serviceType, defaultItems.concat(taggedItems));
};

/**
* Render the div for creating authority items. Each authority is a header and its vocabulary items
* are represented as a sub-list.
*
* @param {Array} recordTypes the authority records
* @param {Object} config the cspace config
* @param {intlShape} intl the intl object
* @param {Function} getAuthorityVocabWorkflowState function to get workflow states
*/
const renderAuthorities = (recordTypes = [], config, intl, getAuthorityVocabWorkflowState) => {
const authorityItems = recordTypes.map((recordType) => {
const recordConfig = config[recordType];
const vocabularies = getVocabularies(
recordConfig, intl, getAuthorityVocabWorkflowState,
);

if (!vocabularies || vocabularies.length === 0) {
return null;
}

const vocabularyItems = vocabularies.map((vocabulary) => (
<li key={vocabulary}>
<Link id={`${recordType}/${vocabulary}`} to={`/record/${recordType}/${vocabulary}`}>
<FormattedMessage {...recordConfig.vocabularies[vocabulary].messages.name} />
</Link>
</li>
));
const vocabularyList = <ul>{vocabularyItems}</ul>;

const recordDisplayName = <FormattedMessage {...recordConfig.messages.record.name} />;
const recordLink = <h3 id={recordType}>{recordDisplayName}</h3>;

return (
<li key={recordType}>
{recordLink}
{vocabularyList}
</li>
);
});

return renderPanel('authority', authorityItems);
};

const contextTypes = {
config: PropTypes.shape({
recordTypes: PropTypes.object,
Expand All @@ -145,6 +294,7 @@ const propTypes = {
intl: intlShape,
perms: PropTypes.instanceOf(Immutable.Map),
getAuthorityVocabWorkflowState: PropTypes.func,
getTagsForRecord: PropTypes.func,
};

const defaultProps = {
Expand All @@ -156,84 +306,36 @@ export default function CreatePage(props, context) {
intl,
perms,
getAuthorityVocabWorkflowState,
getTagsForRecord,
} = props;

const {
config,
} = context;

const {
tags: tagConfig,
recordTypes,
} = config;

const itemsByServiceType = {};
const lists = [];
let objectPanel;
let procedurePanel;
let authorityPanel;

if (recordTypes) {
const recordTypesByServiceType = getRecordTypesByServiceType(recordTypes, perms, intl);

serviceTypes.forEach((serviceType) => {
itemsByServiceType[serviceType] = recordTypesByServiceType[serviceType].map((recordType) => {
const recordTypeConfig = recordTypes[recordType];

const vocabularies = getVocabularies(
recordTypeConfig, intl, getAuthorityVocabWorkflowState,
);

let vocabularyList;

if (vocabularies && vocabularies.length > 0) {
const vocabularyItems = vocabularies.map((vocabulary) => (
<li key={vocabulary}>
<Link id={`${recordType}/${vocabulary}`} to={`/record/${recordType}/${vocabulary}`}>
<FormattedMessage {...recordTypeConfig.vocabularies[vocabulary].messages.name} />
</Link>
</li>
));

vocabularyList = <ul>{vocabularyItems}</ul>;
}

if (recordTypeConfig.vocabularies && !vocabularyList) {
// The record type is an authority, but no vocabularies are enabled. Don't render
// anything.
objectPanel = renderObjects(recordTypesByServiceType.object, recordTypes);

return null;
}

const recordDisplayName = <FormattedMessage {...recordTypeConfig.messages.record.name} />;

let recordLink;

if (vocabularyList) {
recordLink = <h3 id={recordType}>{recordDisplayName}</h3>;
} else {
recordLink = <Link id={recordType} to={`/record/${recordType}`}>{recordDisplayName}</Link>;
}
procedurePanel = renderProcedures(recordTypesByServiceType.procedure,
recordTypes,
getTagsForRecord,
tagConfig);

return (
<li key={recordType}>
{recordLink}
{vocabularyList}
</li>
);
});
});

serviceTypes.forEach((serviceType) => {
const items = itemsByServiceType[serviceType].filter((item) => !!item);

if (items && items.length > 0) {
lists.push(
<div className={panelStyles[serviceType]} key={serviceType}>
<h2><FormattedMessage {...messages[serviceType]} /></h2>
<ul>
{items}
</ul>
</div>,
);
}
});
authorityPanel = renderAuthorities(recordTypesByServiceType.authority,
recordTypes,
intl,
getAuthorityVocabWorkflowState);
}

const title = <FormattedMessage {...messages.title} />;
Expand All @@ -243,7 +345,9 @@ export default function CreatePage(props, context) {
<TitleBar title={title} updateDocumentTitle />

<div>
{lists}
{objectPanel}
{procedurePanel}
{authorityPanel}
</div>
</div>
);
Expand Down
10 changes: 10 additions & 0 deletions src/constants/actionCodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ export const SET_RELATED_RECORD_BROWSER_RELATED_CSID = 'SET_RELATED_RECORD_BROWS

export const SET_RECORD_PAGE_PRIMARY_CSID = 'SET_RECORD_PAGE_PRIMARY_CSID';

// record service tags

export const SERVICE_TAGS_READ_STARTED = 'SERVICE_TAGS_READ_STARTED';
export const SERVICE_TAGS_READ_FULFILLED = 'SERVICE_TAGS_READ_FULFILLED';
export const SERVICE_TAGS_READ_REJECTED = 'SERVICE_TAGS_READ_REJECTED';

export const PROCEDURE_BY_TAG_READ_STARTED = 'PROCEDURE_BY_TAG_READ_STARTED';
export const PROCEDURE_BY_TAG_READ_FULFILLED = 'PROCEDURE_BY_TAG_READ_FULFILLED';
export const PROCEDURE_BY_TAG_READ_REJECTED = 'PROCEDURE_BY_TAG_READ_REJECTED';

// relation

export const CLEAR_RELATION_STATE = 'CLEAR_RELATION_STATE';
Expand Down
Loading

0 comments on commit 83ab7b4

Please sign in to comment.