Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API key field to covidcast imports #55

Merged
merged 9 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions src/api/EpiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export function loadDataSet(
fixedParams: Record<string, unknown>,
userParams: Record<string, unknown>,
columns: string[],
api_key = '',
columnRenamings: Record<string, string> = {},
): Promise<DataGroup | null> {
const duplicates = get(expandedDataGroups).filter((d) => d.title == title);
Expand All @@ -131,7 +132,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));
Expand Down Expand Up @@ -168,10 +173,14 @@ export function loadDataSet(
});
}

export function fetchCOVIDcastMeta(): Promise<
{ geo_type: string; signal: string; data_source: string; time_type?: string }[]
> {
const url = new URL(ENDPOINT + `/covidcast_meta/`);
export function fetchCOVIDcastMeta(
api_key: string,
): Promise<{ geo_type: string; signal: string; data_source: string; time_type?: 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; time_type?: string }[]>(url).catch(
(error) => {
Expand All @@ -190,8 +199,9 @@ export function importCDC({ locations, auth }: { locations: string; auth?: strin
{
epiweeks: epiRange(firstEpiWeek.cdc, currentEpiWeek),
},
{ auth, locations },
{ locations },
['total', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8'],
auth,
);
}

Expand All @@ -201,12 +211,14 @@ export function importCOVIDcast({
geo_value,
signal,
time_type = 'day',
api_key,
}: {
data_source: string;
signal: string;
time_type?: string;
geo_type: string;
geo_value: string;
api_key: string;
}): Promise<DataGroup | null> {
const title = `[API] COVIDcast: ${data_source}:${signal} (${geo_type}:${geo_value})`;
return loadDataSet(
Expand All @@ -221,6 +233,7 @@ export function importCOVIDcast({
},
{ data_source, signal, time_type, geo_type, geo_value },
['value', 'stderr', 'sample_size'],
api_key,
);
}

Expand Down Expand Up @@ -343,7 +356,7 @@ export function importFluView({
{
epiweeks: epiRange(firstEpiWeek.fluview, currentEpiWeek),
},
{ regions, issues, lag, auth },
{ regions, issues, lag },
[
'wili',
'ili',
Expand All @@ -357,6 +370,7 @@ export function importFluView({
'num_age_4',
'num_age_5',
],
auth,
{
wili: '%wILI',
ili: '%ILI',
Expand Down Expand Up @@ -395,8 +409,9 @@ export function importGHT({
{
epiweeks: epiRange(firstEpiWeek.ght, currentEpiWeek),
},
{ auth, locations, query },
{ locations, query },
['value'],
auth,
);
}

Expand Down Expand Up @@ -456,8 +471,9 @@ export function importQuidel({ auth, locations }: { auth: string; locations: str
{
epiweeks: epiRange(firstEpiWeek.quidel, currentEpiWeek),
},
{ auth, locations },
{ locations },
['value'],
auth,
);
}
export function importSensors({
Expand All @@ -478,8 +494,9 @@ export function importSensors({
{
epiweeks: epiRange(firstEpiWeek.sensors, currentEpiWeek),
},
{ auth, names, locations },
{ names, locations },
['value'],
auth,
);
}
// twtr
Expand All @@ -504,8 +521,9 @@ export function importTwitter({
: {
epiweeks: epiRange(firstEpiWeek.twitter, currentEpiWeek),
},
{ auth, locations, resolution },
{ locations, resolution },
['num', 'total', 'percent'],
auth,
);
}
export function importWiki({
Expand Down
89 changes: 69 additions & 20 deletions src/components/dialogs/dataSources/COVIDcast.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

export let id: string;

let api_key = '';
let data_source = '';
let signal = '';
let geo_type = '';
let geo_value = '';
let form_key = '';
let valid_key = true;

let dataSources: (LabelValue & { signals: string[] })[] = [];
let geoTypes: string[] = [];
Expand All @@ -23,38 +26,77 @@
}
}

onMount(() => {
fetchCOVIDcastMeta().then((res) => {
geoTypes = [...new Set(res.map((d) => d.geo_type))];
const byDataSource = new Map<string, LabelValue & { signals: string[] }>();
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);
// Helper function; delay invoking "fn" until "ms" milliseconds have passed
const debounce = (fn: Function, ms = 500) => {
let timeoutId: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
};

function fetchMetadata() {
fetchCOVIDcastMeta(form_key).then((res) => {
if (res.length == 0) {
valid_key = false;
} 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<string, LabelValue & { signals: string[] }>();
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 fetchCOVIDcastMeta().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 });
return importCOVIDcast({ data_source, signal, geo_type, geo_value, time_type, api_key });
});
}
</script>

<div>
<label class="uk-form-label" for="{id}-apikey">
<a href="https://cmu-delphi.github.io/delphi-epidata/api/api_keys.html">API Key</a> (optional)
</label>
<div class="uk-form-controls">
<input
id="{id}-apikey"
type="text"
class="uk-input"
class:uk-form-danger={!valid_key}
name="api_key"
required={false}
bind:value={form_key}
on:input={debounce(() => fetchMetadata(), 500)}
/>
{#if !valid_key}
<div class="invalid">API key is invalid - ignoring</div>
{/if}
</div>
</div>
<SelectField id="{id}-r" label="Data Source" name="data_source" bind:value={data_source} options={dataSources} />
<SelectField id="{id}-r" label="Data Signal" name="signal" bind:value={signal} options={dataSignals} />
<SelectField id="{id}-gt" label="Geographic Type" bind:value={geo_type} name="geo_type" options={geoTypes} />
Expand All @@ -65,3 +107,10 @@
name="geo_values"
placeholder="e.g., PA or 42003"
/>

<style>
.invalid {
color: red;
font-size: 0.75rem;
}
</style>