diff --git a/src/components/Notification.jsx b/src/components/Notification.jsx index e718b9d..8af75e9 100644 --- a/src/components/Notification.jsx +++ b/src/components/Notification.jsx @@ -31,12 +31,12 @@ export default class Notification extends React.Component{ if (this.props.isNotification) { return (
- {this.props.notifications.map(not => ( + {this.props.notifications.map(notification => (
this.toggleDetails() }> -
{`${not.message}`}
+
{`${notification.message}`}
- {(not.items !== null) ? this.renderItems(not.items) : ''} + {(notification.items !== null) ? this.renderItems(notification.items) : ''}
))} diff --git a/src/reducers/utils/validators.js b/src/reducers/utils/validators.js index b6c38b9..8c07a32 100644 --- a/src/reducers/utils/validators.js +++ b/src/reducers/utils/validators.js @@ -18,6 +18,34 @@ function makeError(type, id, message) { } } + +const isLeaf = node => (Object.keys(node.children).length === 0); +const isDuplicate = (node, set) => { return (set.has(node.key)); }; + + +/* +* Traverse a tag tree and check its duplicates +*/ +function traverseNodeAndCheckIt(node, parent, set, duplicates) { + // If it's a leaf, check that it's not duplicate + if (isLeaf(node)) { + if (isDuplicate(node, set)) { + duplicates.push({ + id: node.key, + error: makeError('Tags', node.key, 'tag was found more than once in hierarchy. Ignoring duplicate.') + }); + delete parent.children[node.key]; + } else { + set.add(node.key); + } + } else { + // If it's not a leaf, simply keep going + Object.values(node.children).forEach((childNode) => { + traverseNodeAndCheckIt(childNode, node, set, duplicates); + }); + } +} + /* * Validate domain schema */ @@ -27,7 +55,7 @@ export function validate(domain) { categories: [], sites: [], notifications: domain.notifications, - tags: domain.tags + tags: {} } const discardedDomain = { @@ -59,7 +87,7 @@ export function validate(domain) { validateItem(site, 'sites', siteSchema); }); - // Message the number of failed items + // Message the number of failed items in domain Object.keys(discardedDomain).forEach(disc => { const len = discardedDomain[disc].length; if (len) { @@ -69,7 +97,22 @@ export function validate(domain) { type: 'error' }); } - }) + }); + + // Validate uniqueness of tags + const tagSet = new Set([]); + const duplicateTags = []; + traverseNodeAndCheckIt(domain.tags, {}, tagSet, duplicateTags); + + // Duplicated tags + if (duplicateTags.length > 0) { + sanitizedDomain.notifications.push({ + message: `Tags are required to be unique. Ignoring duplicates for now.`, + items: duplicateTags, + type: 'error' + }); + } + sanitizedDomain.tags = domain.tags; return sanitizedDomain; } diff --git a/src/scss/notification.scss b/src/scss/notification.scss index 4b25d46..49a8280 100644 --- a/src/scss/notification.scss +++ b/src/scss/notification.scss @@ -57,15 +57,23 @@ overflow: hidden; display: flex; flex-direction: column; + border-radius: 3px; + margin-top: 10px; + padding: 10px; + background: $darkgrey; + color: $offwhite; + font-family: monospace; &.true { height: auto; - transition: height 0.4s; + transition: height 0.4s, margin 0.4s; } &.false { height: 0; - transition: height 0.4s; + padding: 0; + margin: 0; + transition: height 0.4s, margin 0.4s; } } }