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

feat(api-form-builder): storage operations #1942

Merged
merged 37 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
970edf1
feat(api-form-builder): interface for storage operations
brunozoric Sep 25, 2021
8e517e9
feat(api-form-builder-so-ddb-es): wip of the package
brunozoric Sep 25, 2021
e012f10
Merge branch 'next' into feat/api-form-builder/storage-operations
brunozoric Sep 28, 2021
d2dd224
feat(db-dynamodb): add transformation option to field plugin params
brunozoric Sep 28, 2021
3c11daa
feat(api-form-builder-so-ddb-es): initial methods
brunozoric Sep 28, 2021
ead032d
Merge branch 'next' into feat/api-form-builder/storage-operations
brunozoric Sep 29, 2021
0bbd4a2
refactor(api-elasticsearch): remove the context from es plugins
brunozoric Oct 1, 2021
e5287ba
refactor(db-dynamodb): pass the field plugins to ddb sorting
brunozoric Oct 1, 2021
0b4395a
feat(api-form-builder): interfaces for storage operations
brunozoric Oct 1, 2021
8bb6528
feat(utils): add utils package
brunozoric Oct 1, 2021
0b203f1
refactor(db-dynamodb): early return if nothing to sort by
brunozoric Oct 1, 2021
73f4cc7
feat(api-form-builder): extract submissions crud parts into separate …
brunozoric Oct 1, 2021
f45988f
feat(api-form-builder-so-ddb-es): add elasticsearch entity and table …
brunozoric Oct 1, 2021
91e9212
refactor(api-form-builder-so-ddb-es): done with system ops
brunozoric Oct 1, 2021
c6ce76e
refactor(api-form-builder-so-ddb-es): done with settings ops
brunozoric Oct 1, 2021
6f04d1a
refactor(api-form-builder-so-ddb-es): done with submission ops
brunozoric Oct 1, 2021
d35faf7
feat(api-form-builder): transfered all the logic from original package
brunozoric Oct 5, 2021
48b90b9
feat(api-elasticsearch): extract applying where conditions onto query
brunozoric Oct 5, 2021
5ccbaab
Merge branch 'next' into feat/api-form-builder/storage-operations
brunozoric Oct 5, 2021
89c311c
fix(pubsub): raise version
brunozoric Oct 5, 2021
681dba4
refactor(api-elasticsearch): split es client and context
brunozoric Oct 7, 2021
6237a56
refactor(api-file-manager): improve types in fileSettings tests
brunozoric Oct 7, 2021
fbc1b7b
feat(handler-db): switch context object for context class
brunozoric Oct 7, 2021
d754999
refactor(api-form-builder): tests support multiple storage operations
brunozoric Oct 7, 2021
983f18d
chore: update yarn.lock
brunozoric Oct 7, 2021
3a375b3
feat: add process.env.DB_TABLE to each table name
brunozoric Oct 7, 2021
6a96b2e
feat(api): add form builder storage operations
brunozoric Oct 7, 2021
78b98c4
fix(api-form-builder-so-ddb-es): test initialization
brunozoric Oct 7, 2021
d588e76
refactor(api-elasticsearch): init operators in global
brunozoric Oct 8, 2021
42d904f
refactor(utils): simplify parse identifier
brunozoric Oct 8, 2021
b19423c
fix(cwp-template-aws): missing form storage operations
brunozoric Oct 8, 2021
149b77e
style(api-form-builder-so-ddb-es): remove unnecessary comments
brunozoric Oct 8, 2021
6ed6530
feat(cli): upgrade for 5.16.0
brunozoric Oct 11, 2021
8882bfa
feat(app-form-builder): ui part of the upgrade
brunozoric Oct 11, 2021
5b29302
Merge branch 'next' into feat/api-form-builder/storage-operations
brunozoric Oct 11, 2021
8e647a3
fix(cli): revert version to 5.15.0
brunozoric Oct 11, 2021
4f584c8
fix(cli): remove $ escaping
brunozoric Oct 11, 2021
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
1 change: 1 addition & 0 deletions api/code/graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@webiny/api-file-manager-ddb-es": "^5.15.0",
"@webiny/api-file-manager-s3": "^5.15.0",
"@webiny/api-form-builder": "^5.15.0",
"@webiny/api-form-builder-so-ddb-es": "^5.15.0",
"@webiny/api-headless-cms": "^5.15.0",
"@webiny/api-headless-cms-ddb-es": "^5.15.0",
"@webiny/api-i18n": "^5.15.0",
Expand Down
27 changes: 20 additions & 7 deletions api/code/graphql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,38 @@ import fileManagerPlugins from "@webiny/api-file-manager/plugins";
import fileManagerDynamoDbElasticStorageOperation from "@webiny/api-file-manager-ddb-es";
import logsPlugins from "@webiny/handler-logs";
import fileManagerS3 from "@webiny/api-file-manager-s3";
import formBuilderPlugins from "@webiny/api-form-builder/plugins";
import { createFormBuilder } from "@webiny/api-form-builder";
import securityPlugins from "./security";
import headlessCmsPlugins from "@webiny/api-headless-cms/plugins";
import headlessCmsDynamoDbElasticStorageOperation from "@webiny/api-headless-cms-ddb-es";
import elasticsearchDataGzipCompression from "@webiny/api-elasticsearch/plugins/GzipCompression";
import { createFormBuilderStorageOperations } from "@webiny/api-form-builder-so-ddb-es";

// Imports plugins created via scaffolding utilities.
import scaffoldsPlugins from "./plugins/scaffolds";
import { createElasticsearchClient } from "@webiny/api-elasticsearch/client";

const debug = process.env.DEBUG === "true";

const documentClient = new DocumentClient({
convertEmptyValues: true,
region: process.env.AWS_REGION
});

const elasticsearchClient = createElasticsearchClient({
endpoint: `https://${process.env.ELASTIC_SEARCH_ENDPOINT}`
});

export const handler = createHandler({
plugins: [
dynamoDbPlugins(),
logsPlugins(),
graphqlPlugins({ debug }),
elasticSearch({ endpoint: `https://${process.env.ELASTIC_SEARCH_ENDPOINT}` }),
elasticSearch(elasticsearchClient),
dbPlugins({
table: process.env.DB_TABLE,
driver: new DynamoDbDriver({
documentClient: new DocumentClient({
convertEmptyValues: true,
region: process.env.AWS_REGION
})
documentClient
})
}),
securityPlugins(),
Expand All @@ -67,7 +75,12 @@ export const handler = createHandler({
pageBuilderPlugins(),
pageBuilderDynamoDbElasticsearchPlugins(),
pageBuilderPrerenderingPlugins(),
formBuilderPlugins(),
createFormBuilder({
storageOperations: createFormBuilderStorageOperations({
documentClient,
elasticsearch: elasticsearchClient
})
}),
headlessCmsPlugins(),
headlessCmsDynamoDbElasticStorageOperation(),
scaffoldsPlugins(),
Expand Down
7 changes: 7 additions & 0 deletions api/code/graphql/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
{
"path": "../../../packages/api-form-builder"
},
{
"path": "../../../packages/api-form-builder-so-ddb-es"
},
{
"path": "../../../packages/api-elasticsearch"
},
Expand Down Expand Up @@ -116,6 +119,10 @@
"@webiny/api-page-builder": ["../../../packages/api-page-builder/src"],
"@webiny/api-form-builder/*": ["../../../packages/api-form-builder/src/*"],
"@webiny/api-form-builder": ["../../../packages/api-form-builder/src"],
"@webiny/api-form-builder-so-ddb-es/*": [
"../../../packages/api-form-builder-so-ddb-es/src/*"
],
"@webiny/api-form-builder-so-ddb-es": ["../../../packages/api-form-builder-so-ddb-es/src"],
"@webiny/api-security/*": ["../../../packages/api-security/src/*"],
"@webiny/api-security": ["../../../packages/api-security/src"],
"@webiny/api-tenancy/*": ["../../../packages/api-tenancy/src/*"],
Expand Down
54 changes: 54 additions & 0 deletions packages/api-elasticsearch/__tests__/where.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { parseWhereKey } from "~/where";

describe("where", () => {
const whereKeys = [
[
"id",
{
field: "id",
operator: "eq"
}
],
[
"id_in",
{
field: "id",
operator: "in"
}
],
[
"id_not_in",
{
field: "id",
operator: "not_in"
}
]
];

test.each(whereKeys)(
"parse should result in field and operator values",
(key: string, expected: any) => {
const result = parseWhereKey(key);

expect(result).toEqual(expected);
}
);

const malformedWhereKeys = [["_a"], ["_"], ["__"], ["a_"]];

test.each(malformedWhereKeys)(
`should throw error when malformed key is passed "%s"`,
(key: string) => {
expect(() => {
parseWhereKey(key);
}).toThrow(`It is not possible to search by key "${key}"`);
}
);

test("should throw error when malformed field is parsed out", () => {
const key = "a0_in";
expect(() => {
parseWhereKey(key);
}).toThrow(`Cannot filter by "a0".`);
});
});
25 changes: 25 additions & 0 deletions packages/api-elasticsearch/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Client, ClientOptions } from "@elastic/elasticsearch";
import AWS from "aws-sdk";
import createAwsElasticsearchConnector from "aws-elasticsearch-connector";

export interface ElasticsearchClientOptions extends ClientOptions {
endpoint?: string;
}

export const createElasticsearchClient = (options: ElasticsearchClientOptions) => {
const { endpoint, node, ...rest } = options;

const clientOptions: ClientOptions = {
node: endpoint || node,
...rest
};

if (!clientOptions.auth) {
/**
* If no `auth` configuration is present, we setup AWS connector.
*/
Object.assign(clientOptions, createAwsElasticsearchConnector(AWS.config));
}

return new Client(clientOptions);
};
63 changes: 12 additions & 51 deletions packages/api-elasticsearch/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,29 @@
import { Client, ClientOptions } from "@elastic/elasticsearch";
import AWS from "aws-sdk";
import createAwsElasticsearchConnector from "aws-elasticsearch-connector";
import { ElasticsearchContext } from "~/types";
import { ContextPlugin } from "@webiny/handler/plugins/ContextPlugin";
import {
ElasticsearchQueryBuilderOperatorBetweenPlugin,
ElasticsearchQueryBuilderOperatorNotBetweenPlugin,
ElasticsearchQueryBuilderOperatorContainsPlugin,
ElasticsearchQueryBuilderOperatorNotContainsPlugin,
ElasticsearchQueryBuilderOperatorEqualPlugin,
ElasticsearchQueryBuilderOperatorNotPlugin,
ElasticsearchQueryBuilderOperatorGreaterThanPlugin,
ElasticsearchQueryBuilderOperatorGreaterThanOrEqualToPlugin,
ElasticsearchQueryBuilderOperatorLesserThanPlugin,
ElasticsearchQueryBuilderOperatorLesserThanOrEqualToPlugin,
ElasticsearchQueryBuilderOperatorInPlugin,
ElasticsearchQueryBuilderOperatorAndInPlugin,
ElasticsearchQueryBuilderOperatorNotInPlugin
} from "~/plugins/operator";
import WebinyError from "@webiny/error";
import { createElasticsearchClient, ElasticsearchClientOptions } from "~/client";
import { getElasticsearchOperators } from "~/operators";
import { Client } from "@elastic/elasticsearch";

interface ElasticsearchClientOptions extends ClientOptions {
endpoint?: string;
}

export default (options: ElasticsearchClientOptions): ContextPlugin<ElasticsearchContext> => {
const { endpoint, node, ...rest } = options;
/**
* We must accept either Elasticsearch client or options that create the client.
*/
export default (
params: ElasticsearchClientOptions | Client
): ContextPlugin<ElasticsearchContext> => {
return new ContextPlugin<ElasticsearchContext>(context => {
if (context.elasticsearch) {
throw new WebinyError(
"Elasticsearch client is already initialized, no need to define it again. Check your code for duplicate initializations.",
"ELASTICSEARCH_ALREADY_INITIALIZED"
);
}
const clientOptions: ClientOptions = {
node: endpoint || node,
...rest
};

if (!clientOptions.auth) {
/**
* If no `auth` configuration is present, we setup AWS connector.
*/
Object.assign(clientOptions, createAwsElasticsearchConnector(AWS.config));
}
/**
* Initialize the Elasticsearch client.
*/
context.elasticsearch = new Client(clientOptions);
context.elasticsearch =
params instanceof Client ? params : createElasticsearchClient(params);

context.plugins.register([
new ElasticsearchQueryBuilderOperatorBetweenPlugin(),
new ElasticsearchQueryBuilderOperatorNotBetweenPlugin(),
new ElasticsearchQueryBuilderOperatorContainsPlugin(),
new ElasticsearchQueryBuilderOperatorNotContainsPlugin(),
new ElasticsearchQueryBuilderOperatorEqualPlugin(),
new ElasticsearchQueryBuilderOperatorNotPlugin(),
new ElasticsearchQueryBuilderOperatorGreaterThanPlugin(),
new ElasticsearchQueryBuilderOperatorGreaterThanOrEqualToPlugin(),
new ElasticsearchQueryBuilderOperatorLesserThanPlugin(),
new ElasticsearchQueryBuilderOperatorLesserThanOrEqualToPlugin(),
new ElasticsearchQueryBuilderOperatorInPlugin(),
new ElasticsearchQueryBuilderOperatorAndInPlugin(),
new ElasticsearchQueryBuilderOperatorNotInPlugin()
]);
context.plugins.register(getElasticsearchOperators());
});
};
34 changes: 34 additions & 0 deletions packages/api-elasticsearch/src/operators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ElasticsearchQueryBuilderOperatorBetweenPlugin } from "~/plugins/operator/between";
import { ElasticsearchQueryBuilderOperatorNotBetweenPlugin } from "~/plugins/operator/notBetween";
import { ElasticsearchQueryBuilderOperatorContainsPlugin } from "~/plugins/operator/contains";
import { ElasticsearchQueryBuilderOperatorNotContainsPlugin } from "~/plugins/operator/notContains";
import { ElasticsearchQueryBuilderOperatorEqualPlugin } from "~/plugins/operator/equal";
import { ElasticsearchQueryBuilderOperatorNotPlugin } from "~/plugins/operator/not";
import { ElasticsearchQueryBuilderOperatorGreaterThanPlugin } from "~/plugins/operator/gt";
import { ElasticsearchQueryBuilderOperatorGreaterThanOrEqualToPlugin } from "~/plugins/operator/gte";
import { ElasticsearchQueryBuilderOperatorLesserThanPlugin } from "~/plugins/operator/lt";
import { ElasticsearchQueryBuilderOperatorLesserThanOrEqualToPlugin } from "~/plugins/operator/lte";
import { ElasticsearchQueryBuilderOperatorInPlugin } from "~/plugins/operator/in";
import { ElasticsearchQueryBuilderOperatorAndInPlugin } from "~/plugins/operator/andIn";
import { ElasticsearchQueryBuilderOperatorNotInPlugin } from "~/plugins/operator/notIn";

const operators = [
new ElasticsearchQueryBuilderOperatorBetweenPlugin(),
new ElasticsearchQueryBuilderOperatorNotBetweenPlugin(),
new ElasticsearchQueryBuilderOperatorContainsPlugin(),
new ElasticsearchQueryBuilderOperatorNotContainsPlugin(),
new ElasticsearchQueryBuilderOperatorEqualPlugin(),
new ElasticsearchQueryBuilderOperatorNotPlugin(),
new ElasticsearchQueryBuilderOperatorGreaterThanPlugin(),
new ElasticsearchQueryBuilderOperatorGreaterThanOrEqualToPlugin(),
new ElasticsearchQueryBuilderOperatorLesserThanPlugin(),
new ElasticsearchQueryBuilderOperatorLesserThanOrEqualToPlugin(),
new ElasticsearchQueryBuilderOperatorInPlugin(),
new ElasticsearchQueryBuilderOperatorAndInPlugin(),
new ElasticsearchQueryBuilderOperatorNotInPlugin()
];
/**
* We export as a function because there might be something to be sent to the operators at some point.
* This way, we make it easier to upgrade.
*/
export const getElasticsearchOperators = () => operators;
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import WebinyError from "@webiny/error";
import { Plugin } from "@webiny/plugins";
import { ContextInterface } from "@webiny/handler/types";
import { SearchBody } from "elastic-ts";

export interface ModifyBodyParams<T extends ContextInterface> {
context: T;
export interface ModifyBodyParams {
body: SearchBody;
}

interface Callable<T extends ContextInterface> {
(params: ModifyBodyParams<T>): void;
interface Callable {
(params: ModifyBodyParams): void;
}

export abstract class ElasticsearchBodyModifierPlugin<
T extends ContextInterface = ContextInterface
> extends Plugin {
private readonly callable?: Callable<T>;
export abstract class ElasticsearchBodyModifierPlugin extends Plugin {
private readonly callable?: Callable;

public constructor(callable?: Callable<T>) {
public constructor(callable?: Callable) {
super();
this.callable = callable;
}

public modifyBody(params: ModifyBodyParams<T>): void {
public modifyBody(params: ModifyBodyParams): void {
if (typeof this.callable !== "function") {
throw new WebinyError(
`Missing modification for the body.`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Plugin } from "@webiny/plugins";
import { FieldSortOptions, SortOrder } from "elastic-ts";
import { ContextInterface } from "@webiny/handler/types";

export type UnmappedTypes = "date" | "long" | string;

const keywordLessUnmappedType = ["date", "long"];

const unmappedTypeHasKeyword = (type: string): boolean => {
if (keywordLessUnmappedType.includes(type)) {
return false;
}
return true;
};

export interface ToSearchValueParams {
/**
* Some variable that has a ContextInterface as a base.
*/
context: ContextInterface;
/**
* The value to transform.
*/
Expand Down Expand Up @@ -96,6 +100,9 @@ export abstract class ElasticsearchFieldPlugin extends Plugin {
this._path = params.path || params.field;
this._keyword = params.keyword === undefined ? true : params.keyword;
this._unmappedType = params.unmappedType;
if (unmappedTypeHasKeyword(params.unmappedType) === false) {
this._keyword = false;
}
this._sortable = params.sortable === undefined ? true : params.sortable;
this._searchable = params.searchable === undefined ? true : params.searchable;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import WebinyError from "@webiny/error";
import { Plugin } from "@webiny/plugins";
import { ElasticsearchBoolQueryConfig } from "~/types";
import { ContextInterface } from "@webiny/handler/types";

export interface ModifyQueryParams<T extends ContextInterface> {
context: T;
export interface ModifyQueryParams {
query: ElasticsearchBoolQueryConfig;
where: Record<string, any>;
}

interface Callable<T extends ContextInterface> {
(params: ModifyQueryParams<T>): void;
interface Callable {
(params: ModifyQueryParams): void;
}

export abstract class ElasticsearchQueryModifierPlugin<
T extends ContextInterface = ContextInterface
> extends Plugin {
private readonly callable?: Callable<T>;
export abstract class ElasticsearchQueryModifierPlugin extends Plugin {
private readonly callable?: Callable;

public constructor(callable?: Callable<T>) {
public constructor(callable?: Callable) {
super();
this.callable = callable;
}

public modifyQuery(params: ModifyQueryParams<T>): void {
public modifyQuery(params: ModifyQueryParams): void {
if (typeof this.callable !== "function") {
throw new WebinyError(
`Missing modification for the query.`,
Expand Down