Skip to content

Commit fdf4a5e

Browse files
authored
fix: expand PB / FM APIs to allow GQL API-based data migrations (#4581)
1 parent 4931327 commit fdf4a5e

File tree

20 files changed

+670
-75
lines changed

20 files changed

+670
-75
lines changed

packages/api-file-manager-s3/src/plugins/graphqlFileStorageS3.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const plugin: GraphQLSchemaPlugin<FileManagerContext> = {
2525
}
2626
2727
input PreSignedPostPayloadInput {
28+
id: ID
2829
name: String!
2930
type: String!
3031
size: Long!

packages/api-page-builder-so-ddb-es/src/operations/pages/index.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export interface CreatePageStorageOperationsParams {
6969
elasticsearch: Client;
7070
plugins: PluginsContainer;
7171
}
72+
7273
export const createPageStorageOperations = (
7374
params: CreatePageStorageOperationsParams
7475
): PageStorageOperations => {
@@ -86,6 +87,11 @@ export const createPageStorageOperations = (
8687
SK: createLatestSortKey()
8788
};
8889

90+
const publishedKeys = {
91+
...versionKeys,
92+
SK: createPublishedSortKey()
93+
};
94+
8995
const entityBatch = createEntityWriteBatch({
9096
entity,
9197
put: [
@@ -103,17 +109,41 @@ export const createPageStorageOperations = (
103109
});
104110

105111
const esData = getESLatestPageData(plugins, page, input);
106-
try {
107-
await entityBatch.execute();
108112

109-
await put({
110-
entity: esEntity,
111-
item: {
113+
const elasticsearchEntityBatch = createEntityWriteBatch({
114+
entity: esEntity,
115+
put: [
116+
{
112117
index: configurations.es(page).index,
113118
data: esData,
114119
...latestKeys
115120
}
121+
]
122+
});
123+
124+
if (page.status === "published") {
125+
entityBatch.put({
126+
...page,
127+
...publishedKeys,
128+
TYPE: createPublishedType()
129+
});
130+
131+
entityBatch.put({
132+
...page,
133+
TYPE: createPublishedPathType(),
134+
PK: createPathPartitionKey(page),
135+
SK: createPathSortKey(page)
116136
});
137+
138+
elasticsearchEntityBatch.put({
139+
index: configurations.es(page).index,
140+
data: getESPublishedPageData(plugins, page),
141+
...publishedKeys
142+
});
143+
}
144+
try {
145+
await entityBatch.execute();
146+
await elasticsearchEntityBatch.execute();
117147
return page;
118148
} catch (ex) {
119149
throw new WebinyError(

packages/api-page-builder-so-ddb/src/operations/pages/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface CreatePageStorageOperationsParams {
8181
entity: Entity<any>;
8282
plugins: PluginsContainer;
8383
}
84+
8485
export const createPageStorageOperations = (
8586
params: CreatePageStorageOperationsParams
8687
): PageStorageOperations => {
@@ -98,6 +99,11 @@ export const createPageStorageOperations = (
9899
SK: createLatestSortKey(page)
99100
};
100101

102+
const publishedKeys = {
103+
PK: createPublishedPartitionKey(page),
104+
SK: createPublishedSortKey(page)
105+
};
106+
101107
const titleLC = page.title.toLowerCase();
102108
/**
103109
* We need to create
@@ -122,6 +128,16 @@ export const createPageStorageOperations = (
122128
]
123129
});
124130

131+
if (page.status === "published") {
132+
entityBatch.put({
133+
...page,
134+
...publishedKeys,
135+
GSI1_PK: createPathPartitionKey(page),
136+
GSI1_SK: page.path,
137+
TYPE: createPublishedType()
138+
});
139+
}
140+
125141
try {
126142
await entityBatch.execute();
127143
return page;
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import useGqlHandler from "./useGqlHandler";
2+
3+
jest.setTimeout(100000);
4+
5+
describe("CRUD Test", () => {
6+
const handler = useGqlHandler();
7+
8+
const { createCategory, createPageV2, getPage, getPublishedPage } = handler;
9+
10+
it("creating pages via the new createPagesV2 mutation", async () => {
11+
await createCategory({
12+
data: {
13+
slug: `slug`,
14+
name: `name`,
15+
url: `/some-url/`,
16+
layout: `layout`
17+
}
18+
});
19+
20+
const page = {
21+
id: "67e15c96026bd2000222d698#0001",
22+
pid: "67e15c96026bd2000222d698",
23+
category: "slug",
24+
version: 1,
25+
title: "Welcome to Webiny",
26+
path: "/welcome-to-webiny",
27+
content: {
28+
id: "Fv1PpPWu-",
29+
type: "document",
30+
data: {
31+
settings: {}
32+
},
33+
elements: []
34+
},
35+
status: "published",
36+
publishedOn: "2025-03-24T13:22:30.918Z",
37+
settings: {
38+
general: {
39+
snippet: null,
40+
tags: null,
41+
layout: "static",
42+
image: null
43+
},
44+
social: {
45+
meta: [],
46+
title: null,
47+
description: null,
48+
image: null
49+
},
50+
seo: {
51+
title: null,
52+
description: null,
53+
meta: []
54+
}
55+
},
56+
createdOn: "2025-03-24T13:22:30.363Z",
57+
createdBy: {
58+
id: "67e15c7d026bd2000222d67a",
59+
displayName: "ad min",
60+
type: "admin"
61+
}
62+
};
63+
64+
// The V2 of the createPage mutation should allow us to create pages with
65+
// predefined `createdOn`, `createdBy`, `id`, and also immediately have the
66+
// page published.
67+
await createPageV2({ data: page });
68+
69+
const [getPageResponse] = await getPage({ id: page.id });
70+
71+
expect(getPageResponse).toMatchObject({
72+
data: {
73+
pageBuilder: {
74+
getPage: {
75+
data: {
76+
id: "67e15c96026bd2000222d698#0001",
77+
pid: "67e15c96026bd2000222d698",
78+
editor: "page-builder",
79+
category: {
80+
slug: "slug"
81+
},
82+
version: 1,
83+
title: "Welcome to Webiny",
84+
path: "/welcome-to-webiny",
85+
url: "https://www.test.com/welcome-to-webiny",
86+
content: {
87+
id: "Fv1PpPWu-",
88+
type: "document",
89+
data: {
90+
settings: {}
91+
},
92+
elements: []
93+
},
94+
savedOn: "2025-03-24T13:22:30.363Z",
95+
status: "published",
96+
locked: true,
97+
publishedOn: "2025-03-24T13:22:30.918Z",
98+
revisions: [
99+
{
100+
id: "67e15c96026bd2000222d698#0001",
101+
status: "published",
102+
locked: true,
103+
version: 1
104+
}
105+
],
106+
settings: {
107+
general: {
108+
snippet: null,
109+
tags: null,
110+
layout: "static",
111+
image: null
112+
},
113+
social: {
114+
meta: [],
115+
title: null,
116+
description: null,
117+
image: null
118+
},
119+
seo: {
120+
title: null,
121+
description: null,
122+
meta: []
123+
}
124+
},
125+
createdFrom: null,
126+
createdOn: "2025-03-24T13:22:30.363Z",
127+
createdBy: {
128+
id: "67e15c7d026bd2000222d67a",
129+
displayName: "ad min",
130+
type: "admin"
131+
}
132+
},
133+
error: null
134+
}
135+
}
136+
}
137+
});
138+
139+
const [getPublishedPageResponse] = await getPublishedPage({ id: page.id });
140+
141+
expect(getPublishedPageResponse).toMatchObject({
142+
data: {
143+
pageBuilder: {
144+
getPublishedPage: {
145+
data: {
146+
id: "67e15c96026bd2000222d698#0001",
147+
pid: "67e15c96026bd2000222d698",
148+
editor: "page-builder",
149+
category: {
150+
slug: "slug"
151+
},
152+
version: 1,
153+
title: "Welcome to Webiny",
154+
path: "/welcome-to-webiny",
155+
url: "https://www.test.com/welcome-to-webiny",
156+
content: {
157+
id: "Fv1PpPWu-",
158+
type: "document",
159+
data: {
160+
settings: {}
161+
},
162+
elements: []
163+
},
164+
savedOn: "2025-03-24T13:22:30.363Z",
165+
status: "published",
166+
locked: true,
167+
publishedOn: "2025-03-24T13:22:30.918Z",
168+
revisions: [
169+
{
170+
id: "67e15c96026bd2000222d698#0001",
171+
status: "published",
172+
locked: true,
173+
version: 1
174+
}
175+
],
176+
settings: {
177+
general: {
178+
snippet: null,
179+
tags: null,
180+
layout: "static",
181+
image: null
182+
},
183+
social: {
184+
meta: [],
185+
title: null,
186+
description: null,
187+
image: null
188+
},
189+
seo: {
190+
title: null,
191+
description: null,
192+
meta: []
193+
}
194+
},
195+
createdFrom: null,
196+
createdOn: "2025-03-24T13:22:30.363Z",
197+
createdBy: {
198+
id: "67e15c7d026bd2000222d67a",
199+
displayName: "ad min",
200+
type: "admin"
201+
}
202+
},
203+
error: null
204+
}
205+
}
206+
}
207+
});
208+
});
209+
});

packages/api-page-builder/__tests__/graphql/graphql/pages.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,21 @@ export const createPageCreateGraphQl = (params: CreateDataFieldsParams = {}) =>
198198
`;
199199
};
200200

201+
export const createPageCreateV2GraphQl = (params: CreateDataFieldsParams = {}) => {
202+
return /* GraphQL */ `
203+
mutation CreatePageV2($data: PbCreatePageV2Input!) {
204+
pageBuilder {
205+
createPageV2(data: $data) {
206+
data ${createDataFields(params)}
207+
error ${ERROR_FIELD}
208+
}
209+
}
210+
}
211+
`;
212+
};
213+
201214
export const CREATE_PAGE = createPageCreateGraphQl();
215+
export const CREATE_PAGE_V2 = createPageCreateV2GraphQl();
202216

203217
export const createPageUpdateGraphQl = (params: CreateDataFieldsParams = {}) => {
204218
return /* GraphQL */ `

packages/api-page-builder/__tests__/graphql/useGqlHandler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from "./graphql/pageElements";
2727
import {
2828
CREATE_PAGE,
29+
CREATE_PAGE_V2,
2930
DELETE_PAGE,
3031
DUPLICATE_PAGE,
3132
GET_PAGE,
@@ -241,6 +242,9 @@ export default ({ permissions, identity, plugins }: Params = {}) => {
241242
async createPage(variables: Record<string, any>) {
242243
return invoke({ body: { query: CREATE_PAGE, variables } });
243244
},
245+
async createPageV2(variables: Record<string, any>) {
246+
return invoke({ body: { query: CREATE_PAGE_V2, variables } });
247+
},
244248
async duplicatePage(variables: Record<string, any>) {
245249
return invoke({ body: { query: DUPLICATE_PAGE, variables } });
246250
},

packages/api-page-builder/src/graphql/crud/categories.crud.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
import { createZodError, removeUndefinedValues } from "@webiny/utils";
2424
import { CategoriesPermissions } from "~/graphql/crud/permissions/CategoriesPermissions";
2525
import { PagesPermissions } from "~/graphql/crud/permissions/PagesPermissions";
26+
import { getDate } from "~/graphql/crud/utils/getDate";
27+
import { getIdentity } from "~/graphql/crud/utils/getIdentity";
2628

2729
export interface CreateCategoriesCrudParams {
2830
context: PbContext;
@@ -172,17 +174,14 @@ export const createCategoriesCrud = (params: CreateCategoriesCrudParams): Catego
172174
}
173175

174176
const identity = context.security.getIdentity();
177+
const currentDateTime = new Date();
175178

176179
const data = validationResult.data;
177180

178181
const category: Category = {
179182
...data,
180-
createdOn: new Date().toISOString(),
181-
createdBy: {
182-
id: identity.id,
183-
type: identity.type,
184-
displayName: identity.displayName
185-
},
183+
createdOn: getDate(input.createdOn, currentDateTime),
184+
createdBy: getIdentity(input.createdBy, identity),
186185
tenant: getTenantId(),
187186
locale: getLocaleCode()
188187
};

0 commit comments

Comments
 (0)