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

App migrate pinia stores #22122

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d2b6bfe
updated collection store
br41nslug Apr 4, 2024
587af31
fixed collection import naming
br41nslug Apr 4, 2024
9041d19
updated extensions store
br41nslug Apr 4, 2024
8fac347
updated fields store
br41nslug Apr 4, 2024
1eb19f6
including graphql in the sdk
br41nslug Apr 4, 2024
5f47699
updated notification store
br41nslug Apr 4, 2024
b1f69e8
updated permission store
br41nslug Apr 4, 2024
1eae22a
updated presets store
br41nslug Apr 4, 2024
a2ca284
updated users store
br41nslug Apr 5, 2024
ab6f1fe
updated translations store
br41nslug Apr 5, 2024
06c435d
updated settings store
br41nslug Apr 5, 2024
c1ebbe1
updated insights store
br41nslug Apr 5, 2024
98cac2b
updated relations store
br41nslug Apr 5, 2024
af894f5
fixed sdk initialization
br41nslug Apr 5, 2024
1b20718
updated permissions test
br41nslug Apr 5, 2024
d1683a3
updated fields test
br41nslug Apr 5, 2024
061be1f
updated settings test
br41nslug Apr 5, 2024
28cf502
implement "extractData" to be optional for some endpoints
br41nslug Apr 8, 2024
bb68d5f
fixed the readProviders command
br41nslug Apr 8, 2024
a3382fc
prettier
br41nslug Apr 8, 2024
15ff75e
fixed user store test
br41nslug Apr 8, 2024
3079b7c
Merge branch 'main' into app-migrate-pinia-stores
br41nslug Apr 22, 2024
39d79e9
fixed error
br41nslug Apr 22, 2024
f25c99c
resolved fields input types
br41nslug Apr 22, 2024
0468084
resolved insights input types
br41nslug Apr 22, 2024
8b6b88c
resolved more type casting
br41nslug Apr 22, 2024
b532c4c
updated sdk ofetch
br41nslug Apr 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 6 additions & 5 deletions app/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { AuthenticationClient, DirectusClient, RestClient } from '@directus/sdk';
import { createDirectus, rest, authentication } from '@directus/sdk';
import type { AuthenticationClient, DirectusClient, GraphqlClient, RestClient } from '@directus/sdk';
import { createDirectus, rest, authentication, graphql } from '@directus/sdk';
import { getPublicURL } from '@/utils/get-root-path';
import { ofetch, type FetchContext } from 'ofetch';
import { useRequestsStore } from './stores/requests';
import { requestQueue } from './api';

export type SdkClient = DirectusClient<any> & AuthenticationClient<any> & RestClient<any>;
export type SdkClient = DirectusClient<any> & AuthenticationClient<any> & RestClient<any> & GraphqlClient<any>;

type OptionsWithId = FetchContext['options'] & { id: string };

Expand Down Expand Up @@ -48,9 +48,10 @@ const baseClient = ofetch.create({
},
});

export const sdk: SdkClient = createDirectus(getPublicURL(), { globals: { fetch: baseClient.native } })
export const sdk: SdkClient = createDirectus(getPublicURL(), { globals: { fetch: baseClient } })
.with(authentication('session', { credentials: 'include', msRefreshBeforeExpires: 10_000 }))
.with(rest({ credentials: 'include' }));
.with(rest({ credentials: 'include' }))
.with(graphql({ credentials: 'include' }));

export default sdk;

Expand Down
8 changes: 4 additions & 4 deletions app/src/stores/collections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test('parseField action should translate field name when translations are added
});

collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
expect(collectionsStore.collections[0]!.name).toEqual('Collection A en-US');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);

const mockCollectionWithMissingTranslations = merge({}, mockCollection, {
Expand All @@ -52,7 +52,7 @@ test('parseField action should translate field name when translations are added
});

collectionsStore.collections = [mockCollectionWithMissingTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('A');
expect(collectionsStore.collections[0]!.name).toEqual('A');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
});

Expand All @@ -71,7 +71,7 @@ test('parseField action should translate field name when all translations are re
});

collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
expect(collectionsStore.collections[0]!.name).toEqual('Collection A en-US');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);

const mockCollectionWithoutTranslations = merge({}, mockCollection, {
Expand All @@ -81,6 +81,6 @@ test('parseField action should translate field name when all translations are re
});

collectionsStore.collections = [mockCollectionWithoutTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('A');
expect(collectionsStore.collections[0]!.name).toEqual('A');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
});
35 changes: 19 additions & 16 deletions app/src/stores/collections.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import api from '@/api';
import { COLLECTIONS_DENY_LIST } from '@/constants';
import { i18n } from '@/lang';
import { Collection } from '@/types/collections';
import { getLiteralInterpolatedTranslation } from '@/utils/get-literal-interpolated-translation';
import { notify } from '@/utils/notify';
import { unexpectedError } from '@/utils/unexpected-error';
import formatTitle from '@directus/format-title';
import { Collection as CollectionRaw, DeepPartial, Field } from '@directus/types';
import { Collection as CollectionRaw } from '@directus/types';
import { getCollectionType } from '@directus/utils';
import { isEqual, isNil, omit, orderBy } from 'lodash';
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { useRelationsStore } from './relations';
import { isSystemCollection } from '@directus/system-data';
import {
DirectusCollection,
NestedPartial,
createCollection as createCollectionCmd,
deleteCollection as deleteCollectionCmd,
readCollections as readCollectionsCmd,
updateCollection as updateCollectionCmd,
} from '@directus/sdk';
import sdk from '@/sdk';

export const useCollectionsStore = defineStore('collectionsStore', () => {
const collections = ref<Collection[]>([]);
Expand Down Expand Up @@ -53,9 +61,7 @@ export const useCollectionsStore = defineStore('collectionsStore', () => {
};

async function hydrate() {
const response = await api.get<any>(`/collections`);

const rawCollections: CollectionRaw[] = response.data.data;
const rawCollections = await sdk.request<CollectionRaw[]>(readCollectionsCmd());

collections.value = rawCollections.map(prepareCollectionForApp);
}
Expand Down Expand Up @@ -132,7 +138,7 @@ export const useCollectionsStore = defineStore('collectionsStore', () => {
});
}

async function upsertCollection(collection: string, values: DeepPartial<Collection & { fields: Field[] }>) {
async function upsertCollection(collection: string, values: NestedPartial<DirectusCollection<any>>) {
const existing = getCollection(collection);

// Strip out any fields the app might've auto-generated at some point
Expand All @@ -142,31 +148,28 @@ export const useCollectionsStore = defineStore('collectionsStore', () => {
if (existing) {
if (isEqual(existing, values)) return;

const updatedCollectionResponse = await api.patch<{ data: CollectionRaw }>(
`/collections/${collection}`,
rawValues,
);
const updatedCollectionResponse = await sdk.request<CollectionRaw>(updateCollectionCmd(collection, rawValues));

collections.value = collections.value.map((existingCollection: Collection) => {
if (existingCollection.collection === collection) {
return prepareCollectionForApp(updatedCollectionResponse.data.data);
return prepareCollectionForApp(updatedCollectionResponse);
}

return existingCollection;
});
} else {
const createdCollectionResponse = await api.post<{ data: CollectionRaw }>('/collections', rawValues);
const createdCollectionResponse = await sdk.request<CollectionRaw>(createCollectionCmd(rawValues));

collections.value = [...collections.value, prepareCollectionForApp(createdCollectionResponse.data.data)];
collections.value = [...collections.value, prepareCollectionForApp(createdCollectionResponse)];
}
} catch (error) {
unexpectedError(error);
}
}

async function updateCollection(collection: string, updates: DeepPartial<Collection>) {
async function updateCollection(collection: string, updates: NestedPartial<DirectusCollection<any>>) {
try {
await api.patch(`/collections/${collection}`, updates);
await sdk.request(updateCollectionCmd(collection, updates));
await hydrate();

notify({
Expand All @@ -181,7 +184,7 @@ export const useCollectionsStore = defineStore('collectionsStore', () => {
const relationsStore = useRelationsStore();

try {
await api.delete(`/collections/${collection}`);
await sdk.request(deleteCollectionCmd(collection));
await Promise.all([hydrate(), relationsStore.hydrate()]);

notify({
Expand Down
39 changes: 28 additions & 11 deletions app/src/stores/extensions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import api from '@/api';
import type { ApiOutput } from '@directus/extensions';
import { APP_OR_HYBRID_EXTENSION_TYPES } from '@directus/extensions';
import { isIn } from '@directus/utils';
Expand All @@ -7,6 +6,8 @@ import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useNotificationsStore } from './notifications';
import { readExtensions, updateExtension } from '@directus/sdk';
import sdk from '@/sdk';

const getEnabledBrowserExtensions = (extensions: ApiOutput[]) => {
const enabledIds: string[] = [];
Expand All @@ -33,6 +34,8 @@ const getEnabledBrowserExtensions = (extensions: ApiOutput[]) => {
return enabledIds;
};

// TODO create actual SDK commands for these

export const useExtensionsStore = defineStore('extensions', () => {
const notificationsStore = useNotificationsStore();
const { t } = useI18n();
Expand All @@ -48,8 +51,7 @@ export const useExtensionsStore = defineStore('extensions', () => {
const currentlyEnabledBrowserExtensions = getEnabledBrowserExtensions(extensions.value);

try {
const response = await api.get('/extensions');
extensions.value = response.data.data;
extensions.value = await sdk.request<ApiOutput[]>(readExtensions());

const newEnabledBrowserExtensions = getEnabledBrowserExtensions(extensions.value);

Expand Down Expand Up @@ -87,31 +89,46 @@ export const useExtensionsStore = defineStore('extensions', () => {

if (!extension) throw new Error(`Extension with ID ${id} does not exist`);

const current = extension.meta.enabled;
const endpoint = `/extensions/${id}`;

await api.patch(endpoint, { meta: { enabled: !current } });
await sdk.request(updateExtension(extension.bundle, extension.id, { meta: { enabled: !extension.meta.enabled } }));

await refresh();
};

const install = async (extensionId: string, versionId: string) => {
await api.post('/extensions/registry/install', { extension: extensionId, version: versionId });
await sdk.request(() => ({
path: '/extensions/registry/install',
method: 'POST',
body: JSON.stringify({ extension: extensionId, version: versionId }),
}));

await refresh();
};

const uninstall = async (extensionId: string) => {
await api.delete(`/extensions/registry/uninstall/${extensionId}`);
await sdk.request(() => ({
path: `/extensions/registry/uninstall/${extensionId}`,
method: 'DELETE',
}));

await refresh();
};

const reinstall = async (extensionId: string) => {
await api.post(`/extensions/registry/reinstall`, { extension: extensionId });
await sdk.request(() => ({
path: `/extensions/registry/reinstall`,
method: 'POST',
body: JSON.stringify({ extension: extensionId }),
}));

await refresh();
};

const remove = async (extensionId: string) => {
await api.delete(`/extensions/${extensionId}`);
await sdk.request(() => ({
path: `/extensions/${extensionId}`,
method: 'DELETE',
}));

await refresh();
};

Expand Down
20 changes: 9 additions & 11 deletions app/src/stores/fields.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,14 @@ const mockField = {
},
} as Field;

vi.mock('@/api', () => {
vi.mock('@/sdk', () => {
return {
default: {
get: (path: string) => {
request: (cfg: () => Record<string, any>) => {
const { path } = cfg();

if (path === '/fields') {
return Promise.resolve({
data: {
data: [mockField],
},
});
return Promise.resolve([mockField]);
}

return Promise.reject(new Error(`Path "${path}" is not mocked in this test`));
Expand Down Expand Up @@ -92,7 +90,7 @@ describe('parseField action', () => {
});

fieldsStore.fields = [mockFieldWithTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('Name en-US');
expect(fieldsStore.fields[0]!.name).toEqual('Name en-US');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(true);

const mockFieldWithoutTranslations = merge({}, mockField, {
Expand All @@ -107,7 +105,7 @@ describe('parseField action', () => {
});

fieldsStore.fields = [mockFieldWithoutTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('Name');
expect(fieldsStore.fields[0]!.name).toEqual('Name');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(false);
});

Expand All @@ -126,7 +124,7 @@ describe('parseField action', () => {
});

fieldsStore.fields = [mockFieldWithTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('name en-US');
expect(fieldsStore.fields[0]!.name).toEqual('name en-US');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(true);

const mockFieldWithoutTranslations = merge({}, mockField, {
Expand All @@ -136,7 +134,7 @@ describe('parseField action', () => {
});

fieldsStore.fields = [mockFieldWithoutTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('Name');
expect(fieldsStore.fields[0]!.name).toEqual('Name');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(false);
});
});