Skip to content

Commit

Permalink
Merge pull request #1035 from City-of-Helsinki/feature/dynamic-servic…
Browse files Browse the repository at this point in the history
…e-tree-result-count

Dynamic service tree result count
  • Loading branch information
vitalis0itu authored Oct 4, 2023
2 parents ca4c0f5 + b0824a7 commit d0d5ce6
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 37 deletions.
3 changes: 3 additions & 0 deletions src/utils/newFetch/HTTPClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ export default class HttpClient {
const newOptions = {
...options,
page_size: 1,
only: 'id',
include: null,
geometry: false,
};
return this.fetch(endpoint, this.optionsToSearchParams(newOptions, true), 'count');
}
Expand Down
7 changes: 4 additions & 3 deletions src/utils/newFetch/ServiceMapAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export default class ServiceMapAPI extends HttpClient {
return this.getSinglePage('search', options);
}

serviceNodeSearch = async (idList, additionalOptions) => {
if (typeof idList !== 'string') {
throw new APIFetchError('Invalid query string provided to ServiceMapAPI search method');
serviceNodeSearch = async (idList, additionalOptions, onlyCount) => {
if (!['string', 'number'].includes(typeof idList)) {
throw new APIFetchError('Invalid query string provided to ServiceMapAPI serviceNodeSearch method');
}
const options = {
page: 1,
Expand All @@ -67,6 +67,7 @@ export default class ServiceMapAPI extends HttpClient {
...additionalOptions,
};

if (onlyCount) return this.getCount('unit', options);
return this.getConcurrent('unit', options);
}

Expand Down
78 changes: 44 additions & 34 deletions src/views/ServiceTreeView/ServiceTreeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { SMAccordion, SMButton, TitleBar } from '../../components';
import setMobilityTree from '../../redux/actions/mobilityTree';
import setServiceTree from '../../redux/actions/serviceTree';
import useMobileStatus from '../../utils/isMobile';
import { getUnitCount } from '../../utils/units';
import useLocaleText from '../../utils/useLocaleText';
import ServiceMapAPI from '../../utils/newFetch/ServiceMapAPI';

const getVariantDependentVariables = (variant, serviceTreeServices, mobilityServices) => {
if (variant === 'ServiceTree') {
Expand Down Expand Up @@ -62,6 +62,7 @@ const ServiceTreeView = ({ intl, variant }) => {
const [services, setServices] = useState(prevServices);
const [opened, setOpened] = useState(prevOpened);
const [selected, setSelected] = useState(prevSelected);
const [unitCounts, setUnitCounts] = useState([]);

const citySettings = config.cities?.filter((city) => cities[city]) || [];
const organizationSettings = config.organizations?.filter((org) => organizations[org.id]) || [];
Expand Down Expand Up @@ -89,10 +90,39 @@ const ServiceTreeView = ({ intl, variant }) => {
.then(data => data.results)
);

const fetchNodeCounts = async (nodes, fullSearch) => {
const idList = nodes.map(node => node.id);
// Do not fetch unit counts again for nodes that have the data, unless specified by fullSearch
const filteredIdList = fullSearch ? idList : idList.filter(id => !unitCounts.some(count => count.id === id))
const smAPI = new ServiceMapAPI();
const fetchOptions = {};
if (organizationSettings.length) {
fetchOptions.organization = organizationSettings.map(setting => setting.id);
}
if (citySettings.length) {
fetchOptions.municipality = citySettings;
}
const counts = await Promise.all(
filteredIdList.map(async (id) => {
const count = await smAPI.serviceNodeSearch(id, fetchOptions, true);
return { id, count };
})

);
if (fullSearch) {
setUnitCounts(counts)
} else {
setUnitCounts([...unitCounts, ...counts])
}
}

const setInitialServices = () => {
// Fetch initially shown service nodes when first entering the pag
fetchRootNodes()
.then(data => setServices(data));
.then(data => {
setServices(data)
fetchNodeCounts(data)
});
};

const fetchChildServices = async (service) => {
Expand All @@ -101,6 +131,7 @@ const ServiceTreeView = ({ intl, variant }) => {
.then(response => response.json())
.then((data) => {
setServices([...services, ...data.results]);
fetchNodeCounts(data.results)
// Expand the opened parent node once the child nodes have been fetched
setOpened([...opened, service]);
if (selected.find(e => e.id === service)) {
Expand Down Expand Up @@ -131,7 +162,7 @@ const ServiceTreeView = ({ intl, variant }) => {
if (typeof item === 'number') {
child = selected.find(e => e.id === item);
}
if (child && child.children) {
if (child?.children) {
data.push(...child.children);
child.children.forEach((c) => {
getSelectedChildNodes(c, data);
Expand Down Expand Up @@ -172,7 +203,7 @@ const ServiceTreeView = ({ intl, variant }) => {

// If all other sibling nodes are selected too, select parent node as well
const parent = services.find(service => service.id === item.parent);
if (parent && parent.children.every(child => [...selected, item].some(i => i.id === child))) {
if (parent?.children.every(child => [...selected, item].some(i => i.id === child))) {
newState = [...newState, parent];
}

Expand Down Expand Up @@ -232,40 +263,19 @@ const ServiceTreeView = ({ intl, variant }) => {
}
}, []);

useEffect(() => {
setUnitCounts([]);
fetchNodeCounts(services, true);
}, [cities, organizations]);

function calculateTitle(item) {
if (variant === 'Mobility') {
return getLocaleText(item.name);
}

// Calculate count
const hasCitySettings = citySettings.length && citySettings.length !== config.cities.length;
const hasOrganizationSettings = organizationSettings.length;
const sum = (a, b) => a + b;

let resultCount;
if (!hasCitySettings) {
resultCount = item.unit_count?.total || 0;
} else {
resultCount = citySettings
.map((city) => getUnitCount(item, city))
.reduce(sum, 0);
}

if (hasOrganizationSettings) {
const organisationCount = organizationSettings
.map((org) => org.name.fi.toLowerCase())
.map((orgNameId) => getUnitCount(item, orgNameId))
.reduce(sum, 0);
resultCount = Math.min(resultCount, organisationCount);
}

if (hasOrganizationSettings) {
const approximationText = resultCount
? `${intl.formatMessage({ id: 'general.approximate' }).toLowerCase()} `
: '';
return `${getLocaleText(item.name)} (${approximationText}${resultCount})`;
}
return `${getLocaleText(item.name)} (${resultCount})`;
const countItem = unitCounts.find(countItem => countItem.id === item.id)
return `${getLocaleText(item.name)} ${countItem !== null && countItem !== undefined ? `(${countItem.count})` : ''}`;
}

const expandingComponent = (item, level, last = []) => {
Expand Down Expand Up @@ -318,7 +328,7 @@ const ServiceTreeView = ({ intl, variant }) => {
</StyledText>
)}
collapseContent={
children && children.length ? (
children?.length ? (
<List disablePadding>
{children.map((child, i) => (
expandingComponent(
Expand All @@ -340,7 +350,7 @@ const ServiceTreeView = ({ intl, variant }) => {

const renderServiceNodeList = () => (
<List role="list" disablePadding>
{services && services.map(service => (
{services?.map(service => (
!service.parent && (
expandingComponent(service, 0)
)
Expand Down

0 comments on commit d0d5ce6

Please sign in to comment.