diff --git a/src/actions/login.js b/src/actions/login.js
index 0dc48221..de627620 100644
--- a/src/actions/login.js
+++ b/src/actions/login.js
@@ -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 {
@@ -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,
diff --git a/src/actions/tags.js b/src/actions/tags.js
new file mode 100644
index 00000000..72d2aa4d
--- /dev/null
+++ b/src/actions/tags.js
@@ -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);
+ });
+};
diff --git a/src/components/pages/CreatePage.jsx b/src/components/pages/CreatePage.jsx
index fd463c65..7aec89bb 100644
--- a/src/components/pages/CreatePage.jsx
+++ b/src/components/pages/CreatePage.jsx
@@ -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 = {};
@@ -135,6 +146,144 @@ const getVocabularies = (recordTypeConfig, intl, getAuthorityVocabWorkflowState)
return vocabularyNames;
};
+const renderListItem = (recordType, config) => {
+ const recordConfig = config[recordType];
+ const recordDisplayName = ;
+ const recordLink = {recordDisplayName};
+ return (
+
+ {recordLink}
+
+ );
+};
+
+/**
+ * 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 ? (
+
+ ) : 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 (
+
+
+
+
+ );
+ });
+
+ 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) => (
+
+
+
+
+
+ ));
+ const vocabularyList = ;
+
+ const recordDisplayName = ;
+ const recordLink = {recordDisplayName} ;
+
+ return (
+
+ {recordLink}
+ {vocabularyList}
+
+ );
+ });
+
+ return renderPanel('authority', authorityItems);
+};
+
const contextTypes = {
config: PropTypes.shape({
recordTypes: PropTypes.object,
@@ -145,6 +294,7 @@ const propTypes = {
intl: intlShape,
perms: PropTypes.instanceOf(Immutable.Map),
getAuthorityVocabWorkflowState: PropTypes.func,
+ getTagsForRecord: PropTypes.func,
};
const defaultProps = {
@@ -156,6 +306,7 @@ export default function CreatePage(props, context) {
intl,
perms,
getAuthorityVocabWorkflowState,
+ getTagsForRecord,
} = props;
const {
@@ -163,77 +314,28 @@ export default function CreatePage(props, context) {
} = 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) => (
-
-
-
-
-
- ));
-
- vocabularyList = ;
- }
-
- 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 = ;
-
- let recordLink;
-
- if (vocabularyList) {
- recordLink = {recordDisplayName} ;
- } else {
- recordLink = {recordDisplayName};
- }
+ procedurePanel = renderProcedures(recordTypesByServiceType.procedure,
+ recordTypes,
+ getTagsForRecord,
+ tagConfig);
- return (
-
- {recordLink}
- {vocabularyList}
-
- );
- });
- });
-
- serviceTypes.forEach((serviceType) => {
- const items = itemsByServiceType[serviceType].filter((item) => !!item);
-
- if (items && items.length > 0) {
- lists.push(
- ,
- );
- }
- });
+ authorityPanel = renderAuthorities(recordTypesByServiceType.authority,
+ recordTypes,
+ intl,
+ getAuthorityVocabWorkflowState);
}
const title = ;
@@ -243,7 +345,9 @@ export default function CreatePage(props, context) {
- {lists}
+ {objectPanel}
+ {procedurePanel}
+ {authorityPanel}
);
diff --git a/src/constants/actionCodes.js b/src/constants/actionCodes.js
index 2d346d79..58820643 100644
--- a/src/constants/actionCodes.js
+++ b/src/constants/actionCodes.js
@@ -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';
diff --git a/src/containers/pages/CreatePageContainer.js b/src/containers/pages/CreatePageContainer.js
index ca3747cb..9f368081 100644
--- a/src/containers/pages/CreatePageContainer.js
+++ b/src/containers/pages/CreatePageContainer.js
@@ -4,6 +4,7 @@ import CreatePage from '../../components/pages/CreatePage';
import {
getAuthorityVocabWorkflowState,
getUserPerms,
+ getTagsForRecord,
} from '../../reducers';
const mapStateToProps = (state) => ({
@@ -11,6 +12,7 @@ const mapStateToProps = (state) => ({
getAuthorityVocabWorkflowState: (recordType, vocabulary) => getAuthorityVocabWorkflowState(
state, recordType, vocabulary,
),
+ getTagsForRecord: (recordType) => getTagsForRecord(state, recordType),
});
export default connect(
diff --git a/src/index.jsx b/src/index.jsx
index 87a75280..8c882f15 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -55,6 +55,17 @@ const defaultConfig = mergeConfig({
showTermListStateIcon: false,
structDateOptionListNames: ['dateQualifiers'],
structDateVocabNames: ['dateera', 'datecertainty', 'datequalifier'],
+ tags: {
+ defaultGroup: {
+ sortOrder: 0,
+ },
+ nagpra: {
+ sortOrder: 1,
+ },
+ legacy: {
+ sortOrder: 3,
+ },
+ },
tenantId: '1',
termDeprecationEnabled: false,
}, {
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 4e78a7d7..2d530bdb 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -17,6 +17,7 @@ import recordPage, * as fromRecordPage from './recordPage';
import searchToSelect, * as fromSearchToSelect from './searchToSelect';
import relation, * as fromRelation from './relation';
import search, * as fromSearch from './search';
+import tags, * as fromTags from './tags';
import user, * as fromUser from './user';
import vocabulary, * as fromVocabulary from './vocabulary';
@@ -39,6 +40,7 @@ export default combineReducers({
searchToSelect,
relation,
search,
+ tags,
user,
vocabulary,
});
@@ -266,3 +268,5 @@ export const getNotifications = (state) => fromNotification.getNotifications(sta
export const getOpenModalName = (state) => fromNotification.getModal(state.notification);
export const getCSpaceSystemInfo = (state) => fromCspace.getSystemInfo(state.cspace);
+
+export const getTagsForRecord = (state, recordType) => fromTags.getTags(state.tags, recordType);
diff --git a/src/reducers/tags.js b/src/reducers/tags.js
new file mode 100644
index 00000000..ba1a8f15
--- /dev/null
+++ b/src/reducers/tags.js
@@ -0,0 +1,43 @@
+import Immutable from 'immutable';
+import get from 'lodash/get';
+import { PROCEDURE_BY_TAG_READ_FULFILLED } from '../constants/actionCodes';
+
+import {
+ NS_PREFIX,
+ DOCUMENT_PROPERTY_NAME,
+} from '../constants/xmlNames';
+
+const handleProcedureByTagFulfilled = (state, action) => {
+ const response = action.payload;
+
+ const hasDocTypes = get(response, ['data',
+ DOCUMENT_PROPERTY_NAME,
+ `${NS_PREFIX}:servicegroups_common`,
+ 'hasDocTypes',
+ 'hasDocType']);
+
+ if (!hasDocTypes) {
+ return state;
+ }
+
+ // currently this makes an assumption that doctypes will only have one service tag
+ // it should be updated to merge state properly
+ let nextState = state;
+ const docTypes = Array.isArray(hasDocTypes) ? hasDocTypes : [hasDocTypes];
+ docTypes.forEach((docType) => {
+ nextState = nextState.setIn([docType.toLowerCase()], action.meta.tag);
+ });
+
+ return nextState;
+};
+
+export default (state = Immutable.Map(), action) => {
+ switch (action.type) {
+ case PROCEDURE_BY_TAG_READ_FULFILLED:
+ return handleProcedureByTagFulfilled(state, action);
+ default:
+ return state;
+ }
+};
+
+export const getTags = (state, recordType) => state.get(recordType);
diff --git a/styles/cspace-ui/CreatePagePanel.css b/styles/cspace-ui/CreatePagePanel.css
index 41b6cd80..4d72a491 100644
--- a/styles/cspace-ui/CreatePagePanel.css
+++ b/styles/cspace-ui/CreatePagePanel.css
@@ -27,18 +27,16 @@
list-style: none;
}
-.common > ul > li > ul > li {
+.authority > ul > li > ul > li {
display: inline-block;
}
-.common > ul > li > ul > li + li::before {
+.authority > ul > li > ul > li + li::before {
content: ' | ';
}
-.audit {
- composes: common;
- background-color: #F0F5FB;
- color: #305A8D;
+.tag > ul > li + li {
+ margin-top: 4px;
}
.object {
diff --git a/test/specs/actions/login.spec.js b/test/specs/actions/login.spec.js
index 183ae359..2051af5c 100644
--- a/test/specs/actions/login.spec.js
+++ b/test/specs/actions/login.spec.js
@@ -16,6 +16,8 @@ import {
ACCOUNT_PERMS_READ_FULFILLED,
ACCOUNT_PERMS_READ_REJECTED,
ACCOUNT_ROLES_READ_FULFILLED,
+ SERVICE_TAGS_READ_STARTED,
+ SERVICE_TAGS_READ_FULFILLED,
} from '../../../src/constants/actionCodes';
import {
@@ -65,6 +67,7 @@ describe('login action creator', () => {
const tokenUrl = '/cspace-services/oauth2/token';
const accountPermsUrl = '/cspace-services/accounts/0/accountperms';
const accountRolesUrl = `/cspace-services/accounts/${accountId}/accountroles`;
+ const procedureTagsUrl = '/cspace-services/servicegroups/procedure/tags';
const config = {};
const store = mockStore({
@@ -109,13 +112,19 @@ describe('login action creator', () => {
}))),
rest.get(accountRolesUrl, (req, res, ctx) => res(ctx.json({}))),
+
+ rest.get(procedureTagsUrl, (req, res, ctx) => res(ctx.json({
+ 'ns2:abstract-common-list': {
+ 'list-item': [],
+ },
+ }))),
);
return store.dispatch(login(config, authCode, authCodeRequestData))
.then(() => {
const actions = store.getActions();
- actions.should.have.lengthOf(6);
+ actions.should.have.lengthOf(8);
actions[0].should.deep.equal({
type: LOGIN_STARTED,
@@ -145,7 +154,10 @@ describe('login action creator', () => {
actions[4].should.have.property('type', PREFS_LOADED);
- actions[5].should.deep.equal({
+ actions[5].should.have.property('type', SERVICE_TAGS_READ_STARTED);
+ actions[6].should.have.property('type', SERVICE_TAGS_READ_FULFILLED);
+
+ actions[7].should.deep.equal({
type: LOGIN_FULFILLED,
meta: {
landingPath: authCodeRequestData.landingPath,
diff --git a/test/specs/actions/tags.spec.js b/test/specs/actions/tags.spec.js
new file mode 100644
index 00000000..2b424ab8
--- /dev/null
+++ b/test/specs/actions/tags.spec.js
@@ -0,0 +1,196 @@
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import chaiAsPromised from 'chai-as-promised';
+import { setupWorker, rest } from 'msw';
+
+import {
+ configureCSpace,
+} from '../../../src/actions/cspace';
+
+import readServiceTags from '../../../src/actions/tags';
+import {
+ PROCEDURE_BY_TAG_READ_FULFILLED,
+ PROCEDURE_BY_TAG_READ_REJECTED,
+ PROCEDURE_BY_TAG_READ_STARTED,
+ SERVICE_TAGS_READ_FULFILLED,
+ SERVICE_TAGS_READ_REJECTED,
+ SERVICE_TAGS_READ_STARTED,
+} from '../../../src/constants/actionCodes';
+
+chai.use(chaiAsPromised);
+chai.should();
+
+const mockStore = configureMockStore([thunk]);
+const successTag = 'test-tag';
+const failureTag = 'failure-tag';
+
+describe('tags action creator', () => {
+ const worker = setupWorker();
+ const procedureUrl = '/cspace-services/servicegroups/procedure';
+ const procedureTagsUrl = '/cspace-services/servicegroups/procedure/tags';
+
+ before(async () => {
+ await worker.start({ quiet: true });
+ });
+
+ after(() => {
+ worker.stop();
+ });
+
+ describe('success', () => {
+ const store = mockStore();
+
+ before(() => store.dispatch(configureCSpace())
+ .then(() => store.clearActions()));
+
+ afterEach(() => {
+ store.clearActions();
+ worker.resetHandlers();
+ });
+
+ it('reads tags and procedures when successful', () => {
+ worker.use(
+ rest.get(procedureTagsUrl, (req, res, ctx) => res(ctx.json({
+ 'ns2:abstract-common-list': {
+ 'list-item': [{ name: successTag }],
+ },
+ }))),
+
+ rest.get(procedureUrl, (req, res, ctx) => res(ctx.json({
+ }))),
+ );
+
+ return store.dispatch(readServiceTags())
+ .then(() => {
+ const actions = store.getActions();
+
+ const tagReadIdx = actions.findIndex((el) => el.type === SERVICE_TAGS_READ_STARTED);
+ const tagFulfilledIdx = actions.findIndex(
+ (el) => el.type === SERVICE_TAGS_READ_FULFILLED,
+ );
+ const procedureReadIdx = actions.findIndex(
+ (el) => el.type === PROCEDURE_BY_TAG_READ_STARTED,
+ );
+ const procedureFulfilledIdx = actions.findIndex(
+ (el) => el.type === PROCEDURE_BY_TAG_READ_FULFILLED,
+ );
+
+ [tagReadIdx, tagFulfilledIdx, procedureReadIdx, procedureFulfilledIdx]
+ .should.not.include(-1);
+
+ const procedureRead = actions[procedureReadIdx];
+ const procedureFulfilled = actions[procedureFulfilledIdx];
+
+ procedureRead.meta.should.contain({ tag: successTag });
+ procedureFulfilled.meta.should.contain({ tag: successTag });
+ procedureFulfilled.payload.status.should.equal(200);
+ });
+ });
+
+ it('resolves early when no tags are found', () => {
+ worker.use(
+ rest.get(procedureTagsUrl, (req, res, ctx) => res(ctx.json({
+ 'ns2:abstract-common-list': {
+ },
+ }))),
+ );
+
+ return store.dispatch(readServiceTags())
+ .then(() => {
+ const actions = store.getActions();
+
+ const tagReadIdx = actions.findIndex((el) => el.type === SERVICE_TAGS_READ_STARTED);
+ const tagFulfilledIdx = actions.findIndex(
+ (el) => el.type === SERVICE_TAGS_READ_FULFILLED,
+ );
+ const procedureReadIdx = actions.findIndex(
+ (el) => el.type === PROCEDURE_BY_TAG_READ_STARTED,
+ );
+
+ [tagReadIdx, tagFulfilledIdx]
+ .should.not.include(-1);
+
+ procedureReadIdx.should.equal(-1);
+ });
+ });
+ });
+
+ describe('failure', () => {
+ const store = mockStore();
+
+ before(() => store.dispatch(configureCSpace())
+ .then(() => store.clearActions()));
+
+ afterEach(() => {
+ store.clearActions();
+ worker.resetHandlers();
+ });
+
+ it('should not query procedures when the service tag read fails', () => {
+ worker.use(
+ rest.get(procedureTagsUrl, (req, res, ctx) => res(
+ ctx.status(403),
+ ctx.json({ message: 'forbidden' }),
+ )),
+ );
+
+ return store.dispatch(readServiceTags()).should.eventually.be.rejected
+ .then(() => {
+ const actions = store.getActions();
+
+ const tagReadIdx = actions.findIndex((el) => el.type === SERVICE_TAGS_READ_STARTED);
+ const tagRejectedIdx = actions.findIndex(
+ (el) => el.type === SERVICE_TAGS_READ_REJECTED,
+ );
+ const tagFulfilledIdx = actions.findIndex(
+ (el) => el.type === SERVICE_TAGS_READ_FULFILLED,
+ );
+
+ [tagReadIdx, tagRejectedIdx].should.not.include(-1, 'Expected service reads to exist in actions');
+ tagFulfilledIdx.should.equal(-1, 'Did not expect fulfilled to exist');
+ });
+ });
+
+ it('should reject if one procedure read fails', () => {
+ worker.use(
+ rest.get(procedureTagsUrl, (req, res, ctx) => res(ctx.json({
+ 'ns2:abstract-common-list': {
+ 'list-item': [{ name: successTag }, { name: failureTag }],
+ },
+ }))),
+
+ rest.get(procedureUrl, (req, res, ctx) => {
+ const tag = req.url.searchParams.get('servicetag');
+
+ if (tag === failureTag) {
+ return res(ctx.status(400));
+ }
+
+ return res(ctx.json({}));
+ }),
+ );
+
+ return store.dispatch(readServiceTags()).should.eventually.be.rejected
+ .then(() => {
+ const actions = store.getActions();
+
+ const tagReadIdx = actions.findIndex((el) => el.type === SERVICE_TAGS_READ_STARTED);
+ const tagFulfilledIdx = actions.findIndex(
+ (el) => el.type === SERVICE_TAGS_READ_FULFILLED,
+ );
+ const procedureReadIdx = actions.findIndex(
+ (el) => el.type === PROCEDURE_BY_TAG_READ_STARTED,
+ );
+ const procedureRejectedIdx = actions.findIndex(
+ (el) => el.type === PROCEDURE_BY_TAG_READ_REJECTED,
+ );
+
+ [tagReadIdx, tagFulfilledIdx, procedureReadIdx, procedureRejectedIdx]
+ .should.not.include(-1);
+
+ const rejected = actions[procedureRejectedIdx];
+ rejected.meta.should.contain({ tag: failureTag });
+ });
+ });
+ });
+});
diff --git a/test/specs/components/pages/CreatePage.spec.jsx b/test/specs/components/pages/CreatePage.spec.jsx
index 09d4a105..2f117ff2 100644
--- a/test/specs/components/pages/CreatePage.spec.jsx
+++ b/test/specs/components/pages/CreatePage.spec.jsx
@@ -15,6 +15,7 @@ const { expect } = chai;
chai.should();
const config = {
+ tags: {},
recordTypes: {
collectionobject: {
messages: {
@@ -190,6 +191,7 @@ const perms = Immutable.fromJS({
describe('CreatePage', () => {
const getAuthorityVocabWorkflowState = () => 'project';
+ const getTagsForRecord = () => undefined;
beforeEach(function before() {
this.container = createTestContainer(this);
@@ -202,6 +204,7 @@ describe('CreatePage', () => {
@@ -219,6 +222,7 @@ describe('CreatePage', () => {
@@ -253,6 +257,7 @@ describe('CreatePage', () => {
@@ -274,6 +279,7 @@ describe('CreatePage', () => {
@@ -297,6 +303,7 @@ describe('CreatePage', () => {
@@ -320,6 +327,7 @@ describe('CreatePage', () => {
@@ -341,6 +349,7 @@ describe('CreatePage', () => {
@@ -383,6 +392,7 @@ describe('CreatePage', () => {
@@ -434,6 +444,7 @@ describe('CreatePage', () => {
diff --git a/test/specs/reducers/index.spec.js b/test/specs/reducers/index.spec.js
index 3af9b536..4075b160 100644
--- a/test/specs/reducers/index.spec.js
+++ b/test/specs/reducers/index.spec.js
@@ -61,6 +61,7 @@ import reducer, {
getSearchToSelectVocabulary,
getNotifications,
getOpenModalName,
+ getTagsForRecord,
} from '../../../src/reducers';
import { searchKey } from '../../../src/reducers/search';
@@ -91,6 +92,7 @@ describe('reducer', () => {
'relation',
'search',
'searchToSelect',
+ 'tags',
'user',
'vocabulary',
]);
@@ -1026,4 +1028,17 @@ describe('reducer', () => {
}).should.equal(modalName);
});
});
+
+ describe('getTagsForProcedure selector', () => {
+ it('should select from the tags key', () => {
+ const recordType = 'collectionobject';
+ const recordTags = Immutable.Map();
+
+ getTagsForRecord({
+ tags: Immutable.Map({
+ [recordType]: recordTags,
+ }),
+ }, recordType).should.deep.equal(recordTags);
+ });
+ });
});
diff --git a/test/specs/reducers/tags.spec.js b/test/specs/reducers/tags.spec.js
new file mode 100644
index 00000000..2feee4cd
--- /dev/null
+++ b/test/specs/reducers/tags.spec.js
@@ -0,0 +1,77 @@
+import Immutable from 'immutable';
+import chaiImmutable from 'chai-immutable';
+
+import {
+ PROCEDURE_BY_TAG_READ_FULFILLED,
+} from '../../../src/constants/actionCodes';
+
+import reducer, {
+ getTags,
+} from '../../../src/reducers/tags';
+
+const { expect } = chai;
+
+chai.use(chaiImmutable);
+chai.should();
+
+describe('tags reducer', () => {
+ it('should have an empty immutable initial state', () => {
+ reducer(undefined, {}).should.deep.equal(Immutable.Map({}));
+ });
+
+ it('should do nothing if no record types are present', () => {
+ const recordType = 'collectionobject';
+ const serviceTag = 'test-tag';
+ const tagData = {
+ document: {
+ 'ns2:servicegroups_common': {
+ },
+ },
+ };
+
+ const state = reducer(undefined, {
+ type: PROCEDURE_BY_TAG_READ_FULFILLED,
+ payload: {
+ data: tagData,
+ },
+ meta: {
+ tag: serviceTag,
+ },
+ });
+
+ state.should.deep.equal(Immutable.Map({}));
+ expect(getTags(state, recordType)).to.equal(undefined);
+ });
+
+ it('should associate record types with a tag', () => {
+ const recordType = 'CollectionObject';
+ const recordTypeLower = recordType.toLowerCase();
+ const serviceTag = 'test-tag';
+ const tagData = {
+ document: {
+ 'ns2:servicegroups_common': {
+ hasDocTypes: {
+ hasDocType: [
+ recordType,
+ ],
+ },
+ },
+ },
+ };
+
+ const state = reducer(undefined, {
+ type: PROCEDURE_BY_TAG_READ_FULFILLED,
+ payload: {
+ data: tagData,
+ },
+ meta: {
+ tag: serviceTag,
+ },
+ });
+
+ state.should.deep.equal(Immutable.Map({
+ [recordTypeLower]: serviceTag,
+ }));
+ expect(getTags(state, recordTypeLower)).to.equal(serviceTag);
+ });
+});