Skip to content

Commit

Permalink
fix: create settings entry when new locale is created (#4446)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrians5j authored Dec 17, 2024
1 parent a9313d6 commit 017b55b
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 90 deletions.
15 changes: 15 additions & 0 deletions packages/api-form-builder/__tests__/graphql/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const CREATE_LOCALE = /* GraphQL */ `
mutation CreateI18NLocale($data: I18NLocaleInput!) {
i18n {
createI18NLocale(data: $data) {
data {
code
}
error {
message
code
}
}
}
}
`;
40 changes: 39 additions & 1 deletion packages/api-form-builder/__tests__/settings.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import useGqlHandler from "./useGqlHandler";
import { GET_SETTINGS } from "~tests/graphql/formBuilderSettings";

describe("Settings Test", () => {
const { getSettings, updateSettings, install, isInstalled } = useGqlHandler();
const { getSettings, updateSettings, install, createI18NLocale, isInstalled } = useGqlHandler();

test(`Should not be able to get & update settings before "install"`, async () => {
// Should not have any settings without install
Expand Down Expand Up @@ -154,4 +155,41 @@ describe("Settings Test", () => {
}
});
});

test(`Should be able to get & update settings after in a new locale`, async () => {
// Let's install the `Form builder`
await install({ domain: "http://localhost:3001" });

await createI18NLocale({ data: { code: "de-DE" } });

const { invoke } = useGqlHandler();

// Had to do it via `invoke` directly because this way it's possible to
// set the locale header. Wasn't easily possible via the `getSettings` helper.
const [newLocaleFbSettings] = await invoke({
body: { query: GET_SETTINGS },
headers: {
"x-i18n-locale": "default:de-DE;content:de-DE;"
}
});

// Settings should exist in the newly created locale.
expect(newLocaleFbSettings).toEqual({
data: {
formBuilder: {
getSettings: {
data: {
domain: null,
reCaptcha: {
enabled: null,
secretKey: null,
siteKey: null
}
},
error: null
}
}
}
});
});
});
22 changes: 11 additions & 11 deletions packages/api-form-builder/__tests__/useGqlHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import i18nContext from "@webiny/api-i18n/graphql/context";
import { mockLocalesPlugins } from "@webiny/api-i18n/graphql/testing";
import { SecurityIdentity, SecurityPermission } from "@webiny/api-security/types";
import { createFormBuilder } from "~/index";
import { createI18NGraphQL } from "@webiny/api-i18n/graphql";

// Graphql
import { INSTALL as INSTALL_FILE_MANAGER } from "./graphql/fileManagerSettings";
import { CREATE_LOCALE } from "./graphql/i18n";

import {
GET_SETTINGS,
INSTALL,
Expand Down Expand Up @@ -41,11 +45,7 @@ import { PluginCollection } from "@webiny/plugins/types";
import { getStorageOps } from "@webiny/project-utils/testing/environment";
import { FileManagerStorageOperations } from "@webiny/api-file-manager/types";
import { HeadlessCmsStorageOperations } from "@webiny/api-headless-cms/types";
import {
CmsParametersPlugin,
createHeadlessCmsContext,
createHeadlessCmsGraphQL
} from "@webiny/api-headless-cms";
import { createHeadlessCmsContext, createHeadlessCmsGraphQL } from "@webiny/api-headless-cms";
import { FormBuilderStorageOperations } from "~/types";
import { APIGatewayEvent, LambdaContext } from "@webiny/handler-aws/types";
import { createPageBuilderContext } from "@webiny/api-page-builder";
Expand Down Expand Up @@ -83,14 +83,9 @@ export default (params: UseGqlHandlerParams = {}) => {
graphqlHandlerPlugins(),
...createTenancyAndSecurity({ permissions, identity }),
i18nContext(),
createI18NGraphQL(),
i18nStorage.storageOperations,
mockLocalesPlugins(),
new CmsParametersPlugin(async () => {
return {
locale: "en-US",
type: "manage"
};
}),
createHeadlessCmsContext({ storageOperations: cmsStorage.storageOperations }),
createHeadlessCmsGraphQL(),
createPageBuilderContext({
Expand Down Expand Up @@ -228,6 +223,11 @@ export default (params: UseGqlHandlerParams = {}) => {
},
async exportFormSubmissions(variables: Record<string, any>) {
return invoke({ body: { query: EXPORT_FORM_SUBMISSIONS, variables } });
},

// Locales.
async createI18NLocale(variables: Record<string, any>) {
return invoke({ body: { query: CREATE_LOCALE, variables } });
}
};
};
167 changes: 90 additions & 77 deletions packages/api-form-builder/src/plugins/crud/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,94 +15,107 @@ export interface CreateFormBuilderCrudParams {
export default (params: CreateFormBuilderCrudParams) => {
const { storageOperations } = params;

return new ContextPlugin<FormBuilderContext>(async context => {
const getLocale = () => {
const locale = context.i18n.getContentLocale();
if (!locale) {
throw new WebinyError(
"Missing locale on context.i18n locale in API Form Builder.",
"LOCALE_ERROR"
);
return [
new ContextPlugin<FormBuilderContext>(async context => {
const getLocale = () => {
const locale = context.i18n.getContentLocale();
if (!locale) {
throw new WebinyError(
"Missing locale on context.i18n locale in API Form Builder.",
"LOCALE_ERROR"
);
}
return locale;
};

const getIdentity = () => {
return context.security.getIdentity();
};

const getTenant = () => {
return context.tenancy.getCurrentTenant();
};

if (storageOperations.beforeInit) {
try {
await storageOperations.beforeInit(context);
} catch (ex) {
throw new WebinyError(
ex.message ||
"Could not run before init in Form Builder storage operations.",
ex.code || "STORAGE_OPERATIONS_BEFORE_INIT_ERROR",
{
...ex
}
);
}
}
return locale;
};

const getIdentity = () => {
return context.security.getIdentity();
};
const basePermissionsArgs = {
getIdentity,
fullAccessPermissionName: "fb.*"
};

const formsPermissions = new FormsPermissions({
...basePermissionsArgs,
getPermissions: () => context.security.getPermissions("fb.form")
});

const getTenant = () => {
return context.tenancy.getCurrentTenant();
};
const settingsPermissions = new SettingsPermissions({
...basePermissionsArgs,
getPermissions: () => context.security.getPermissions("fb.settings")
});

if (storageOperations.beforeInit) {
context.formBuilder = {
storageOperations,
...createSystemCrud({
getIdentity,
getTenant,
getLocale,
context
}),
...createSettingsCrud({
getTenant,
getLocale,
settingsPermissions,
context
}),
...createFormsCrud({
getTenant,
getLocale,
formsPermissions,
context
}),
...createSubmissionsCrud({
context,
formsPermissions
})
};

if (!storageOperations.init) {
return;
}
try {
await storageOperations.beforeInit(context);
await storageOperations.init(context);
} catch (ex) {
throw new WebinyError(
ex.message || "Could not run before init in Form Builder storage operations.",
ex.code || "STORAGE_OPERATIONS_BEFORE_INIT_ERROR",
ex.message || "Could not run init in Form Builder storage operations.",
ex.code || "STORAGE_OPERATIONS_INIT_ERROR",
{
...ex
}
);
}
}

const basePermissionsArgs = {
getIdentity,
fullAccessPermissionName: "fb.*"
};

const formsPermissions = new FormsPermissions({
...basePermissionsArgs,
getPermissions: () => context.security.getPermissions("fb.form")
});
}),

const settingsPermissions = new SettingsPermissions({
...basePermissionsArgs,
getPermissions: () => context.security.getPermissions("fb.settings")
});

context.formBuilder = {
storageOperations,
...createSystemCrud({
getIdentity,
getTenant,
getLocale,
context
}),
...createSettingsCrud({
getTenant,
getLocale,
settingsPermissions,
context
}),
...createFormsCrud({
getTenant,
getLocale,
formsPermissions,
context
}),
...createSubmissionsCrud({
context,
formsPermissions
})
};

if (!storageOperations.init) {
return;
}
try {
await storageOperations.init(context);
} catch (ex) {
throw new WebinyError(
ex.message || "Could not run init in Form Builder storage operations.",
ex.code || "STORAGE_OPERATIONS_INIT_ERROR",
{
...ex
}
);
}
});
// Once a new locale is created, we need to create a new settings entry for it.
new ContextPlugin<FormBuilderContext>(async context => {
context.i18n.locales.onLocaleAfterCreate.subscribe(async params => {
const { locale } = params;
await context.i18n.withLocale(locale, async () => {
return context.formBuilder.createSettings({});
});
});
})
];
};
22 changes: 21 additions & 1 deletion packages/api-i18n/src/graphql/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,25 @@ const createBaseContextPlugin = () => {
return results;
};

const withLocale: I18NContextObject["withLocale"] = async (locale, cb) => {
const initialLocale = getDefaultLocale();
if (!initialLocale) {
return;
}

setContentLocale(locale);
setCurrentLocale("default", locale);

try {
// We have to await the callback, because, in case it's an async function,
// the `finally` block would get executed before the callback finishes.
return await cb();
} finally {
setContentLocale(initialLocale);
setCurrentLocale("default", initialLocale);
}
};

context.i18n = {
...context.i18n,
getDefaultLocale,
Expand All @@ -252,7 +271,8 @@ const createBaseContextPlugin = () => {
reloadLocales,
hasI18NContentPermission: () => hasI18NContentPermission(context),
checkI18NContentPermission,
withEachLocale
withEachLocale,
withLocale
};
});
};
Expand Down
4 changes: 4 additions & 0 deletions packages/api-i18n/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export interface I18NContextObject {
locales: I18NLocale[],
cb: (locale: I18NLocale) => Promise<TReturn>
) => Promise<TReturn[] | undefined>;
withLocale: <TReturn>(
locale: I18NLocale,
cb: () => Promise<TReturn>
) => Promise<TReturn | undefined>;
}

export interface SystemInstallParams {
Expand Down

0 comments on commit 017b55b

Please sign in to comment.