Skip to content

Commit

Permalink
Merge pull request #72 from cmu-delphi/rzatserkovnyi/persistent-apikeys
Browse files Browse the repository at this point in the history
Add persistence to API keys
  • Loading branch information
rzats authored Dec 18, 2024
2 parents f6c12ab + 20fd21f commit 6b6fe3b
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 32 deletions.
30 changes: 23 additions & 7 deletions src/components/dialogs/ImportAPIDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import NowCast from './dataSources/Nowcast.svelte';
import CovidHosp from './dataSources/COVIDHosp.svelte';
import CoviDcast from './dataSources/COVIDcast.svelte';
import { navMode } from '../../store';
import { navMode, storeApiKeys } from '../../store';
import { NavMode } from '../chartUtils';
const dispatch = createEventDispatcher();
Expand Down Expand Up @@ -173,10 +173,26 @@
{/if}
</form>

<button slot="footer" class="uk-button uk-button-primary" type="submit" form={id} disabled={loading}>
Fetch Data
{#if loading}
<div uk-spinner />
{/if}
</button>
<div slot="footer">
<div class="uk-form-controls uk-form-controls-text container">
<button class="uk-button uk-button-primary" type="submit" form={id} disabled={loading}>
Fetch Data
{#if loading}
<div uk-spinner />
{/if}
</button>
<label
><input class="uk-checkbox" type="checkbox" bind:checked={$storeApiKeys} />
Save API key (auth token) between visits</label
>
</div>
</div>
</Dialog>

<style>
.container {
display: flex;
align-items: center;
column-gap: 2em;
}
</style>
12 changes: 9 additions & 3 deletions src/components/dialogs/dataSources/CDC.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
import { cdcLocations as regions } from '../../../data/data';
import SelectField from '../inputs/SelectField.svelte';
import TextField from '../inputs/TextField.svelte';
import { apiKey } from '../../../store';
export let id: string;
let locations = regions[0].value;
let auth = '';
export function importDataSet() {
return importCDC({ locations, auth });
return importCDC({ locations, auth: $apiKey });
}
</script>

<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
<TextField
id="{id}-auth"
name="auth"
label="Authorizaton Token"
bind:value={$apiKey}
placeholder="authorization token"
/>
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
12 changes: 5 additions & 7 deletions src/components/dialogs/dataSources/COVIDcast.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import type { LabelValue } from '../../../data/data';
import SelectField from '../inputs/SelectField.svelte';
import TextField from '../inputs/TextField.svelte';
import { apiKey } from '../../../store';
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[] })[] = [];
Expand All @@ -36,12 +35,11 @@
};
function fetchMetadata() {
fetchCOVIDcastMeta(form_key).then((res) => {
fetchCOVIDcastMeta($apiKey).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) {
Expand Down Expand Up @@ -69,10 +67,10 @@
});
export function importDataSet() {
return fetchCOVIDcastMeta(api_key).then((res) => {
return fetchCOVIDcastMeta($apiKey).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 });
return importCOVIDcast({ data_source, signal, geo_type, geo_value, time_type, api_key: $apiKey });
});
}
</script>
Expand All @@ -89,7 +87,7 @@
class:uk-form-danger={!valid_key}
name="api_key"
required={false}
bind:value={form_key}
bind:value={$apiKey}
on:input={debounce(() => fetchMetadata(), 500)}
/>
{#if !valid_key}
Expand Down
6 changes: 3 additions & 3 deletions src/components/dialogs/dataSources/FluView.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { importFluView } from '../../../api/EpiData';
import { fluViewRegions } from '../../../data/data';
import { apiKey } from '../../../store';
import SelectField from '../inputs/SelectField.svelte';
import SelectIssue from '../inputs/SelectIssue.svelte';
import TextField from '../inputs/TextField.svelte';
Expand All @@ -10,10 +11,9 @@
let regions = fluViewRegions[0].value;
let issue = DEFAULT_ISSUE;
let auth: string = '';
export function importDataSet() {
return importFluView({ regions, ...issue, auth });
return importFluView({ regions, ...issue, auth: $apiKey });
}
</script>

Expand All @@ -23,7 +23,7 @@
id="{id}-auth"
name="auth"
label="Auth Key"
bind:value={auth}
bind:value={$apiKey}
required={false}
placeholder="authorization token"
/>
12 changes: 9 additions & 3 deletions src/components/dialogs/dataSources/GHT.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
<script lang="ts">
import { importGHT } from '../../../api/EpiData';
import { ghtLocations as regions } from '../../../data/data';
import { apiKey } from '../../../store';
import SelectField from '../inputs/SelectField.svelte';
import TextField from '../inputs/TextField.svelte';
export let id: string;
let locations = regions[0].value;
let auth = '';
let query = '';
export function importDataSet() {
return importGHT({ auth, locations, query });
return importGHT({ auth: $apiKey, locations, query });
}
</script>

<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
<TextField
id="{id}-auth"
name="auth"
label="Authorizaton Token"
bind:value={$apiKey}
placeholder="authorization token"
/>
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
<TextField id="{id}-query" name="query" label="Search Query or Topic" bind:value={query} />
12 changes: 9 additions & 3 deletions src/components/dialogs/dataSources/Quidel.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
<script lang="ts">
import { importQuidel } from '../../../api/EpiData';
import { quidelLocations as regions } from '../../../data/data';
import { apiKey } from '../../../store';
import SelectField from '../inputs/SelectField.svelte';
import TextField from '../inputs/TextField.svelte';
export let id: string;
let locations = regions[0].value;
let auth = '';
export function importDataSet() {
return importQuidel({ auth, locations });
return importQuidel({ auth: $apiKey, locations });
}
</script>

<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
<TextField
id="{id}-auth"
name="auth"
label="Authorizaton Token"
bind:value={$apiKey}
placeholder="authorization token"
/>
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
12 changes: 9 additions & 3 deletions src/components/dialogs/dataSources/Sensors.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
<script lang="ts">
import { importSensors } from '../../../api/EpiData';
import { sensorLocations as regions, sensorNames } from '../../../data/data';
import { apiKey } from '../../../store';
import SelectField from '../inputs/SelectField.svelte';
import TextField from '../inputs/TextField.svelte';
export let id: string;
let locations = regions[0].value;
let auth = '';
let names = sensorNames[0].value;
export function importDataSet() {
return importSensors({ auth, names, locations });
return importSensors({ auth: $apiKey, names, locations });
}
</script>

<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
<TextField
id="{id}-auth"
name="auth"
label="Authorizaton Token"
bind:value={$apiKey}
placeholder="authorization token"
/>
<SelectField id="{id}-s" label="Name" bind:value={names} options={sensorNames} name="sensor" />
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
12 changes: 9 additions & 3 deletions src/components/dialogs/dataSources/Twitter.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
<script lang="ts">
import { importTwitter } from '../../../api/EpiData';
import { twitterLocations as regions } from '../../../data/data';
import { apiKey } from '../../../store';
import SelectField from '../inputs/SelectField.svelte';
import TextField from '../inputs/TextField.svelte';
export let id: string;
let locations = regions[0].value;
let auth = '';
let resolution: 'daily' | 'weekly' = 'daily';
export function importDataSet() {
return importTwitter({ auth, locations, resolution });
return importTwitter({ auth: $apiKey, locations, resolution });
}
</script>

<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
<TextField
id="{id}-auth"
name="auth"
label="Authorizaton Token"
bind:value={$apiKey}
placeholder="authorization token"
/>
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
<div>
<div class="uk-form-label">Temporal Resolution</div>
Expand Down
22 changes: 22 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ export const isShowingPoints = writable(defaults.showPoints);
export const initialViewport = writable(defaults.viewport);
export const navMode = writable(NavMode.autofit);

export const storeApiKeys = writable(localStorage.getItem('store-api-key') === 'true');
storeApiKeys.subscribe((val) => {
localStorage.setItem('store-api-key', val.toString());
if (val) {
// persist key from session to local storage
localStorage.setItem('api-key', sessionStorage.getItem('api-key') || '');
} else {
// remove key from local storage
localStorage.removeItem('api-key');
}
});

export const apiKey = writable(localStorage.getItem('api-key')! || '');
apiKey.subscribe((val) => {
// always keep key around in session storage (resets on page refresh)
sessionStorage.setItem('api-key', val);
if (localStorage.getItem('store-api-key') === 'true') {
// store it in local storage (persistent)
localStorage.setItem('api-key', val);
}
});

export function addDataSet(dataset: DataSet | DataGroup): void {
const root = get(datasetTree);
root.datasets.push(dataset);
Expand Down

0 comments on commit 6b6fe3b

Please sign in to comment.