From 609bbfab71b32c84645a8114b37ca2e82edbbf22 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 13 Jan 2025 08:24:48 -0500 Subject: [PATCH 01/47] fix: not found text for typeahead --- src/components/SearchForm.vue | 6 +- src/pages/data-request/create.vue | 152 ++++---- src/pages/data-source/create.vue | 568 ++++++++++++++---------------- 3 files changed, 349 insertions(+), 377 deletions(-) diff --git a/src/components/SearchForm.vue b/src/components/SearchForm.vue index a256b41..8d8c0c3 100644 --- a/src/components/SearchForm.vue +++ b/src/components/SearchForm.vue @@ -25,9 +25,9 @@ diff --git a/src/pages/data-request/create.vue b/src/pages/data-request/create.vue index 47f7197..432b780 100644 --- a/src/pages/data-request/create.vue +++ b/src/pages/data-request/create.vue @@ -18,14 +18,12 @@ name="new-request" :schema="SCHEMA" @error="error" - @submit="submit" - > + @submit="submit"> + placeholder="Briefly describe the general purpose or topic."> @@ -47,8 +45,7 @@ if (indexToRemove > -1) selectedLocations.splice(indexToRemove, 1); } - " - /> + " /> + @on-input="fetchTypeaheadResults"> + + position="left"> @@ -105,8 +106,7 @@ class="md:col-span-2" :name="INPUT_NAMES.target" :options="SELECT_OPTS" - placeholder="When would you like to see this request filled?" - > + placeholder="When would you like to see this request filled?"> @@ -117,8 +117,7 @@ class="md:col-start-1 md:col-end-2" :name="INPUT_NAMES.notes" placeholder="What are you trying to learn? Is there anything you've already tried?" - rows="4" - > + rows="4"> @@ -129,31 +128,27 @@ class="md:col-start-2 md:col-end-3" :name="INPUT_NAMES.requirements" placeholder="Details the data must have, like 'case numbers' or 'incident location'." - rows="4" - > + rows="4">
+ class="flex gap-2 flex-col max-w-full md:flex-row md:col-start-1 md:col-end-2 mt-8">
@@ -168,42 +163,42 @@ import { InputText, InputSelect, InputTextArea, - InputDatePicker, -} from "pdap-design-system"; -import Typeahead from "@/components/TypeaheadInput.vue"; -import LocationSelected from "@/components/TypeaheadSelected.vue"; -import { toast } from "vue3-toastify"; -import { createRequest } from "@/api/data-requests"; -import { getFullLocationText } from "@/util/locationFormatters"; -import _debounce from "lodash/debounce"; -import _cloneDeep from "lodash/cloneDeep"; -import { nextTick, ref, watch } from "vue"; -import { getTypeaheadLocations } from "@/api/typeahead"; + InputDatePicker +} from 'pdap-design-system'; +import Typeahead from '@/components/TypeaheadInput.vue'; +import LocationSelected from '@/components/TypeaheadSelected.vue'; +import { toast } from 'vue3-toastify'; +import { createRequest } from '@/api/data-requests'; +import { getFullLocationText } from '@/util/locationFormatters'; +import _debounce from 'lodash/debounce'; +import _cloneDeep from 'lodash/cloneDeep'; +import { nextTick, ref, watch } from 'vue'; +import { getTypeaheadLocations } from '@/api/typeahead'; const INPUT_NAMES = { // contact: 'contact', - title: "title", - area: "area", - range: "coverage_range", - target: "request_urgency", - notes: "submission_notes", - requirements: "data_requirements", + title: 'title', + area: 'area', + range: 'coverage_range', + target: 'request_urgency', + notes: 'submission_notes', + requirements: 'data_requirements' }; const SELECT_OPTS = [ - { value: "urgent", label: "Urgent (Less than a week)" }, + { value: 'urgent', label: 'Urgent (Less than a week)' }, { - value: "somewhat_urgent", - label: "Somewhat urgent (Less than a month)", + value: 'somewhat_urgent', + label: 'Somewhat urgent (Less than a month)' }, { - value: "not_urgent", - label: "Not urgent (A few months)", + value: 'not_urgent', + label: 'Not urgent (A few months)' }, { - value: "long_term", - label: "Long term (6 months or more)", + value: 'long_term', + label: 'Long term (6 months or more)' }, - { value: "indefinite_unknown", label: "Indefinite/Unknown" }, + { value: 'indefinite_unknown', label: 'Indefinite/Unknown' } ]; const SCHEMA = [ // { @@ -220,47 +215,46 @@ const SCHEMA = [ validators: { required: { value: true, - message: "Please let us know what to call this request.", - }, - }, + message: 'Please let us know what to call this request.' + } + } }, { name: INPUT_NAMES.range, validators: { required: { value: true, - message: "Please let us know a range of years to look for this data.", - }, - }, + message: 'Please let us know a range of years to look for this data.' + } + } }, { name: INPUT_NAMES.target, validators: { required: { value: true, - message: - "Please let us know when you'd like this request to be filled.", - }, - }, + message: "Please let us know when you'd like this request to be filled." + } + } }, { name: INPUT_NAMES.notes, validators: { required: { value: true, - message: "Please let us know a little more about your request.", - }, - }, + message: 'Please let us know a little more about your request.' + } + } }, { name: INPUT_NAMES.requirements, validators: { required: { value: true, - message: "Please let us know the requirements for this request.", - }, - }, - }, + message: 'Please let us know the requirements for this request.' + } + } + } ]; const selectedLocations = ref([]); @@ -286,7 +280,7 @@ const fetchTypeaheadResults = _debounce( } }, 350, - { leading: true, trailing: true }, + { leading: true, trailing: true } ); async function clear() { @@ -296,9 +290,9 @@ async function clear() { .reduce( (acc, cur) => ({ ...acc, - [cur]: "", + [cur]: '' }), - {}, + {} ); formRef.value.setValues(newVal); @@ -310,22 +304,22 @@ async function clear() { function error(v$) { // Janky error handling for typeahead because it's not a controlled input - on form error, check for this error, too if (v$.value.$anyDirty && !selectedLocations.value.length) { - typeaheadError.value = "Please include a location with your request"; + typeaheadError.value = 'Please include a location with your request'; } } async function submit(values) { if (!selectedLocations.value.length) { // Janky error handling for typeahead because it's not a controlled input - if form doesn't error, check for this error anyway. - typeaheadError.value = "Please include a location with your request"; + typeaheadError.value = 'Please include a location with your request'; return; } requestPending.value = true; if (values[INPUT_NAMES.range]?.length) { const range = values[INPUT_NAMES.range] - .map((d) => (typeof d === "number" ? String(d) : "")) - .join(" - "); + .map((d) => (typeof d === 'number' ? String(d) : '')) + .join(' - '); values[INPUT_NAMES.range] = range; } @@ -340,8 +334,8 @@ async function submit(values) { delete loc.display_name; delete loc.location_id; return loc; - }), - ], + }) + ] }; try { @@ -352,7 +346,7 @@ async function submit(values) { } catch (error) { if (error) { console.error(error); - formError.value = "Something went wrong, please try again."; + formError.value = 'Something went wrong, please try again.'; formRef.value.setValues({ ...values }); var isError = !!error; } @@ -375,9 +369,9 @@ watch( // clearing and re-applying when dirty if (!selected.length && formRef.value.v$.$anyDirty) { - typeaheadError.value = "Please include a location with your request"; + typeaheadError.value = 'Please include a location with your request'; } - }, + } ); diff --git a/src/pages/data-source/create.vue b/src/pages/data-source/create.vue index e7d74fe..d376875 100644 --- a/src/pages/data-source/create.vue +++ b/src/pages/data-source/create.vue @@ -21,17 +21,18 @@ class="flex flex-col gap-2" name="new-request" :schema="SCHEMA" - @submit="submit" - > + @submit="submit"> + @input="checkDuplicates"> @@ -39,8 +40,7 @@ :id="'input-' + INPUT_NAMES.readMeUrl" class="md:col-span-2" :name="INPUT_NAMES.readMeUrl" - placeholder="A link to any contextual info, like a data dictionary or explanation of the data." - > + placeholder="A link to any contextual info, like a data dictionary or explanation of the data."> @@ -61,8 +61,7 @@ const indexToRemove = selectedAgencies.indexOf(agency); if (indexToRemove > -1) selectedAgencies.splice(indexToRemove, 1); } - " - /> + " /> + @on-input="fetchTypeaheadResults"> diff --git a/src/pages/about.vue b/src/pages/about.vue index 8b1ff2b..e166c7e 100644 --- a/src/pages/about.vue +++ b/src/pages/about.vue @@ -30,24 +30,21 @@ + rel="noreferrer"> Eddie Brown, + rel="noreferrer"> Alec Akin, and + rel="noreferrer"> Josh Lintag.

@@ -56,8 +53,7 @@ + rel="noreferrer"> Josh Chamberlain.

@@ -66,8 +62,7 @@ + rel="noreferrer"> Max Chis.

@@ -76,8 +71,7 @@ + rel="noreferrer"> Joshua Graber.

@@ -100,8 +94,7 @@
+ class="pdap-grid-container pdap-grid-container-columns-2 mb-12 px-4 md:px-8">

Frequently Asked Questions

What makes you experts?

@@ -120,8 +113,7 @@ + rel="noreferrer"> what a Data Source is in our docs. @@ -154,8 +146,7 @@ + rel="noreferrer"> established legal precedents.
diff --git a/src/pages/change-password.vue b/src/pages/change-password.vue index 087c408..a5056d6 100644 --- a/src/pages/change-password.vue +++ b/src/pages/change-password.vue @@ -29,8 +29,7 @@ @@ -44,13 +43,11 @@ :schema="VALIDATION_SCHEMA" @change="onChange" @submit="onSubmit" - @input="onInput" - > + @input="onInput"> + :key="input.name" /> @@ -63,78 +60,78 @@ diff --git a/src/pages/data-source/[id].vue b/src/pages/data-source/[id].vue index e35284f..935213d 100644 --- a/src/pages/data-source/[id].vue +++ b/src/pages/data-source/[id].vue @@ -5,25 +5,21 @@ :search-ids="searchStore.mostRecentSearchIds" :previous-index="previousIdIndex" :next-index="nextIdIndex" - :set-nav-is="(val) => (navIs = val)" - /> + :set-nav-is="(val) => (navIs = val)" />

+ class="flex items-center justify-center h-[80vh] w-full flex-col relative"> + text="Fetching data source results..." />
+ class="flex flex-col sm:flex-row sm:flex-wrap items-center sm:items-stretch sm:justify-between gap-4 h-full w-full relative"> diff --git a/src/pages/donate.vue b/src/pages/donate.vue index 15c9285..ab334ef 100644 --- a/src/pages/donate.vue +++ b/src/pages/donate.vue @@ -1,7 +1,6 @@ diff --git a/src/pages/jobs.vue b/src/pages/jobs.vue index 297923d..778793a 100644 --- a/src/pages/jobs.vue +++ b/src/pages/jobs.vue @@ -62,8 +62,7 @@ work on our software projects. During an initial interview, we will discuss which issues on + href="https://github.com/orgs/Police-Data-Accessibility-Project/projects/21/views/2"> the roadmap seem like a good fit for your skills. @@ -112,8 +111,6 @@ diff --git a/src/pages/profile/_components/APIKey.vue b/src/pages/profile/_components/APIKey.vue index 6ebeb0b..7602695 100644 --- a/src/pages/profile/_components/APIKey.vue +++ b/src/pages/profile/_components/APIKey.vue @@ -1,7 +1,6 @@ diff --git a/src/pages/profile/_components/ThreeColumnTable.vue b/src/pages/profile/_components/ThreeColumnTable.vue index 0dd33dd..9bc96db 100644 --- a/src/pages/profile/_components/ThreeColumnTable.vue +++ b/src/pages/profile/_components/ThreeColumnTable.vue @@ -1,15 +1,13 @@ - + diff --git a/src/pages/sign-in.vue b/src/pages/sign-in.vue index ddc3098..26e03af 100644 --- a/src/pages/sign-in.vue +++ b/src/pages/sign-in.vue @@ -5,8 +5,7 @@
+ class="flex items-center justify-center h-full w-full">
@@ -28,8 +27,7 @@ class="border-2 border-neutral-950 border-solid [&>svg]:ml-0" intent="tertiary" :disabled="githubAuthData?.userExists" - @click="async () => await beginOAuthLogin()" - > + @click="async () => await beginOAuthLogin()"> Sign in with Github @@ -43,8 +41,7 @@ name="login" :error="error" :schema="VALIDATION_SCHEMA" - @submit="onSubmit" - > + @submit="onSubmit"> + placeholder="Your email address" /> + placeholder="Your password" />
+ class="flex flex-col items-start sm:flex-row sm:items-center sm:gap-4 w-full"> + to="/sign-up"> Create Account + to="/request-reset-password"> Reset Password
@@ -99,16 +90,16 @@ diff --git a/src/pages/sign-up/index.vue b/src/pages/sign-up/index.vue index af10dd9..9f1c490 100644 --- a/src/pages/sign-up/index.vue +++ b/src/pages/sign-up/index.vue @@ -6,8 +6,7 @@
+ class="flex items-center justify-center h-full w-full">
@@ -30,8 +29,7 @@ class="border-2 border-neutral-950 border-solid [&>svg]:ml-0" intent="tertiary" :disabled="githubAuthData?.userExists" - @click="async () => await beginOAuthLogin('/sign-up')" - > + @click="async () => await beginOAuthLogin('/sign-up')"> Sign up with Github @@ -47,8 +45,7 @@ :schema="VALIDATION_SCHEMA" @change="onChange" @submit="onSubmit" - @input="onInput" - > + @input="onInput"> + placeholder="Your email address" /> + :key="input.name" /> @@ -72,28 +67,24 @@ :disabled="loading" :is-loading="loading" type="submit" - data-test="submit-button" - > + data-test="submit-button"> Create Account
+ class="flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:gap-4 sm:flex-wrap w-full">

Already have an account?

+ to="/sign-in"> Log in + to="/request-reset-password"> Reset Password
@@ -107,16 +98,16 @@ diff --git a/src/pages/validate/email.vue b/src/pages/validate/email.vue index 5d4d79d..b93111b 100644 --- a/src/pages/validate/email.vue +++ b/src/pages/validate/email.vue @@ -1,17 +1,16 @@ - + + + + + PDAP + + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/src/App.vue b/src/App.vue index 35e635b..0f7c4f8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,7 +26,6 @@ import { ErrorBoundary, Footer, Header, Spinner } from 'pdap-design-system'; import AuthWrapper from './components/AuthWrapper.vue'; import acronym from 'pdap-design-system/images/acronym.svg'; -import lockup from 'pdap-design-system/images/lockup.svg'; import { NAV_LINKS, FOOTER_LINKS } from '@/util/constants'; import { provide, ref } from 'vue'; From 6dbad8b7d4558c8f6eda65c3d3db15f3e69c92eb Mon Sep 17 00:00:00 2001 From: Josh Chamberlain Date: Wed, 8 Jan 2025 14:27:07 -0500 Subject: [PATCH 06/47] reduce nav links --- src/util/constants.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/util/constants.js b/src/util/constants.js index bc2afe1..03613b8 100644 --- a/src/util/constants.js +++ b/src/util/constants.js @@ -3,18 +3,11 @@ import { FOOTER_LINK_ICONS } from 'pdap-design-system'; export const ALL_LOCATION_TYPES = ['locality', 'county', 'state', 'federal']; export const NAV_LINKS = [ + { path: '/', - text: 'Search' - }, - { - path: '/data', text: 'Data' }, - { - path: '/community', - text: 'Community' - }, { path: '/about', text: 'About' @@ -23,10 +16,6 @@ export const NAV_LINKS = [ path: '/donate', text: 'Donate' }, - { - path: '/jobs', - text: 'Jobs' - }, { href: 'https://docs.pdap.io/', text: 'Docs' From 16d756d1ca1fc31ab9a17e45a5df435bfa9d0ec1 Mon Sep 17 00:00:00 2001 From: Josh Chamberlain Date: Wed, 8 Jan 2025 14:33:54 -0500 Subject: [PATCH 07/47] remove main scroll --- src/pages/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/index.vue b/src/pages/index.vue index 5d6e711..dfde774 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -5,7 +5,7 @@ + + Date: Fri, 17 Jan 2025 00:04:25 -0500 Subject: [PATCH 29/47] add caching to requests --- src/api/data-requests.js | 52 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/api/data-requests.js b/src/api/data-requests.js index dd1c7cd..f9e54ab 100644 --- a/src/api/data-requests.js +++ b/src/api/data-requests.js @@ -94,33 +94,47 @@ export async function createRequest(data) { } export async function getRecentRequests() { + const requestsStore = useDataRequestsStore(); + + const cached = requestsStore.getDataRequestFromCache('recent-requests'); + + if ( + cached && + isCachedResponseValid({ + cacheTime: cached.timestamp, + intervalBeforeInvalidation: 1000 * 60 * 3, // 3 minutes + }) + ) { + return cached.data; + } + const params = { sort_by: 'date_created', - sort_order: 'DESC' - // requested_columns: 'id,title', // was not working, see data-sources-app/issues/581 - // request_statuses: 'Intake', // used for testing, should be 'Ready to start' - // limit: 3, // not supported, see data-sources-app/issues/579 + sort_order: 'DESC', + // requested_columns: 'id,title', // Was not working, see data-sources-app/issues/581 + // request_statuses: 'Intake', // Used for testing, should be 'Ready to start' + // limit: 3, // Not supported, see data-sources-app/issues/579 }; - try { - const response = await axios.get(REQUESTS_BASE, { - headers: HEADERS_BASIC, - params - }); + const response = await axios.get(REQUESTS_BASE, { + headers: HEADERS_BASIC, + params, + }); - return response.data.data.map((item) => ({ + // Limit results to 3 items on the front end and process the data + const recentRequests = response.data.data + .slice(0, 3) // Limit to 3 items + .map((item) => ({ id: item.id, title: item.title, status: item.request_status, locationDisplayName: item.locations?.[0]?.display_name || 'Unknown location', - route: `/data-request/${item.id}` + route: `/data-request/${item.id}`, })); - } catch (error) { - console.error( - 'Error fetching recent requests:', - error.response?.data || error.message - ); - throw error; - } -} + + // Cache the processed data + requestsStore.setDataRequestToCache('recent-requests', recentRequests); + + return recentRequests; +} \ No newline at end of file From abd04b061738e203f50c42276f4a762b75bcf34f Mon Sep 17 00:00:00 2001 From: Josh Chamberlain Date: Fri, 17 Jan 2025 00:08:09 -0500 Subject: [PATCH 30/47] clean up comments --- src/api/data-requests.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/data-requests.js b/src/api/data-requests.js index f9e54ab..7123bb9 100644 --- a/src/api/data-requests.js +++ b/src/api/data-requests.js @@ -62,7 +62,7 @@ export async function getDataRequest(id) { cached && isCachedResponseValid({ cacheTime: cached.timestamp, - // Cache for 5 minutes + // Cache for 3 minutes intervalBeforeInvalidation: 1000 * 60 * 3 }) ) { @@ -102,7 +102,8 @@ export async function getRecentRequests() { cached && isCachedResponseValid({ cacheTime: cached.timestamp, - intervalBeforeInvalidation: 1000 * 60 * 3, // 3 minutes + // Cache for 3 minutes + intervalBeforeInvalidation: 1000 * 60 * 3, }) ) { return cached.data; @@ -121,9 +122,8 @@ export async function getRecentRequests() { params, }); - // Limit results to 3 items on the front end and process the data const recentRequests = response.data.data - .slice(0, 3) // Limit to 3 items + .slice(0, 3) .map((item) => ({ id: item.id, title: item.title, @@ -133,7 +133,6 @@ export async function getRecentRequests() { route: `/data-request/${item.id}`, })); - // Cache the processed data requestsStore.setDataRequestToCache('recent-requests', recentRequests); return recentRequests; From 8185047cd5306e472be010a83ec45249385b4f53 Mon Sep 17 00:00:00 2001 From: Josh Chamberlain Date: Fri, 17 Jan 2025 00:56:43 -0500 Subject: [PATCH 31/47] add recent sources --- src/api/data-sources.js | 47 ++++++++++++++++++++++++++++++++++++++ src/pages/index.vue | 50 ++++++++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/api/data-sources.js b/src/api/data-sources.js index 19f1bee..a1e7790 100644 --- a/src/api/data-sources.js +++ b/src/api/data-sources.js @@ -54,3 +54,50 @@ export async function getDataSource(id) { return response; } + +export async function getRecentSources() { + const dataSourceStore = useDataSourceStore(); + const cached = dataSourceStore.getDataSourceFromCache('recent-sources'); + + if ( + cached && + isCachedResponseValid({ + cacheTime: cached.timestamp, + // Cache for 3 minutes + intervalBeforeInvalidation: 1000 * 60 * 3, + }) + ) { + return cached.data; + } + + const params = { + sort_by: 'created_at', + sort_order: 'DESC', + // requested_columns: 'id,name,created_at,source_url', // Was not working, see data-sources-app/issues/581 + approval_status: 'approved', + }; + + try { + const response = await axios.get(DATA_SOURCES_BASE, { + headers: HEADERS_BASIC, + params, + }); + + const recentSources = response.data.data + .slice(0, 3) + .map((item) => ({ + id: item.id, + name: item.name, + createdAt: item.created_at, + sourceUrl: item.source_url, + route: `/data-source/${item.id}`, + })); + + dataSourceStore.setDataSourceToCache('recent-sources', recentSources); + + return recentSources; + } catch (error) { + console.error('Error fetching recent sources:', error.response?.data || error.message); + throw error; + } +} \ No newline at end of file diff --git a/src/pages/index.vue b/src/pages/index.vue index 18b4543..0dae844 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -27,6 +27,19 @@ District of Columbia. They are published by both government agencies and independent organizations.

+
+

Recently added Data Sources

+
+ {{ source.name }}  + + +
+ Created: {{ source.formattedCreatedAt }}
+ + Source Link +
+
+

@@ -98,7 +111,7 @@
-

Research requests

+

Data projects

Some research projects could benefit from help with data analysis, web scraping, or records requests. PDAP is a place for collaborators to find each other. @@ -276,7 +289,7 @@