mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-11 04:48:36 +03:00
Finding association duplicates; sanitizing appropriately; beginning to edit narrativise function in UI
This commit is contained in:
@@ -26,6 +26,7 @@ module.exports = {
|
||||
USE_CATEGORIES: true,
|
||||
CATEGORIES_AS_FILTERS: true,
|
||||
USE_ASSOCIATIONS: true,
|
||||
USE_ASSOCIATION_DESCRIPTIONS: true,
|
||||
USE_SOURCES: true,
|
||||
USE_COVER: true,
|
||||
USE_SEARCH: false,
|
||||
|
||||
@@ -199,18 +199,18 @@ export function binarySearch (ar, el, compareFn) {
|
||||
return -m - 1
|
||||
}
|
||||
|
||||
export const isFilterLeaf = node => (Object.keys(node.children).length === 0)
|
||||
export const isFilterDuplicate = (node, set) => { return (set.has(node.key)) }
|
||||
// export const isFilterLeaf = node => (Object.keys(node.children).length === 0)
|
||||
// export const isFilterDuplicate = (node, set) => { return (set.has(node.key)) }
|
||||
|
||||
export function findDescriptionInFilterTree (key, node) {
|
||||
if (node.key === key) return node.description
|
||||
if (isFilterLeaf(node)) return false
|
||||
const descs = Object.keys(node.children)
|
||||
.map(c => findDescriptionInFilterTree(key, node.children[c]))
|
||||
.filter(v => !!v)
|
||||
if (descs.length !== 1) return false
|
||||
return descs[0]
|
||||
}
|
||||
// export function findDescriptionInFilterTree (key, node) {
|
||||
// if (node.key === key) return node.description
|
||||
// if (isFilterLeaf(node)) return false
|
||||
// const descs = Object.keys(node.children)
|
||||
// .map(c => findDescriptionInFilterTree(key, node.children[c]))
|
||||
// .filter(v => !!v)
|
||||
// if (descs.length !== 1) return false
|
||||
// return descs[0]
|
||||
// }
|
||||
|
||||
export function makeNiceDate (datetime) {
|
||||
if (datetime === null) return null
|
||||
|
||||
@@ -40,7 +40,8 @@ class Dashboard extends React.Component {
|
||||
componentDidMount () {
|
||||
if (!this.props.app.isMobile) {
|
||||
this.props.actions.fetchDomain()
|
||||
.then(domain => this.props.actions.updateDomain({
|
||||
.then(domain =>
|
||||
this.props.actions.updateDomain({
|
||||
domain,
|
||||
features: this.props.features
|
||||
}))
|
||||
|
||||
@@ -29,46 +29,62 @@ function isValidDate (d) {
|
||||
* Traverse a filter tree and check its duplicates. Also recompose as
|
||||
* description if `features.USE_ASSOCIATION_DESCRIPTIONS` is true.
|
||||
*/
|
||||
function validateFilterTree (node, parent, set, duplicates, hasFilterDescriptions) {
|
||||
if (hasFilterDescriptions) {
|
||||
if (node.key === '_root') {
|
||||
node.isDescription = true // setting first set of nodes to values
|
||||
} else if (!parent.isDescription) {
|
||||
node.isDescription = true
|
||||
} else {
|
||||
node.isDescription = false
|
||||
}
|
||||
// function validateFilterTree (node, parent, set, duplicates, hasAssociationDescriptions) {
|
||||
// if (hasAssociationDescriptions) {
|
||||
// if (node.key === '_root') {
|
||||
// node.isDescription = true // setting first set of nodes to values
|
||||
// } else if (!parent.isDescription) {
|
||||
// node.isDescription = true
|
||||
// } else {
|
||||
// node.isDescription = false
|
||||
// }
|
||||
|
||||
if (node.isDescription && node.key !== 'root') {
|
||||
parent.description = node.key
|
||||
parent.children = node.children
|
||||
delete parent.isDescription
|
||||
}
|
||||
if (isFilterLeaf(node)) {
|
||||
delete parent.isDescription
|
||||
}
|
||||
}
|
||||
// if (node.isDescription && node.key !== 'root') {
|
||||
// parent.description = node.key
|
||||
// parent.children = node.children
|
||||
// delete parent.isDescription
|
||||
// }
|
||||
// if (isFilterLeaf(node)) {
|
||||
// delete parent.isDescription
|
||||
// }
|
||||
// }
|
||||
|
||||
if (typeof (node) !== 'object' || typeof (node.children) !== 'object') {
|
||||
return
|
||||
}
|
||||
// If it's a leaf, check that it's not duplicate
|
||||
if (isFilterLeaf(node)) {
|
||||
if (isFilterDuplicate(node, set)) {
|
||||
// if (typeof (node) !== 'object' || typeof (node.children) !== 'object') {
|
||||
// return
|
||||
// }
|
||||
// // If it's a leaf, check that it's not duplicate
|
||||
// if (isFilterLeaf(node)) {
|
||||
// if (isFilterDuplicate(node, set)) {
|
||||
// duplicates.push({
|
||||
// id: node.key,
|
||||
// error: makeError('Filters', node.key, 'filter 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) => {
|
||||
// validateFilterTree(childNode, node, set, duplicates, hasAssociationDescriptions)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
function findDuplicateAssociations (associations) {
|
||||
const seenSet = new Set([])
|
||||
const duplicates = []
|
||||
associations.forEach(item => {
|
||||
if (seenSet.has(item.id)) {
|
||||
duplicates.push({
|
||||
id: node.key,
|
||||
error: makeError('Filters', node.key, 'filter was found more than once in hierarchy. Ignoring duplicate.')
|
||||
id: item.id,
|
||||
error: makeError('Association', item.id, 'association was found more than once. Ignoring duplicate.')
|
||||
})
|
||||
delete parent.children[node.key]
|
||||
} else {
|
||||
set.add(node.key)
|
||||
seenSet.add(item.id)
|
||||
}
|
||||
} else {
|
||||
// If it's not a leaf, simply keep going
|
||||
Object.values(node.children).forEach((childNode) => {
|
||||
validateFilterTree(childNode, node, set, duplicates, hasFilterDescriptions)
|
||||
})
|
||||
}
|
||||
})
|
||||
return duplicates
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -162,10 +178,10 @@ export function validateDomain (domain, features) {
|
||||
)
|
||||
|
||||
// Validate uniqueness of associations
|
||||
const associationSet = new Set([])
|
||||
const duplicateAssociations = []
|
||||
validateFilterTree(domain.associations, {}, associationSet, duplicateAssociations, features.USE_ASSOCIATION_DESCRIPTIONS)
|
||||
|
||||
// const associationSet = new Set([])
|
||||
// const duplicateAssociations = []
|
||||
// validateFilterTree(domain.associations, {}, associationSet, duplicateAssociations, features.USE_ASSOCIATION_DESCRIPTIONS)
|
||||
const duplicateAssociations = findDuplicateAssociations(domain.associations)
|
||||
// Duplicated associations
|
||||
if (duplicateAssociations.length > 0) {
|
||||
sanitizedDomain.notifications.push({
|
||||
|
||||
@@ -6,17 +6,16 @@ const initial = {
|
||||
* The Domain or 'domain' of this state refers to the tree of data
|
||||
* available for render and display.
|
||||
* Selections and filters in the 'app' subtree will operate the domain
|
||||
* in mapStateToProps of the Dashboard, and deterimne which items
|
||||
* in mapStateToProps of the Dashboard, and determine which items
|
||||
* in the domain will get rendered by React
|
||||
*/
|
||||
domain: {
|
||||
events: [],
|
||||
narratives: [],
|
||||
locations: [],
|
||||
categories: [],
|
||||
associations: [],
|
||||
sources: {},
|
||||
sites: [],
|
||||
filters: {},
|
||||
notifications: []
|
||||
},
|
||||
|
||||
@@ -24,7 +23,7 @@ const initial = {
|
||||
* The 'app' subtree of this state determines the data and information to be
|
||||
* displayed.
|
||||
* It may refer to those the user interacts with, by selecting,
|
||||
* fitlering and so on, which ultimately operate on the data to be displayed.
|
||||
* filtering and so on, which ultimately operate on the data to be displayed.
|
||||
* Additionally, some of the 'app' flags are determined by the config file
|
||||
* or by the characteristics of the client, browser, etc.
|
||||
*/
|
||||
@@ -137,12 +136,11 @@ const initial = {
|
||||
|
||||
features: {
|
||||
USE_COVER: false,
|
||||
USE_FILTERS: false,
|
||||
USE_ASSOCIATIONS: false,
|
||||
USE_SEARCH: false,
|
||||
USE_SITES: false,
|
||||
USE_SOURCES: false,
|
||||
USE_SHAPES: false,
|
||||
USE_NARRATIVES: false,
|
||||
GRAPH_NONLOCATED: false,
|
||||
HIGHLIGHT_GROUPS: false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user