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

DRYD-1351: Rework Create Page #246

Merged
merged 24 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
556e9cb
Create redux action and reducer for getting service tags
mikejritter Nov 14, 2024
24df710
Read service tags on login
mikejritter Nov 14, 2024
5709751
Relabel fn
mikejritter Nov 14, 2024
c9b8d38
Add getTagForRecord reducer to CreatePage
mikejritter Nov 16, 2024
180c0d5
Add getTagForRecord reducer
mikejritter Nov 16, 2024
59cfedf
Remove console log
mikejritter Nov 16, 2024
e4a4eb7
First pass at sub groups for procedures
mikejritter Nov 16, 2024
00c5585
Roll back minor change
mikejritter Nov 16, 2024
e0c97ad
Update styles for procedure by tag lists
mikejritter Nov 18, 2024
56d3511
Display tags as a nested list
mikejritter Nov 18, 2024
e6be225
Add tag sort order to config
mikejritter Nov 18, 2024
f27c4f7
Protect against keys with no config
mikejritter Nov 18, 2024
ffe8229
Add tags reducer to expected keys
mikejritter Nov 18, 2024
554606c
Add test for getTagsForProcedure selector
mikejritter Nov 18, 2024
61107de
Additional null guards, separate render panel func
mikejritter Nov 18, 2024
1d8e1e0
Update create page tests
mikejritter Nov 18, 2024
d3b0a74
Update types in param documentation
mikejritter Nov 18, 2024
0e553cc
Remove .only
mikejritter Nov 18, 2024
0bc859d
Add service tag read action to login test
mikejritter Nov 18, 2024
661f00e
Add tests for actions/tags.js
mikejritter Nov 19, 2024
e59ab85
Fix incorrect method call
mikejritter Nov 19, 2024
47087b7
Test for tags reducer
mikejritter Nov 19, 2024
738710d
Small updates to tags action creator test
mikejritter Nov 19, 2024
92ee697
Change test name to trigger github actions
mikejritter Nov 20, 2024
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
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