Skip to content

Commit 4bcce9c

Browse files
authored
Merge pull request #2 from DevelopmentFinanceObservatory/sync/website/6533af2ae872ced7c72a5d661db87ce073a1ffa4
Sync dfo-website@6533af2ae872
2 parents 5a57247 + 2dea1c3 commit 4bcce9c

6 files changed

Lines changed: 111 additions & 32 deletions

File tree

‎src/components/molecules/DataPrompt/DataPrompt.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const DataPrompt = ({
122122
/>
123123
</form>
124124
<span className={styles.rule} aria-hidden="true" />
125-
{suggestions && (
125+
{suggestions && suggestions.length > 0 && (
126126
<TryChips
127127
examples={suggestions}
128128
onPick={submit}

‎src/components/organisms/DataSearch/DataSearch.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { revealIndex, useReveal } from '@/hooks/useReveal';
2929
import {
3030
catalogueChips,
3131
catalogueTiles,
32+
roundedCount,
3233
STARTING_POINT_QUERY,
3334
type SearchCatalogue,
3435
} from '@/lib/search/catalogue';
@@ -842,8 +843,10 @@ const DataSearch = ({ catalogue }: { catalogue: SearchCatalogue | null }) => {
842843
<h2 className={styles.buildTitle}>Build a Dataset</h2>
843844
<p className={styles.buildText}>
844845
Describe the data you need, tune the parameters, and export a
845-
structured dataset drawn from 40,000+ indicators in the DFO
846-
knowledge graph.
846+
structured dataset drawn from{' '}
847+
{catalogue &&
848+
`${roundedCount(catalogue.counts.indicators)} indicators in `}
849+
the DFO knowledge graph.
847850
</p>
848851
</div>
849852
{tiles.length > 0 && <StatTiles stats={tiles} />}

‎src/components/organisms/DownloadDataset/DownloadDataset.tsx‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ import DataPrompt from '@/components/molecules/DataPrompt/DataPrompt';
1111
import SectionHeading from '@/components/molecules/SectionHeading/SectionHeading';
1212
import StatTiles from '@/components/molecules/StatTiles/StatTiles';
1313
import { revealIndex, useReveal } from '@/hooks/useReveal';
14-
import { catalogueChips, catalogueTiles, type SearchCatalogue } from '@/lib/search/catalogue';
14+
import {
15+
catalogueChips,
16+
catalogueTiles,
17+
roundedCount,
18+
type SearchCatalogue,
19+
} from '@/lib/search/catalogue';
1520

1621
// `catalogue` is the search catalogue the homepage loaded on the server, or
17-
// null: its examples are the chips, its counts the tiles.
22+
// null: its examples are the chips, its counts the tiles and the indicator
23+
// figure in the description.
1824
const DownloadDataset = ({ catalogue }: { catalogue: SearchCatalogue | null }) => {
1925
const router = useRouter();
2026
const sectionRef = useReveal<HTMLElement>();
@@ -46,7 +52,10 @@ const DownloadDataset = ({ catalogue }: { catalogue: SearchCatalogue | null }) =
4652
<div className={styles.aside} data-reveal-group="trigger">
4753
<p className={styles.description} data-reveal="text" style={revealIndex(0)}>
4854
Describe the data you need, tune your parameters, and export a clean
49-
structured dataset from over 40,000 indicators with more to come.
55+
structured dataset
56+
{catalogue &&
57+
` from ${roundedCount(catalogue.counts.indicators)} indicators`}
58+
.
5059
</p>
5160
<Button
5261
variant="dark"

‎src/lib/search/catalogue.ts‎

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ export type AidCounts = {
2929
years: { first: number; last: number } | null;
3030
};
3131

32-
/** What DFO holds as a whole. */
32+
/** What DFO holds as a whole. `indicators` counts the StatVars search can
33+
* serve and `datasets` the source datasets behind them. */
3334
export type CatalogueCounts = {
3435
publishers: number;
3536
countries: number;
37+
indicators: number;
38+
datasets: number;
3639
aid: AidCounts;
3740
};
3841

@@ -59,8 +62,9 @@ export type PlaceCatalogue = {
5962
examples: CatalogueExample[];
6063
};
6164

62-
/** The first idle chip. It shows the starting point from the catalogue the
63-
* page already holds, with no search call. */
65+
/** The starting point's own question. Asked as a search, it shows the
66+
* starting point from the catalogue the page already holds, with no search
67+
* call. */
6468
export const STARTING_POINT_QUERY = 'What data do you have?';
6569

6670
/** How many examples a declined out_of_scope answer offers. */
@@ -83,7 +87,14 @@ const isExample = (value: unknown): value is CatalogueExample => {
8387

8488
type CatalogueBody = {
8589
catalogue_version?: unknown;
86-
counts?: { publishers?: unknown; countries?: unknown; variables?: unknown; aid?: unknown };
90+
counts?: {
91+
publishers?: unknown;
92+
countries?: unknown;
93+
indicators?: unknown;
94+
datasets?: unknown;
95+
variables?: unknown;
96+
aid?: unknown;
97+
};
8798
examples?: unknown;
8899
};
89100

@@ -141,14 +152,23 @@ const parseBody = (body: unknown, scoped: 'countries' | 'variables') => {
141152
/** The whole catalogue read off the wire, or null when the body is not one. */
142153
export function parseCatalogue(body: unknown): SearchCatalogue | null {
143154
const read = parseBody(body, 'countries');
155+
const counts = (body as CatalogueBody | null)?.counts;
144156

145-
return (
146-
read && {
147-
catalogue_version: read.catalogue_version,
148-
counts: { publishers: read.publishers, countries: read.scoped, aid: read.aid },
149-
examples: read.examples,
150-
}
151-
);
157+
if (!read || !isCount(counts?.indicators) || !isCount(counts.datasets)) {
158+
return null;
159+
}
160+
161+
return {
162+
catalogue_version: read.catalogue_version,
163+
counts: {
164+
publishers: read.publishers,
165+
countries: read.scoped,
166+
indicators: counts.indicators,
167+
datasets: counts.datasets,
168+
aid: read.aid,
169+
},
170+
examples: read.examples,
171+
};
152172
}
153173

154174
/** One country's catalogue read off the wire, or null when the body is not
@@ -165,13 +185,30 @@ export function parsePlaceCatalogue(body: unknown): PlaceCatalogue | null {
165185
);
166186
}
167187

168-
/** The idle prompt's chips: the starting point, then every example. */
188+
/** The idle prompt's chips: every example. */
169189
export function catalogueChips(catalogue: SearchCatalogue | null): string[] {
170-
return [STARTING_POINT_QUERY, ...(catalogue?.examples ?? []).map((example) => example.text)];
190+
return (catalogue?.examples ?? []).map((example) => example.text);
171191
}
172192

173193
const formatCount = (value: number): string => value.toLocaleString('en-GB');
174194

195+
// `en` writes millions as `1M`, where `en-GB` writes `1m`.
196+
const compactFormat = new Intl.NumberFormat('en', { notation: 'compact' });
197+
198+
/** A count of 1,000 or more floored to one significant figure in compact
199+
* form, with a `+` when the floor drops part of it: 112,866 reads `100K+`,
200+
* 100,000 reads `100K`. A smaller count reads exactly. */
201+
export function roundedCount(count: number): string {
202+
if (count < 1000) {
203+
return formatCount(count);
204+
}
205+
206+
const magnitude = 10 ** Math.floor(Math.log10(count));
207+
const floored = Math.floor(count / magnitude) * magnitude;
208+
209+
return `${compactFormat.format(floored)}${floored < count ? '+' : ''}`;
210+
}
211+
175212
const publisherCount = (publishers: number): string =>
176213
`${formatCount(publishers)} ${publishers === 1 ? 'publisher' : 'publishers'}`;
177214

@@ -213,7 +250,7 @@ export function declinedExamples(
213250
return (placed.length > 0 ? placed : (catalogue?.examples ?? [])).slice(0, count);
214251
}
215252

216-
/** The stat tiles on /data and the homepage: publishers and countries, or
253+
/** The stat tiles on /data and the homepage: indicators and datasets, or
217254
* none without a catalogue. */
218255
export function catalogueTiles(
219256
catalogue: SearchCatalogue | null,
@@ -223,7 +260,7 @@ export function catalogueTiles(
223260
}
224261

225262
return [
226-
{ value: formatCount(catalogue.counts.publishers), label: 'Publishers' },
227-
{ value: formatCount(catalogue.counts.countries), label: 'Countries' },
263+
{ value: roundedCount(catalogue.counts.indicators), label: 'Indicators' },
264+
{ value: formatCount(catalogue.counts.datasets), label: 'Datasets' },
228265
];
229266
}

‎src/lib/search/test/catalogue.test.ts‎

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
parseCatalogue,
99
parsePlaceCatalogue,
1010
placeScopeLine,
11+
roundedCount,
1112
scopeLine,
12-
STARTING_POINT_QUERY,
1313
type CatalogueExample,
1414
type PlaceCatalogue,
1515
type SearchCatalogue,
@@ -32,6 +32,8 @@ const catalogue = (examples: CatalogueExample[]): SearchCatalogue => ({
3232
counts: {
3333
publishers: 40,
3434
countries: 205,
35+
indicators: 112866,
36+
datasets: 90,
3537
aid: { donors: 169, recipients: 205, years: { first: 1973, last: 2024 } },
3638
},
3739
examples,
@@ -99,18 +101,27 @@ test('parseCatalogue refuses a body without numeric counts', () => {
99101
assert.equal(parseCatalogue({ ...whole, examples: undefined }), null);
100102
});
101103

102-
test('catalogueChips puts the starting point first, then every example', () => {
104+
test('parseCatalogue refuses a whole body missing indicators or datasets', () => {
105+
for (const field of ['indicators', 'datasets'] as const) {
106+
const counts = Object.fromEntries(
107+
Object.entries(wholeCatalogueBody.counts).filter(([key]) => key !== field),
108+
);
109+
110+
assert.equal(parseCatalogue({ ...wholeCatalogueBody, counts }), null, field);
111+
}
112+
});
113+
114+
test('catalogueChips offers every example', () => {
103115
assert.deepEqual(catalogueChips(whole), [
104-
STARTING_POINT_QUERY,
105116
'Health aid to Ethiopia',
106117
'Humanitarian aid from Germany',
107118
'Debt service paid by Ghana',
108119
'Population of Kenya',
109120
]);
110121
});
111122

112-
test('catalogueChips offers the starting point alone without a catalogue', () => {
113-
assert.deepEqual(catalogueChips(null), [STARTING_POINT_QUERY]);
123+
test('catalogueChips offers no chips without a catalogue', () => {
124+
assert.deepEqual(catalogueChips(null), []);
114125
});
115126

116127
test('scopeLine counts publishers and countries for the whole catalogue', () => {
@@ -164,10 +175,27 @@ test('declinedExamples falls back to the first three of the whole catalogue', ()
164175
assert.deepEqual(declinedExamples(null, null), []);
165176
});
166177

167-
test('catalogueTiles shows publishers and countries, or nothing without a catalogue', () => {
178+
test('catalogueTiles shows indicators and datasets, or nothing without a catalogue', () => {
168179
assert.deepEqual(catalogueTiles(whole), [
169-
{ value: '40', label: 'Publishers' },
170-
{ value: '205', label: 'Countries' },
180+
{ value: '100K+', label: 'Indicators' },
181+
{ value: '90', label: 'Datasets' },
171182
]);
172183
assert.deepEqual(catalogueTiles(null), []);
173184
});
185+
186+
for (const [count, shown] of [
187+
[0, '0'],
188+
[999, '999'],
189+
[1000, '1K'],
190+
[1234, '1K+'],
191+
[100000, '100K'],
192+
[112866, '100K+'],
193+
[250000, '200K+'],
194+
[999999, '900K+'],
195+
[1000000, '1M'],
196+
[1234567, '1M+'],
197+
] as const) {
198+
test(`roundedCount shows ${count} as ${shown}`, () => {
199+
assert.equal(roundedCount(count), shown);
200+
});
201+
}

‎src/lib/search/test/fixtures/catalogue-bodies.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* `GET /api/search/catalogue` bodies, written to the API's `CatalogueResponse`
33
* model (dfo-apis `src/search/catalogue/response.py`) and checked against it
4-
* by scripts/check-search-fixtures.ts. The whole catalogue counts countries;
5-
* one country's counts variables in their place, and either may carry
4+
* by scripts/check-search-fixtures.ts. The whole catalogue counts countries,
5+
* indicators and datasets; one country's counts variables in their place, and either may carry
66
* `aid.years: null` when the build holds no CRS rows.
77
*/
88

@@ -14,6 +14,8 @@ export const wholeCatalogueBody = {
1414
counts: {
1515
publishers: 40,
1616
countries: 205,
17+
indicators: 112866,
18+
datasets: 90,
1719
aid: { donors: 169, recipients: 205, years: { first: 1973, last: 2024 } },
1820
},
1921
examples: [

0 commit comments

Comments
 (0)