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