Skip to content

Commit

Permalink
feat: introduce page builder data sources and data bindings (#4469)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel910 authored Jan 7, 2025
1 parent 23ba3e0 commit 592175e
Show file tree
Hide file tree
Showing 293 changed files with 6,658 additions and 1,525 deletions.
16 changes: 11 additions & 5 deletions packages/api-audit-logs/src/utils/getAuditConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,17 @@ export const getAuditConfig = (audit: AuditAction) => {

// Check if there is delay on audit log creation for this action.
if (delay) {
return await createOrMergeAuditLog({
app,
payload: auditLogPayload,
delay
});
try {
return await createOrMergeAuditLog({
app,
payload: auditLogPayload,
delay
});
} catch {
// Don't care at this point!
} finally {
return JSON.stringify({});
}
}
return await createAuditLog({
app,
Expand Down
12 changes: 9 additions & 3 deletions packages/api-headless-cms/src/graphql/generateSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ export const generateSchema = async (params: GenerateSchemaParams): Promise<Grap

context.plugins.register(generatedSchemaPlugins);

const schemaPlugins = context.plugins.byType<ICmsGraphQLSchemaPlugin>(
CmsGraphQLSchemaPlugin.type
);
const schemaPlugins = context.plugins
.byType<ICmsGraphQLSchemaPlugin>(CmsGraphQLSchemaPlugin.type)
.filter(pl => {
if (typeof pl.isApplicable === "function") {
return pl.isApplicable(context);
}
return true;
});

return createExecutableSchema({
plugins: schemaPlugins
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import { extractFilesFromData } from "~/export/utils";
export interface ExportedTemplateData {
template: Pick<
PageTemplate,
"title" | "slug" | "tags" | "description" | "content" | "layout" | "pageCategory"
| "title"
| "slug"
| "tags"
| "description"
| "content"
| "layout"
| "dataBindings"
| "dataSources"
>;
files: File[];
}
Expand Down Expand Up @@ -38,7 +45,8 @@ export class PageTemplateExporter {
description: template.description,
content: template.content,
layout: template.layout,
pageCategory: template.pageCategory
dataBindings: template.dataBindings,
dataSources: template.dataSources
},
files: imageFilesData
};
Expand Down
22 changes: 18 additions & 4 deletions packages/api-page-builder-import-export/src/export/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { CompleteMultipartUploadOutput } from "@webiny/aws-sdk/client-s3";
import { BlockCategory, Page, PageBlock, PageTemplate } from "@webiny/api-page-builder/types";
import {
BlockCategory,
Page,
PageBlock,
PageTemplate,
PageTemplateInput
} from "@webiny/api-page-builder/types";
import { FileManagerContext, File } from "@webiny/api-file-manager/types";
import get from "lodash/get";
import Zipper from "./zipper";
Expand Down Expand Up @@ -114,7 +120,14 @@ export async function exportBlock(
export interface ExportedTemplateData {
template: Pick<
PageTemplate,
"title" | "slug" | "tags" | "description" | "content" | "layout" | "pageCategory"
| "title"
| "slug"
| "tags"
| "description"
| "content"
| "layout"
| "dataSources"
| "dataBindings"
>;
files: File[];
}
Expand All @@ -135,15 +148,16 @@ export async function exportTemplate(
}

// Extract the template data in a json file and upload it to S3
const templateData = {
const templateData: { template: PageTemplateInput; files: File[] } = {
template: {
title: template.title,
slug: template.slug,
tags: template.tags,
description: template.description,
content: template.content,
layout: template.layout,
pageCategory: template.pageCategory
dataSources: template.dataSources,
dataBindings: template.dataBindings
},
files: imageFilesData
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ export const templatesHandler = async (
slug: template.slug,
tags: template.tags,
layout: template.layout,
pageCategory: template.pageCategory,
description: template.description,
content: template.content
content: template.content,
dataBindings: template.dataBindings,
dataSources: template.dataSources
});

// Update task record in DB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ describe("page custom field", () => {
{
name: "revisions"
},
{
name: "dataSources"
},
{
name: "dataBindings"
},
{
name: "customViews"
},
Expand Down Expand Up @@ -201,6 +207,12 @@ describe("page custom field", () => {
{
name: "content"
},
{
name: "dataSources"
},
{
name: "dataBindings"
},
{
name: "customViews"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export const createPageBlockEntity = (params: Params): Entity<any> => {
locale: {
type: "string"
},
dataSources: {
type: "list",
default: []
},
dataBindings: {
type: "list",
default: []
},
...(attributes || {})
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ export const createPageEntity = (params: Params): Entity<any> => {
webinyVersion: {
type: "string"
},
dataSources: {
type: "list",
default: []
},
dataBindings: {
type: "list",
default: []
},
...(attributes || {})
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export const createPageBlockEntity = (params: Params): Entity<any> => {
locale: {
type: "string"
},
dataSources: {
type: "list",
default: []
},
dataBindings: {
type: "list",
default: []
},
...(attributes || {})
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ export const createPageEntity = (params: Params): Entity<any> => {
webinyVersion: {
type: "string"
},
dataSources: {
type: "list",
default: []
},
dataBindings: {
type: "list",
default: []
},
...(attributes || {})
}
});
Expand Down
2 changes: 2 additions & 0 deletions packages/api-page-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@webiny/api-tenancy": "0.0.0",
"@webiny/aws-sdk": "0.0.0",
"@webiny/error": "0.0.0",
"@webiny/feature-flags": "0.0.0",
"@webiny/handler": "0.0.0",
"@webiny/handler-aws": "0.0.0",
"@webiny/handler-db": "0.0.0",
Expand Down Expand Up @@ -51,6 +52,7 @@
"@webiny/project-utils": "0.0.0",
"bytes": "^3.1.2",
"jest": "^29.7.0",
"prettier": "^2.8.8",
"rimraf": "^6.0.1",
"ttypescript": "^1.5.15",
"typescript": "4.9.5"
Expand Down
28 changes: 28 additions & 0 deletions packages/api-page-builder/src/dataSources/DataLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createZodError } from "@webiny/utils";
import type { DataLoaderResult, IDataLoader, IDataSource } from "~/dataSources/types";
import type { DataLoaderRequest } from "~/dataSources/DataLoaderRequest";

export class DataLoader implements IDataLoader {
private readonly dataSources: IDataSource[];

constructor(dataSources: IDataSource[]) {
this.dataSources = dataSources;
}

async load(request: DataLoaderRequest): Promise<DataLoaderResult> {
const type = request.getType();
const dataSource = this.dataSources.find(ds => ds.getType() === type);

if (!dataSource) {
throw new Error(`Can't find dataSource ${type}`);
}

const configSchema = dataSource.getConfigSchema();
const result = await configSchema.safeParseAsync(request.getConfig());
if (!result.success) {
throw createZodError(result.error);
}

return dataSource.load(request.withConfig(result.data));
}
}
34 changes: 34 additions & 0 deletions packages/api-page-builder/src/dataSources/DataLoaderRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { GenericRecord } from "@webiny/api/types";
import type { RequestDto } from "~/dataSources/types";

export class DataLoaderRequest<TConfig extends GenericRecord = GenericRecord> {
private readonly type: string;
private readonly config: TConfig;
private readonly paths: string[];

protected constructor(type: string, config: TConfig, paths: string[]) {
this.type = type;
this.config = config;
this.paths = paths;
}

static create(params: RequestDto) {
return new DataLoaderRequest(params.type, params.config, params.paths);
}

getType() {
return this.type;
}

getConfig() {
return this.config;
}

getPaths() {
return this.paths || [];
}

withConfig(config: TConfig) {
return new DataLoaderRequest(this.type, config, this.paths);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import zod from "zod";
import type { CmsContext } from "@webiny/api-headless-cms/types";
import type { DataLoaderRequest, DataLoaderResult, IDataSource } from "~/dataSources";
import { ModelListQuery } from "~/dataSources/cmsDataSources/ModelListQuery";

export interface CmsEntriesDataSourceConfig {
modelId: string;
limit: number;
}

export class CmsEntriesDataSource implements IDataSource<CmsEntriesDataSourceConfig> {
private cms: CmsContext["cms"];

constructor(cms: CmsContext["cms"]) {
this.cms = cms;
}

getType() {
return "cms.entries";
}

getConfigSchema(): zod.Schema {
return zod.object({
modelId: zod.string(),
limit: zod.number()
});
}

async load(request: DataLoaderRequest<CmsEntriesDataSourceConfig>): Promise<DataLoaderResult> {
const requestedPaths = request.getPaths();
const queryPaths = !requestedPaths || requestedPaths.length === 0 ? ["id"] : requestedPaths;

const config = request.getConfig();
const schemaClient = await this.cms.getExecutableSchema("preview");
const model = await this.cms.getModel(config.modelId);

const listQuery = new ModelListQuery();
const query = listQuery.getQuery(model, queryPaths);

const response = await schemaClient({
query,
operationName: "ListEntries",
variables: {
limit: config.limit || 10
}
});

// @ts-expect-error Naive return for the time being.
return response.data.entries.data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import zod from "zod";
import type { CmsContext } from "@webiny/api-headless-cms/types";
import type { DataLoaderRequest, DataLoaderResult, IDataSource } from "~/dataSources";
import { ModelGetQuery } from "~/dataSources/cmsDataSources/ModelGetQuery";

export interface CmsEntryDataSourceConfig {
modelId: string;
entryId: string;
}

export class CmsEntryDataSource implements IDataSource<CmsEntryDataSourceConfig> {
private cms: CmsContext["cms"];

constructor(cms: CmsContext["cms"]) {
this.cms = cms;
}

getType() {
return "cms.entry";
}

getConfigSchema(): zod.Schema {
return zod.object({
modelId: zod.string(),
entryId: zod.string()
});
}

async load(request: DataLoaderRequest<CmsEntryDataSourceConfig>): Promise<DataLoaderResult> {
const requestedPaths = request.getPaths();
const queryPaths = !requestedPaths || requestedPaths.length === 0 ? ["id"] : requestedPaths;

const config = request.getConfig();
const schemaClient = await this.cms.getExecutableSchema("preview");
const model = await this.cms.getModel(config.modelId);

const listQuery = new ModelGetQuery();
const query = listQuery.getQuery(model, queryPaths);

const response = await schemaClient({
query,
operationName: "GetEntry",
variables: {
entryId: config.entryId
}
});

// @ts-expect-error Naive return for the time being.
return response.data.entry.data;
}
}
Loading

0 comments on commit 592175e

Please sign in to comment.