Skip to content

Commit

Permalink
Merge pull request #2316 from alephdata/develop
Browse files Browse the repository at this point in the history
Next Release
  • Loading branch information
Steve Haffenden authored Jun 9, 2022
2 parents 0b3fcec + 06d5d27 commit 2a77827
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 96 deletions.
2 changes: 1 addition & 1 deletion helm/charts/aleph/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ ingestfile:

image:
repository: ghcr.io/alephdata/ingest-file
tag: "3.16.1"
tag: "3.16.3"
pullPolicy: Always

containerSecurityContext:
Expand Down
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dependencies maintained by OCCRP
banal==1.0.6
followthemoney==2.9.3
followthemoney==2.9.5
followthemoney-store[postgresql]==3.0.3
followthemoney-compare==0.4.3
fingerprints==1.0.3
Expand All @@ -16,21 +16,21 @@ Flask-Migrate==3.1.0
Flask-Cors==3.0.10
Flask-Babel==2.0.0
flask-talisman==1.0.0
SQLAlchemy==1.4.36
alembic==1.7.7
SQLAlchemy==1.4.37
alembic==1.8.0
authlib==0.15.5

elasticsearch==7.17.0
marshmallow==2.19.2
gunicorn[eventlet]==20.1.0
jsonschema==4.5.1
jsonschema==4.6.0
apispec==5.2.2
apispec-webframeworks==0.5.2
blinker==1.4
Babel==2.10.1
PyYAML==5.4.1
python-frontmatter==1.0.0
pyjwt >= 2.0.1, < 2.4.0
pyjwt >= 2.0.1, < 2.5.0
cryptography >= 36.0.0, < 38.0.0
requests[security] >= 2.25.1, < 3.0.0
urllib3==1.26.9
Expand Down
7 changes: 4 additions & 3 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "3.12.3",
"private": true,
"dependencies": {
"@alephdata/followthemoney": "2.9.3",
"@alephdata/react-ftm": "2.6.0",
"@alephdata/followthemoney": "2.9.4",
"@alephdata/react-ftm": "2.6.5",
"@blueprintjs/colors": "^3.0.0",
"@blueprintjs/core": "3.54.0",
"@blueprintjs/icons": "3.31.0",
Expand All @@ -14,7 +14,7 @@
"@formatjs/cli": "^4.2.2",
"@formatjs/intl-locale": "^2.4.14",
"@formatjs/intl-pluralrules": "^4.0.6",
"@formatjs/intl-relativetimeformat": "^10.0.1",
"@formatjs/intl-relativetimeformat": "^11.0.1",
"@formatjs/intl-utils": "^3.8.4",
"@types/jest": "^27.0.1",
"@types/node": "^17.0.8",
Expand All @@ -26,6 +26,7 @@
"js-file-download": "^0.4.9",
"jwt-decode": "^3.0.0",
"lodash": "^4.17.11",
"moment": "^2.29.1",
"node-sass": "6.0.1",
"numeral": "^2.0.6",
"papaparse": "^5.1.0",
Expand Down
107 changes: 92 additions & 15 deletions ui/src/components/Facet/DateFacet.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { Component } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { defineMessages, FormattedMessage, FormattedDate, injectIntl } from 'react-intl';
import queryString from 'query-string';
import { Button, Card, Icon, Intent, Spinner } from '@blueprintjs/core';
import { Histogram } from '@alephdata/react-ftm';
import moment from 'moment';

import withRouter from 'app/withRouter'
import { DEFAULT_START_INTERVAL, filterDateIntervals, formatDateQParam, timestampToYear } from 'components/Facet/util';

import { DEFAULT_START_INTERVAL, filterDateIntervals, formatDateQParam, timestampToLabel, isDateIntervalUncertain } from 'components/Facet/util';
import { selectEntitiesResult, selectLocale } from 'selectors'

import './DateFacet.scss';

Expand All @@ -19,6 +20,18 @@ const messages = defineMessages({
id: 'search.screen.dates_label',
defaultMessage: 'results',
},
uncertainMonth: {
id: 'search.screen.dates_uncertain_month',
defaultMessage: '* this count includes dates in {year} where no month is specified',
},
uncertainDay: {
id: 'search.screen.dates_uncertain_day',
defaultMessage: '* this count includes dates where no day is specified',
},
uncertainDayMonth: {
id: 'search.screen.dates_uncertain_day_month',
defaultMessage: '* this count includes dates in {year} where no day or month is specified',
},
});

export class DateFilter extends Component {
Expand All @@ -29,16 +42,28 @@ export class DateFilter extends Component {
}

onSelect(selected) {
const { field, query, updateQuery } = this.props;
const { facetInterval, field, query, updateQuery } = this.props;

let newRange;
let newQuery = query;

if (Array.isArray(selected)) {
newRange = selected.sort().map(formatDateQParam);
newRange = selected.sort().map(val => formatDateQParam(val, facetInterval));
} else {
const year = formatDateQParam(selected);
newRange = [year, year];

if (facetInterval === 'year') {
newQuery = newQuery.set(`facet_interval:${field}`, 'month')
const end = moment.utc(selected).endOf('year').format('YYYY-MM-DD')
newRange = [formatDateQParam(selected, 'month'), formatDateQParam(end, 'month')]
} else if (facetInterval === 'month') {
newQuery = newQuery.set(`facet_interval:${field}`, 'day')
const end = moment.utc(selected).endOf('month').format('YYYY-MM-DD')
newRange = [formatDateQParam(selected, 'day'), formatDateQParam(end, 'day')]
} else {
newRange = [formatDateQParam(selected, 'day'), formatDateQParam(selected, 'day')]
}
}
const newQuery = query.setFilter(`gte:${field}`, newRange[0])
newQuery = newQuery.setFilter(`gte:${field}`, newRange[0])
.setFilter(`lte:${field}`, newRange[1]);

updateQuery(newQuery)
Expand Down Expand Up @@ -88,26 +113,74 @@ export class DateFilter extends Component {
);
}

renderParentLabel() {
const { facetInterval, filteredIntervals } = this.props;

const sampleDate = filteredIntervals[0].label

const content = facetInterval === 'month'
? moment.utc(sampleDate).year()
: (
<FormattedDate
value={sampleDate}
year="numeric"
month="long"
/>
)
return <span className="DateFacet__parent-label">{content}</span>
}

formatUncertainWarning(timestamp) {
const { facetInterval, intl } = this.props;
const year = moment.utc(timestamp).year()

if (facetInterval === 'month') {
return intl.formatMessage(messages.uncertainMonth, { year })
} else {
const isFirstMonth = moment.utc(timestamp).month() === 0
return intl.formatMessage(messages[isFirstMonth ? 'uncertainDayMonth' : 'uncertainDay'], { year });
}
}

formatData(dataPropName) {
const { facetInterval, filteredIntervals, locale } = this.props;

return filteredIntervals.map(({ label, count, id }) => {
const isUncertain = facetInterval !== 'year' && isDateIntervalUncertain(label, facetInterval)
const uncertainWarning = isUncertain && this.formatUncertainWarning(label)

return ({
...timestampToLabel(label, facetInterval, locale),
[dataPropName]: count,
isUncertain,
uncertainWarning,
id
})
})
}

render() {
const { dataLabel, emptyComponent, filteredIntervals, intl, displayShowHiddenToggle, showAll, showLabel = true } = this.props;
const { dataLabel, emptyComponent, facetInterval, filteredIntervals, intl, displayShowHiddenToggle, showLabel = true } = this.props;
let content;

if (filteredIntervals) {
if (!filteredIntervals.length) {
content = emptyComponent;
} else {
const dataPropName = dataLabel || intl.formatMessage(messages.results);

content = (
<>
{facetInterval !== 'year' && this.renderParentLabel()}
<Histogram
data={filteredIntervals.map(({ label, count, ...rest }) => ({ label: timestampToYear(label), [dataPropName]: count, ...rest }))}
data={this.formatData(dataPropName)}
dataPropName={dataPropName}
onSelect={this.onSelect}
containerProps={{
height: DATE_FACET_HEIGHT,
}}
/>
{(displayShowHiddenToggle || showAll) && this.renderShowHiddenToggle()}
{displayShowHiddenToggle && this.renderShowHiddenToggle()}
</>
)
}
Expand Down Expand Up @@ -137,17 +210,21 @@ export class DateFilter extends Component {
}

const mapStateToProps = (state, ownProps) => {
const { location, intervals, query } = ownProps;
const { field, location, intervals, query } = ownProps;
const hashQuery = queryString.parse(location.hash);

const showAll = hashQuery.show_all_dates === 'true';
const result = selectEntitiesResult(state, query);

if (intervals) {
const { filteredIntervals, hasOutOfRange } = filterDateIntervals({ query, intervals, useDefaultBounds: !showAll })
if (intervals && !result.isPending) {
const { filteredIntervals, hasOutOfRange } = filterDateIntervals({ field, query, intervals, useDefaultBounds: !showAll })
const locale = selectLocale(state);
return {
filteredIntervals,
displayShowHiddenToggle: hasOutOfRange,
showAll
facetInterval: query.getString(`facet_interval:${field}`),
showAll,
locale: locale === 'en' ? 'en-gb' : locale
};
}
return {};
Expand Down
6 changes: 6 additions & 0 deletions ui/src/components/Facet/DateFacet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
}
}

&__parent-label {
font-weight: bold;
color: $blue2;
margin-bottom: $aleph-grid-size/2;
}

.ErrorSection {
min-height: unset;

Expand Down
3 changes: 2 additions & 1 deletion ui/src/components/Facet/Facet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class Facet extends Component {
const { field, query } = this.props;
const newQuery = query
.clearFilter(`lte:${field}`)
.clearFilter(`gte:${field}`);
.clearFilter(`gte:${field}`)
.set(`facet_interval:${field}`, 'year')

this.props.updateQuery(newQuery);
}
Expand Down
84 changes: 64 additions & 20 deletions ui/src/components/Facet/util.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,98 @@
const DEFAULT_START_INTERVAL = 1950;
const ES_SUFFIX = '||/y';
import moment from 'moment';

const formatDateQParam = (datetime) => {
return `${new Date(datetime).getFullYear()}||/y`
const DEFAULT_START_INTERVAL = '1950';

const formatDateQParam = (datetime, granularity) => {
const date = moment.utc(datetime).format("YYYY-MM-DD")
if (granularity === 'month') {
return `${date}||/M`
} else if (granularity === 'day') {
return `${date}||/d`
}
return `${date}||/y`
};

const cleanDateQParam = (value) => {
return value.replace(ES_SUFFIX, '');
if (!value) { return; }
const [date, suffix] = value.split('||/');

if (suffix === 'y') {
return moment.utc(date).format('YYYY')
} else if (suffix === 'M') {
return moment.utc(date).format('YYYY-MM')
} else {
return date;
}
};

const timestampToYear = timestamp => {
return new Date(timestamp).getFullYear();
const timestampToLabel = (timestamp, granularity, locale) => {
const dateObj = new Date(timestamp)
let label, tooltipLabel;

if (granularity === 'month') {
label = new Intl.DateTimeFormat(locale, { month: 'short' }).format(dateObj)
tooltipLabel = new Intl.DateTimeFormat(locale, { month: 'short', year: 'numeric' }).format(dateObj)
} else if (granularity === 'day') {
label = dateObj.getDate()
tooltipLabel = new Intl.DateTimeFormat(locale, { month: 'short', year: 'numeric', day: 'numeric' }).format(dateObj)
} else {
label = tooltipLabel = dateObj.getFullYear();
}

return ({ label, tooltipLabel })
}

const filterDateIntervals = ({ query, intervals, useDefaultBounds }) => {
const defaultEndInterval = new Date().getFullYear();
const hasGtFilter = query.hasFilter('gte:dates');
const hasLtFilter = query.hasFilter('lte:dates');
const filterDateIntervals = ({ field, query, intervals, useDefaultBounds }) => {
const defaultEndInterval = moment.utc().format('YYYY-MM-DD');
const hasGtFilter = query.hasFilter(`gte:${field}`);
const hasLtFilter = query.hasFilter(`lte:${field}`);

const gt = hasGtFilter
? cleanDateQParam(query.getFilter('gte:dates')[0])
const gtRaw = hasGtFilter
? cleanDateQParam(query.getFilter(`gte:${field}`)[0])
: (useDefaultBounds && DEFAULT_START_INTERVAL);

const lt = hasLtFilter
? cleanDateQParam(query.getFilter('lte:dates')[0])
const ltRaw = hasLtFilter
? cleanDateQParam(query.getFilter(`lte:${field}`)[0])
: (useDefaultBounds && defaultEndInterval);

let gtOutOfRange, ltOutOfRange = false;
const gt = gtRaw && moment(gtRaw)
const lt = ltRaw && moment(ltRaw)

let gtOutOfRange, ltOutOfRange = false;
const filteredIntervals = intervals.filter(({ id }) => {
const year = timestampToYear(id);
if (gt && year < gt) {
if (gt && gt.isAfter(id)) {
gtOutOfRange = true;
return false;
}
if (lt && year > lt) {
if (lt && lt.isBefore(id)) {
ltOutOfRange = true;
return false;
}
return true;
})

const hasOutOfRange = useDefaultBounds && ((!hasGtFilter && gtOutOfRange) || (!hasLtFilter && ltOutOfRange));

return { filteredIntervals, hasOutOfRange };
}

const isDateIntervalUncertain = (timestamp, granularity) => {
const dateObj = moment.utc(timestamp)

if (granularity === 'month' && dateObj.month() === 0) {
return true;
} else if (granularity === 'day' && dateObj.date() === 1) {
return true;
}

return false;
}

export {
cleanDateQParam,
DEFAULT_START_INTERVAL,
formatDateQParam,
timestampToYear,
timestampToLabel,
isDateIntervalUncertain,
filterDateIntervals
}
Loading

0 comments on commit 2a77827

Please sign in to comment.