-
Notifications
You must be signed in to change notification settings - Fork 63
Convert provider
store from Vuex to Pinia
#1158
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! I was worried this module would make too many requests for providers that actually rarely change, thanks for adding the check if it needs an update or not ⭐️
src/middleware/middleware.ts
Outdated
export default async function ({ | ||
query, | ||
route, | ||
$pinia, | ||
}: { | ||
query: Record<string, string> | ||
route: Route | ||
$pinia: Pinia | ||
}): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this conflicts with #1228, should we merge that first then update this afterwards to use the standard implementation for the types?
src/stores/provider.ts
Outdated
/** | ||
* Timestamp is used to limit the update frequency to one every 60 minutes per request. | ||
*/ | ||
const lastUpdated: Ref<Date | null> = ssrRef(null) | ||
|
||
const updateFrequency = process.env.providerUpdateFrequency | ||
? parseInt(process.env.providerUpdateFrequency, 10) | ||
: 60 * 60 * 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this necessary in the end? I thought it was agreed to just make the request for the providers list on every request to SSR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we've decided to make the request for the providers list on every request to SSR. However, without this check, it was making more than one provider request per SSR. It was updating on the server and on the client, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was making more than one provider request per SSR
Is this because multiple places request it? Is there somehow we could centralize it per request 🤔 Like adding it to the request context or something I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Some minor typescript stuff but tested locally and it works great.
src/stores/provider.ts
Outdated
} | ||
|
||
const timeSinceLastUpdate = | ||
new Date().getTime() - new Date(lastUpdated.value).getTime() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL getTime
and valueOf
on Date
are equivalent!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting!
…he search store easier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Tested well locally 😁
@@ -28,6 +28,8 @@ export interface Media { | |||
|
|||
provider: string | |||
source?: string | |||
providerName?: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does providerName
get populated? And should it be snake case if it's coming from the API?
I see the API mapping provider_name
field to display_name
in the provider serializer: https://github.com/WordPress/openverse-api/blob/a7955c86d43bff504e8d41454f68717d79dd3a44/api/catalog/api/serializers/provider_serializers.py#L11-L14
But not on the media serializer 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, I wonder the same ❓
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
providerName
is generated when fetching the media item (only when fetching the single item, because I guess search views do not require it?):
openverse-frontend/src/stores/media/index.ts
Lines 343 to 351 in 73d0dac
mediaDetail.providerName = providerStore.getProviderName( | |
mediaDetail.provider, | |
mediaType | |
) | |
if (mediaDetail.source) { | |
mediaDetail.sourceName = providerStore.getProviderName( | |
mediaDetail.source, | |
mediaType | |
) |
I guess we could also split the interface into
API Media
and Local Media
if that makes it clearer in the future.
@@ -0,0 +1,60 @@ | |||
import { isClient } from '~/composables/window' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove this module now right?
afterEach(() => { | ||
afterEach(() => { | ||
providerServices.audio.getProviderStats.mockClear() | ||
providerServices.image.getProviderStats.mockClear() | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afterEach(() => { | |
afterEach(() => { | |
providerServices.audio.getProviderStats.mockClear() | |
providerServices.image.getProviderStats.mockClear() | |
}) | |
}) | |
afterEach(() => { | |
providerServices.audio.getProviderStats.mockClear() | |
providerServices.image.getProviderStats.mockClear() | |
}) |
src/stores/provider.ts
Outdated
} | ||
} | ||
|
||
export const initialProviderState: ProviderState = Object.freeze({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use deepFreeze
here since it's a nested structure?
state: (): ProviderState => ({ | ||
providers: { | ||
[AUDIO]: [], | ||
[IMAGE]: [], | ||
}, | ||
fetchState: { | ||
[AUDIO]: { ...initialFetchState }, | ||
[IMAGE]: { ...initialFetchState }, | ||
}, | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can use the initialProviderState
object, right? Just need to deepclone
it if it's frozen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the initialProviderState
object altogether, because it was overcomplicating things (deepFreeze/deepclone), and not really serving much purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it with changes of main
and everything seems to work well :) Nice work!
@@ -28,6 +28,8 @@ export interface Media { | |||
|
|||
provider: string | |||
source?: string | |||
providerName?: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, I wonder the same ❓
Fixes
Fixes #1027 by @obulat
Fixes #967 by @sarayourfriend
This PR was edited to convert the setup store to an options store to fix the server state hydration issues. Please, re-review :)
Description
This PR converts the
provider
store to Pinia. Provider stats include provider short name and display name, URL, as well as the media count per each provider. These data are used in the provider filters for image/audio, and on the sources page.The state is simplified similar to the
media
store state to enable simple access to properties for various media types without string manipulation (e.g.,fetchState[mediaType].isFetching
instead ofisFetching${titleCase(mediaType)}Providers
).Previously, we fetched provider data once, during the server initialization, using
nuxtServerInit
hook. Pinia does not have a direct analog. Here, I used amiddleware
to callfetchMediaProviders
before every route (found in a Pinia discussion).To minimize the number of unnecessary requests, I set a
lastUpdated
timestamp in the store as assrRef
. When callingfetchMediaProviders
, we only actually fetch data from the API if the provider array is empty, or more than 1 hour has passed sincelastUpdated
.This way
provider
store loads all the provider stats for each request, and updates them if necessary once an hour.Testing Instructions
Run the app, check the provider filters in the sidebar, they should work for images and audio.
Sources pages should display the image providers (note that audio providers were not yet added to this page).
Try disabling the network, you should see a warning in the console that providers were not loaded.
Checklist
Update index.md
).main
) or a parent feature branch.Developer Certificate of Origin
Developer Certificate of Origin