From 0a94576fec62b8c5c3dc9e1f02667ec9ebe60114 Mon Sep 17 00:00:00 2001 From: Rostyslav Zatserkovnyi Date: Tue, 13 Aug 2024 01:09:12 +0300 Subject: [PATCH 01/11] Add API key field to covidcast imports --- src/api/EpiData.ts | 18 ++++++-- .../dialogs/dataSources/COVIDcast.svelte | 46 +++++++++++-------- .../dialogs/inputs/TextField.svelte | 3 +- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/api/EpiData.ts b/src/api/EpiData.ts index 80e91fb..f9244dc 100644 --- a/src/api/EpiData.ts +++ b/src/api/EpiData.ts @@ -112,6 +112,7 @@ export function loadDataSet( fixedParams: Record, userParams: Record, columns: string[], + api_key = '', ): Promise { const duplicates = get(expandedDataGroups).filter((d) => d.title == title); if (duplicates.length > 0) { @@ -124,7 +125,11 @@ export function loadDataSet( ) .then(() => null); } - const url = new URL(ENDPOINT + `/${endpoint}/`); + let url_string = ENDPOINT + `/${endpoint}/`; + if (api_key !== '') { + url_string += `?api_key=${api_key}`; + } + const url = new URL(url_string); const params = cleanParams(userParams); Object.entries(fixedParams).forEach(([key, value]) => { url.searchParams.set(key, String(value)); @@ -150,8 +155,12 @@ export function loadDataSet( }); } -export function fetchCOVIDcastMeta(): Promise<{ geo_type: string; signal: string; data_source: string }[]> { - const url = new URL(ENDPOINT + `/covidcast_meta/`); +export function fetchCOVIDcastMeta(api_key = ''): Promise<{ geo_type: string; signal: string; data_source: string }[]> { + let url_string = ENDPOINT + `/covidcast_meta/`; + if (api_key !== '') { + url_string += `?api_key=${api_key}`; + } + const url = new URL(url_string); url.searchParams.set('format', 'json'); return fetchImpl<{ geo_type: string; signal: string; data_source: string }[]>(url).catch((error) => { console.warn('failed fetching data', error); @@ -179,12 +188,14 @@ export function importCOVIDcast({ geo_value, signal, time_type = 'day', + api_key, }: { data_source: string; signal: string; time_type?: 'day'; geo_type: string; geo_value: string; + api_key: string; }): Promise { const title = `[API] Delphi CODIDcast: ${geo_value} ${signal} (${data_source})`; return loadDataSet( @@ -196,6 +207,7 @@ export function importCOVIDcast({ }, { data_source, signal, time_type, geo_type, geo_value }, ['value', 'stderr', 'sample_size'], + api_key, ); } diff --git a/src/components/dialogs/dataSources/COVIDcast.svelte b/src/components/dialogs/dataSources/COVIDcast.svelte index bfbeeff..a0c18a4 100644 --- a/src/components/dialogs/dataSources/COVIDcast.svelte +++ b/src/components/dialogs/dataSources/COVIDcast.svelte @@ -7,6 +7,7 @@ export let id: string; + let api_key = ''; let data_source = ''; let signal = ''; let geo_type = ''; @@ -23,34 +24,41 @@ } } - onMount(() => { - fetchCOVIDcastMeta().then((res) => { - geoTypes = [...new Set(res.map((d) => d.geo_type))]; - const byDataSource = new Map(); - for (const row of res) { - const ds = byDataSource.get(row.data_source); - if (!ds) { - byDataSource.set(row.data_source, { - label: row.data_source, - value: row.data_source, - signals: [row.signal], - }); - } else if (!ds.signals.includes(row.signal)) { - ds.signals.push(row.signal); + function fetchMetadata() { + fetchCOVIDcastMeta((api_key = api_key)).then((res) => { + if (res.length !== 0) { + geoTypes = [...new Set(res.map((d) => d.geo_type))]; + const byDataSource = new Map(); + for (const row of res) { + const ds = byDataSource.get(row.data_source); + if (!ds) { + byDataSource.set(row.data_source, { + label: row.data_source, + value: row.data_source, + signals: [row.signal], + }); + } else if (!ds.signals.includes(row.signal)) { + ds.signals.push(row.signal); + } } + byDataSource.forEach((entry) => { + entry.signals.sort(); + }); + dataSources = [...byDataSource.values()].sort((a, b) => a.value.localeCompare(b.value)); } - byDataSource.forEach((entry) => { - entry.signals.sort(); - }); - dataSources = [...byDataSource.values()].sort((a, b) => a.value.localeCompare(b.value)); }); + } + + onMount(() => { + fetchMetadata(); }); export function importDataSet() { - return importCOVIDcast({ data_source, signal, geo_type, geo_value }); + return importCOVIDcast({ data_source, signal, geo_type, geo_value, api_key }); } + fetchMetadata()} bind:value={api_key} /> diff --git a/src/components/dialogs/inputs/TextField.svelte b/src/components/dialogs/inputs/TextField.svelte index a3ffb09..ab4f13e 100644 --- a/src/components/dialogs/inputs/TextField.svelte +++ b/src/components/dialogs/inputs/TextField.svelte @@ -5,11 +5,12 @@ export let value: string; export let placeholder = ''; export let required = true; + export let on_change: () => void = () => {};
- +
From 85a0dd280cf370e6d19674cb3dd07673b2ae79c3 Mon Sep 17 00:00:00 2001 From: Rostyslav Zatserkovnyi Date: Fri, 16 Aug 2024 15:42:42 +0300 Subject: [PATCH 02/11] Review fixes --- .../dialogs/dataSources/COVIDcast.svelte | 36 +++++++++++++++++-- .../dialogs/inputs/TextField.svelte | 3 +- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/components/dialogs/dataSources/COVIDcast.svelte b/src/components/dialogs/dataSources/COVIDcast.svelte index a0c18a4..f3f0f2f 100644 --- a/src/components/dialogs/dataSources/COVIDcast.svelte +++ b/src/components/dialogs/dataSources/COVIDcast.svelte @@ -12,6 +12,8 @@ let signal = ''; let geo_type = ''; let geo_value = ''; + let form_key = ''; + let invalid_key = false; let dataSources: (LabelValue & { signals: string[] })[] = []; let geoTypes: string[] = []; @@ -25,8 +27,10 @@ } function fetchMetadata() { - fetchCOVIDcastMeta((api_key = api_key)).then((res) => { + fetchCOVIDcastMeta((api_key = form_key)).then((res) => { if (res.length !== 0) { + invalid_key = false; + api_key = form_key; // API key is valid -> use it to fetch data later on geoTypes = [...new Set(res.map((d) => d.geo_type))]; const byDataSource = new Map(); for (const row of res) { @@ -45,6 +49,8 @@ entry.signals.sort(); }); dataSources = [...byDataSource.values()].sort((a, b) => a.value.localeCompare(b.value)); + } else { + invalid_key = api_key != ''; // mark field as invalid, unless it's empty } }); } @@ -58,7 +64,26 @@ } - fetchMetadata()} bind:value={api_key} /> +
+ +
+ fetchMetadata()} + /> + {#if invalid_key} +
API key is invalid - ignoring
+ {/if} +
+
@@ -69,3 +94,10 @@ name="geo_values" placeholder="e.g., PA or 42003" /> + + diff --git a/src/components/dialogs/inputs/TextField.svelte b/src/components/dialogs/inputs/TextField.svelte index ab4f13e..a3ffb09 100644 --- a/src/components/dialogs/inputs/TextField.svelte +++ b/src/components/dialogs/inputs/TextField.svelte @@ -5,12 +5,11 @@ export let value: string; export let placeholder = ''; export let required = true; - export let on_change: () => void = () => {};
- +
From b065357fa72556c4318ccb6148fc6856a24bdf97 Mon Sep 17 00:00:00 2001 From: Rostyslav Zatserkovnyi Date: Fri, 16 Aug 2024 15:48:31 +0300 Subject: [PATCH 03/11] +API key --- src/components/dialogs/dataSources/COVIDcast.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dialogs/dataSources/COVIDcast.svelte b/src/components/dialogs/dataSources/COVIDcast.svelte index 68deb0c..49c8eaa 100644 --- a/src/components/dialogs/dataSources/COVIDcast.svelte +++ b/src/components/dialogs/dataSources/COVIDcast.svelte @@ -60,7 +60,7 @@ }); export function importDataSet() { - return fetchCOVIDcastMeta().then((res) => { + return fetchCOVIDcastMeta((api_key = api_key)).then((res) => { const meta = res.filter((row) => row.data_source === data_source && row.signal === signal); const time_type = meta[0].time_type; return importCOVIDcast({ data_source, signal, geo_type, geo_value, time_type, api_key }); From 51c1dbc0357c93f330818a1cf85f7264e673c318 Mon Sep 17 00:00:00 2001 From: Rostyslav Zatserkovnyi Date: Tue, 24 Sep 2024 15:30:35 +0300 Subject: [PATCH 04/11] Review fixes --- src/api/EpiData.ts | 2 +- .../dialogs/dataSources/COVIDcast.svelte | 33 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/api/EpiData.ts b/src/api/EpiData.ts index 9f4b3dc..074e76b 100644 --- a/src/api/EpiData.ts +++ b/src/api/EpiData.ts @@ -156,7 +156,7 @@ export function loadDataSet( } export function fetchCOVIDcastMeta( - api_key = '', + api_key: string, ): Promise<{ geo_type: string; signal: string; data_source: string; time_type?: string }[]> { let url_string = ENDPOINT + `/covidcast_meta/`; if (api_key !== '') { diff --git a/src/components/dialogs/dataSources/COVIDcast.svelte b/src/components/dialogs/dataSources/COVIDcast.svelte index 49c8eaa..130b482 100644 --- a/src/components/dialogs/dataSources/COVIDcast.svelte +++ b/src/components/dialogs/dataSources/COVIDcast.svelte @@ -13,7 +13,7 @@ let geo_type = ''; let geo_value = ''; let form_key = ''; - let invalid_key = false; + let valid_key = true; let dataSources: (LabelValue & { signals: string[] })[] = []; let geoTypes: string[] = []; @@ -26,10 +26,21 @@ } } + // Helper function; delay invoking "fn" until "ms" milliseconds have passed + const debounce = (fn: Function, ms = 500) => { + let timeoutId: ReturnType; + return function (this: any, ...args: any[]) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn.apply(this, args), ms); + }; + }; + function fetchMetadata() { - fetchCOVIDcastMeta((api_key = form_key)).then((res) => { - if (res.length !== 0) { - invalid_key = false; + fetchCOVIDcastMeta(form_key).then((res) => { + if (res.length == 0) { + valid_key = form_key == ''; // mark key as valid if it's empty, otherwise invalid + } else { + valid_key = true; api_key = form_key; // API key is valid -> use it to fetch data later on geoTypes = [...new Set(res.map((d) => d.geo_type))]; const byDataSource = new Map(); @@ -49,8 +60,6 @@ entry.signals.sort(); }); dataSources = [...byDataSource.values()].sort((a, b) => a.value.localeCompare(b.value)); - } else { - invalid_key = api_key != ''; // mark field as invalid, unless it's empty } }); } @@ -60,7 +69,7 @@ }); export function importDataSet() { - return fetchCOVIDcastMeta((api_key = api_key)).then((res) => { + return fetchCOVIDcastMeta(api_key).then((res) => { const meta = res.filter((row) => row.data_source === data_source && row.signal === signal); const time_type = meta[0].time_type; return importCOVIDcast({ data_source, signal, geo_type, geo_value, time_type, api_key }); @@ -69,21 +78,21 @@
-