diff --git a/packages/amplication-client/src/Workspaces/WorkspaceOverview.scss b/packages/amplication-client/src/Workspaces/WorkspaceOverview.scss
new file mode 100644
index 0000000000..f5e4162810
--- /dev/null
+++ b/packages/amplication-client/src/Workspaces/WorkspaceOverview.scss
@@ -0,0 +1,9 @@
+@import "../style/index.scss";
+
+.workspaces-layout__main_content--xl {
+ .workspace-overview {
+ &__content {
+ flex-direction: row;
+ }
+ }
+}
diff --git a/packages/amplication-client/src/Workspaces/WorkspaceOverview.tsx b/packages/amplication-client/src/Workspaces/WorkspaceOverview.tsx
index fc46671834..541f28b0dd 100644
--- a/packages/amplication-client/src/Workspaces/WorkspaceOverview.tsx
+++ b/packages/amplication-client/src/Workspaces/WorkspaceOverview.tsx
@@ -28,6 +28,7 @@ import { EnumSubscriptionPlan } from "../models";
import { GET_WORKSPACE_MEMBERS, TData as MemberListData } from "./MemberList";
import WorkspaceSelector, { getWorkspaceColor } from "./WorkspaceSelector";
import { UsageInsights } from "../UsageInsights/UsageInsights";
+import "./WorkspaceOverview.scss";
const CLASS_NAME = "workspace-overview";
const PAGE_TITLE = "Workspace Overview";
@@ -115,15 +116,15 @@ export const WorkspaceOverview = () => {
-
-
+
);
diff --git a/packages/amplication-client/src/models.ts b/packages/amplication-client/src/models.ts
index 2ecbe2709d..38bc944ed5 100644
--- a/packages/amplication-client/src/models.ts
+++ b/packages/amplication-client/src/models.ts
@@ -727,6 +727,7 @@ export enum EnumDataType {
DateTime = 'DateTime',
DecimalNumber = 'DecimalNumber',
Email = 'Email',
+ File = 'File',
GeographicLocation = 'GeographicLocation',
Id = 'Id',
Json = 'Json',
diff --git a/packages/amplication-code-gen-types/src/code-gen-types.ts b/packages/amplication-code-gen-types/src/code-gen-types.ts
index e850ca92b2..e75627b685 100644
--- a/packages/amplication-code-gen-types/src/code-gen-types.ts
+++ b/packages/amplication-code-gen-types/src/code-gen-types.ts
@@ -376,7 +376,7 @@ type defaultDtoNestedTypes = Extract<
>;
export type entityDefaultDtos = {
- [key in defaultDtoTypes]: ModuleDto | undefined;
+ [key in defaultDtoTypes]: ModuleDto;
};
export type entityDefaultNestedDtos = {
diff --git a/packages/amplication-code-gen-types/src/data-type-to-schema.ts b/packages/amplication-code-gen-types/src/data-type-to-schema.ts
index 8f730411c4..2977bc9105 100644
--- a/packages/amplication-code-gen-types/src/data-type-to-schema.ts
+++ b/packages/amplication-code-gen-types/src/data-type-to-schema.ts
@@ -26,4 +26,5 @@ export const DATA_TYPE_TO_SCHEMA: { [dataType in models.EnumDataType]: any } = {
[models.EnumDataType.Username]: schemas.username,
[models.EnumDataType.Roles]: schemas.roles,
[models.EnumDataType.Json]: schemas.json,
+ [models.EnumDataType.File]: schemas.file,
};
diff --git a/packages/amplication-code-gen-types/src/models.ts b/packages/amplication-code-gen-types/src/models.ts
index 41da155cdd..b7b694a531 100644
--- a/packages/amplication-code-gen-types/src/models.ts
+++ b/packages/amplication-code-gen-types/src/models.ts
@@ -724,6 +724,7 @@ export enum EnumDataType {
DateTime = 'DateTime',
DecimalNumber = 'DecimalNumber',
Email = 'Email',
+ File = 'File',
GeographicLocation = 'GeographicLocation',
Id = 'Id',
Json = 'Json',
diff --git a/packages/amplication-code-gen-types/src/schemas/file.json b/packages/amplication-code-gen-types/src/schemas/file.json
new file mode 100644
index 0000000000..59f4a2ab74
--- /dev/null
+++ b/packages/amplication-code-gen-types/src/schemas/file.json
@@ -0,0 +1,26 @@
+{
+ "$id": "https://amplication.com/schema/entityfield/properties/file.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "file",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "maxFileSize": {
+ "type": "number",
+ "minimum": 0
+ },
+ "allowedMimeTypes": {
+ "type": "array",
+ "additionalItems": true,
+ "minItems": 0,
+ "items": {
+ "$comment": "String that the MIME type of the file may contain",
+ "type": "string"
+ }
+ },
+ "containerPath": {
+ "type": "string",
+ "default": "/"
+ }
+ }
+}
diff --git a/packages/amplication-code-gen-types/src/schemas/index.ts b/packages/amplication-code-gen-types/src/schemas/index.ts
index f3545d99ee..ccbb8a718b 100644
--- a/packages/amplication-code-gen-types/src/schemas/index.ts
+++ b/packages/amplication-code-gen-types/src/schemas/index.ts
@@ -15,4 +15,5 @@ export { default as geographicLocation } from "./geographicLocation.json";
export { default as password } from "./password.json";
export { default as username } from "./username.json";
export { default as roles } from "./roles.json";
+export { default as file } from "./file.json";
export { default as json } from "./json.json";
diff --git a/packages/amplication-prisma-db/prisma/migrations/20240331203055_add_file_to_enum_data_type/migration.sql b/packages/amplication-prisma-db/prisma/migrations/20240331203055_add_file_to_enum_data_type/migration.sql
new file mode 100644
index 0000000000..35f0b07213
--- /dev/null
+++ b/packages/amplication-prisma-db/prisma/migrations/20240331203055_add_file_to_enum_data_type/migration.sql
@@ -0,0 +1,2 @@
+-- AlterEnum
+ALTER TYPE "EnumDataType" ADD VALUE 'File';
diff --git a/packages/amplication-prisma-db/prisma/schema.prisma b/packages/amplication-prisma-db/prisma/schema.prisma
index a6a1a3aaa9..1f92073704 100644
--- a/packages/amplication-prisma-db/prisma/schema.prisma
+++ b/packages/amplication-prisma-db/prisma/schema.prisma
@@ -497,6 +497,7 @@ enum EnumDataType {
Username
Password
Json
+ File
}
enum EnumBlockType {
diff --git a/packages/amplication-server/jest.config.ts b/packages/amplication-server/jest.config.ts
index 03cc947e92..0616d2d370 100644
--- a/packages/amplication-server/jest.config.ts
+++ b/packages/amplication-server/jest.config.ts
@@ -19,8 +19,8 @@ export default {
coverageDirectory: "../../coverage/packages/amplication-server",
coverageThreshold: {
global: {
- branches: 82.27,
- lines: 53.9,
+ branches: 81,
+ lines: 53,
},
},
};
diff --git a/packages/amplication-server/src/core/UsageInsights/usageInsights.service.ts b/packages/amplication-server/src/core/UsageInsights/usageInsights.service.ts
index 455ab15890..d42b035b08 100644
--- a/packages/amplication-server/src/core/UsageInsights/usageInsights.service.ts
+++ b/packages/amplication-server/src/core/UsageInsights/usageInsights.service.ts
@@ -23,6 +23,14 @@ export class UsageInsightsService {
private readonly prisma: PrismaService
) {}
+ private projectIdsFilter = (projectIds: string[]) => {
+ let filter = Prisma.empty;
+ if (projectIds.length > 0) {
+ filter = Prisma.sql`AND r."projectId" IN (${Prisma.join(projectIds)})`;
+ }
+ return filter;
+ };
+
async countLinesOfCode({
projectIds,
startDate,
@@ -91,7 +99,7 @@ export class UsageInsightsService {
JOIN "Resource" r ON b."resourceId" = r."id"
WHERE b."createdAt" >= ${startDate}
AND b."createdAt" <= ${endDate}
- AND r."projectId" IN (${Prisma.join(projectIds)})
+ ${this.projectIdsFilter(projectIds)}
GROUP BY year, month, time_group
ORDER BY year, month, time_group;
`;
@@ -112,8 +120,8 @@ export class UsageInsightsService {
FROM "EntityVersion" ev
JOIN "Entity" e ON ev."entityId" = e."id"
JOIN "Resource" r ON e."resourceId" = r."id"
- WHERE r."projectId" IN (${Prisma.join(projectIds)})
- AND ev."updatedAt" >= ${startDate} AND ev."updatedAt" <= ${endDate}
+ WHERE ev."updatedAt" >= ${startDate} AND ev."updatedAt" <= ${endDate}
+ ${this.projectIdsFilter(projectIds)}
GROUP BY year, month, time_group
ORDER BY year, month, time_group;
`;
@@ -131,7 +139,8 @@ export class UsageInsightsService {
timeGroup,
}: BlockChangesArgs): Promise
{
let results: QueryRawResult[];
- // Prisma query row doesn't work as expected with enums, so we use switch case instead of one query with ${blokType}
+
+ // Prisma query raw doesn't work as expected with enums, so we use switch case instead of one query with ${blockType}
switch (blockType) {
case EnumBlockType.ModuleAction:
results = await this.prisma.$queryRaw`
@@ -140,7 +149,7 @@ export class UsageInsightsService {
JOIN "Block" b ON bv."blockId" = b."id"
JOIN "Resource" r ON b."resourceId" = r."id"
WHERE b."blockType" = 'ModuleAction'
- AND r."projectId" IN (${Prisma.join(projectIds)})
+ ${this.projectIdsFilter(projectIds)}
AND bv."updatedAt" >= ${startDate} AND bv."updatedAt" <= ${endDate}
GROUP BY year, month, time_group
ORDER BY year, month, time_group;
@@ -153,7 +162,7 @@ export class UsageInsightsService {
JOIN "Block" b ON bv."blockId" = b."id"
JOIN "Resource" r ON b."resourceId" = r."id"
WHERE b."blockType" = 'PluginInstallation'
- AND r."projectId" IN (${Prisma.join(projectIds)})
+ ${this.projectIdsFilter(projectIds)}
AND bv."updatedAt" >= ${startDate} AND bv."updatedAt" <= ${endDate}
GROUP BY year, month, time_group
ORDER BY year, month, time_group;
diff --git a/packages/amplication-server/src/core/entity/constants.ts b/packages/amplication-server/src/core/entity/constants.ts
index 5c8e945c1c..f32b5a4d6c 100644
--- a/packages/amplication-server/src/core/entity/constants.ts
+++ b/packages/amplication-server/src/core/entity/constants.ts
@@ -231,6 +231,7 @@ export const DATA_TYPE_TO_DEFAULT_PROPERTIES: {
[EnumDataType.Username]: {},
[EnumDataType.Password]: {},
[EnumDataType.Roles]: {},
+ [EnumDataType.File]: {},
};
export const PRISMA_IMPORT_ACTION_LOG: Action = {
diff --git a/packages/amplication-server/src/core/module/module.service.ts b/packages/amplication-server/src/core/module/module.service.ts
index aafed8e9ac..f76bf370d1 100644
--- a/packages/amplication-server/src/core/module/module.service.ts
+++ b/packages/amplication-server/src/core/module/module.service.ts
@@ -255,7 +255,7 @@ export class ModuleService extends BlockTypeService<
await this.moduleDtoService.createDefaultDtosForEntityModule(
entity,
- module,
+ module.id,
user
);
diff --git a/packages/amplication-server/src/core/moduleDto/moduleDto.service.spec.ts b/packages/amplication-server/src/core/moduleDto/moduleDto.service.spec.ts
index c579ec1d47..caaf4a6728 100644
--- a/packages/amplication-server/src/core/moduleDto/moduleDto.service.spec.ts
+++ b/packages/amplication-server/src/core/moduleDto/moduleDto.service.spec.ts
@@ -174,7 +174,7 @@ const blockServiceCreateMock = jest.fn(
updatedAt: new Date(),
blockType: EnumBlockType.ModuleDto,
enabled: true,
- dtoType: EnumModuleDtoType.Custom,
+ dtoType: args.data.dtoType as EnumModuleDtoType,
versionNumber: 0,
parentBlock: null,
description: data.description,
@@ -317,7 +317,10 @@ describe("ModuleDtoService", () => {
name: EXAMPLE_DTO_NAME,
},
};
- expect(await service.createEnum(args, EXAMPLE_USER)).toEqual(EXAMPLE_DTO);
+ expect(await service.createEnum(args, EXAMPLE_USER)).toEqual({
+ ...EXAMPLE_DTO,
+ dtoType: EnumModuleDtoType.CustomEnum,
+ });
expect(blockServiceCreateMock).toBeCalledTimes(1);
expect(blockServiceCreateMock).toBeCalledWith(
{
@@ -498,12 +501,12 @@ describe("ModuleDtoService", () => {
expect(
await service.createDefaultDtosForEntityModule(
EXAMPLE_ENTITY,
- EXAMPLE_MODULE,
+ EXAMPLE_MODULE.id,
EXAMPLE_USER
)
).toEqual([
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.Entity,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -519,7 +522,7 @@ describe("ModuleDtoService", () => {
properties: [],
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.CountArgs,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -535,7 +538,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.CreateArgs,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -551,7 +554,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.CreateInput,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -567,7 +570,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.DeleteArgs,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -583,7 +586,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.FindManyArgs,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -599,7 +602,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.FindOneArgs,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -615,7 +618,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.ListRelationFilter,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -631,7 +634,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.OrderByInput,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -647,7 +650,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.UpdateArgs,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -663,7 +666,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.UpdateInput,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -679,7 +682,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.WhereInput,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -695,7 +698,7 @@ describe("ModuleDtoService", () => {
versionNumber: 0,
},
{
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.WhereUniqueInput,
blockType: "ModuleDto",
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
@@ -714,6 +717,231 @@ describe("ModuleDtoService", () => {
expect(blockServiceCreateMock).toBeCalledTimes(13);
});
+ it("should create default dtos for entity only if not already created", async () => {
+ const mockServiceFindMany = jest.spyOn(service, "findMany");
+
+ const expectedExistingModuleDto: ModuleDto = {
+ dtoType: EnumModuleDtoType.Entity,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "the Example entity model",
+ displayName: "ExampleEntity",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntity",
+ outputParameters: null,
+ parentBlock: null,
+ versionNumber: 0,
+ properties: [],
+ };
+ mockServiceFindMany.mockResolvedValue([expectedExistingModuleDto]);
+
+ expect(
+ await service.createDefaultDtosForEntityModule(
+ EXAMPLE_ENTITY,
+ EXAMPLE_MODULE.id,
+ EXAMPLE_USER
+ )
+ ).toEqual([
+ {
+ dtoType: EnumModuleDtoType.CountArgs,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity count",
+ displayName: "ExampleEntityCountArgs",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityCountArgs",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.CreateArgs,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Args type for Example entity creation",
+ displayName: "CreateExampleEntityArgs",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "CreateExampleEntityArgs",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.CreateInput,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity creation",
+ displayName: "ExampleEntityCreateInput",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityCreateInput",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.DeleteArgs,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Args type for Example entity deletion",
+ displayName: "DeleteExampleEntityArgs",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "DeleteExampleEntityArgs",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.FindManyArgs,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Args type for Example entity search",
+ displayName: "ExampleEntityFindManyArgs",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityFindManyArgs",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.FindOneArgs,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Args type for Example entity retrieval",
+ displayName: "ExampleEntityFindUniqueArgs",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityFindUniqueArgs",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.ListRelationFilter,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity relation filter",
+ displayName: "ExampleEntityListRelationFilter",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityListRelationFilter",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.OrderByInput,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity sorting",
+ displayName: "ExampleEntityOrderByInput",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityOrderByInput",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.UpdateArgs,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Args type for Example entity update",
+ displayName: "UpdateExampleEntityArgs",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "UpdateExampleEntityArgs",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.UpdateInput,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity update",
+ displayName: "ExampleEntityUpdateInput",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityUpdateInput",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.WhereInput,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity search",
+ displayName: "ExampleEntityWhereInput",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityWhereInput",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ {
+ dtoType: EnumModuleDtoType.WhereUniqueInput,
+ blockType: "ModuleDto",
+ createdAt: expect.any(Date),
+ updatedAt: expect.any(Date),
+ description: "Input type for Example entity retrieval",
+ displayName: "ExampleEntityWhereUniqueInput",
+ enabled: true,
+ id: "exampleDtoId",
+ inputParameters: null,
+ name: "ExampleEntityWhereUniqueInput",
+ outputParameters: null,
+ parentBlock: null,
+ properties: [],
+ versionNumber: 0,
+ },
+ ]);
+ expect(mockServiceFindMany).toBeCalledTimes(1);
+ expect(blockServiceCreateMock).toBeCalledTimes(12);
+ });
+
it("should update default dtos for entity", async () => {
await service.updateDefaultDtosForEntityModule(
EXAMPLE_ENTITY,
@@ -780,7 +1008,7 @@ describe("ModuleDtoService", () => {
description:
"Input type for Example entity creation with related ExampleEntity",
displayName: "ExampleEntityCreateNestedManyWithoutExampleEntitiesInput",
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.CreateNestedManyInput,
enabled: true,
id: "exampleDtoId",
inputParameters: null,
@@ -797,7 +1025,7 @@ describe("ModuleDtoService", () => {
createdAt: expect.any(Date),
description: "Input type for Example entity retrieval",
displayName: "ExampleEntityUpdateManyWithoutExampleEntitiesInput",
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.UpdateNestedManyInput,
enabled: true,
id: "exampleDtoId",
inputParameters: null,
@@ -832,7 +1060,7 @@ describe("ModuleDtoService", () => {
EXAMPLE_MODULE.id,
EXAMPLE_USER
)
- ).toEqual(null);
+ ).toEqual([]);
});
it("should not update default dtos for relation field when it is not one-to-many ", async () => {
@@ -1100,7 +1328,7 @@ describe("ModuleDtoService", () => {
description:
"Enum type for field exampleEntityFieldName of Example entity model",
displayName: "EnumExampleEntityExampleEntityFieldName",
- dtoType: "Custom",
+ dtoType: EnumModuleDtoType.Enum,
enabled: true,
id: "exampleDtoId",
inputParameters: null,
@@ -1204,7 +1432,7 @@ describe("ModuleDtoService", () => {
expect(
await service.createDefaultDtosForEntityModule(
EXAMPLE_ENTITY,
- EXAMPLE_MODULE,
+ EXAMPLE_MODULE.id,
EXAMPLE_USER
)
).toEqual([]);
diff --git a/packages/amplication-server/src/core/moduleDto/moduleDto.service.ts b/packages/amplication-server/src/core/moduleDto/moduleDto.service.ts
index 8969e8fcb5..0dac9eccb3 100644
--- a/packages/amplication-server/src/core/moduleDto/moduleDto.service.ts
+++ b/packages/amplication-server/src/core/moduleDto/moduleDto.service.ts
@@ -317,7 +317,7 @@ export class ModuleDtoService extends BlockTypeService<
async createDefaultDtosForEntityModule(
entity: Entity,
- module: Module,
+ moduleId: Module["id"],
user: User
): Promise {
if (!this.customActionsEnabled) {
@@ -327,36 +327,60 @@ export class ModuleDtoService extends BlockTypeService<
const defaultDtos = await getDefaultDtosForEntity(
entity as unknown as CodeGenTypes.Entity
);
- return await Promise.all(
- Object.keys(defaultDtos).map((dto) => {
- const dtoData = defaultDtos[dto];
+ const existingModuleDtosForEntity = await this.findMany({
+ where: {
+ parentBlock: {
+ id: moduleId,
+ },
+ },
+ });
- return (
- defaultDtos[dto] &&
- super.create(
- {
- data: {
- ...dtoData,
- displayName: defaultDtos[dto].name,
- properties: [], //default DTOs do not have properties
- parentBlock: {
- connect: {
- id: module.id,
+ const existingModuleDtosForEntityMap = existingModuleDtosForEntity.reduce(
+ (map, dto) => {
+ map[dto.dtoType] = dto;
+ return map;
+ },
+ {} as Record
+ );
+
+ const promisesResults = await Promise.allSettled(
+ Object.entries(defaultDtos).map(async ([dtoType, dtoData]) => {
+ return existingModuleDtosForEntityMap[dtoType]
+ ? null
+ : super.create(
+ {
+ data: {
+ ...dtoData,
+ displayName: dtoData.name,
+ properties: [], //default DTOs do not have properties
+ parentBlock: {
+ connect: {
+ id: moduleId,
+ },
},
- },
- resource: {
- connect: {
- id: entity.resourceId,
+ resource: {
+ connect: {
+ id: entity.resourceId,
+ },
},
},
},
- },
- user
- //@todo: create properties
- )
- );
+ user
+ //@todo: create properties
+ );
})
);
+
+ return promisesResults.reduce((acc, result) => {
+ // if the promise was rejected or the moduleDto Block was already created, we return the accumulator
+ if (result.status === "rejected") {
+ const error = result.reason as Error;
+ this.logger.error(error.message, error, ModuleDtoService.name);
+ return acc;
+ }
+
+ return result.value ? [...acc, result.value] : acc;
+ }, [] as ModuleDto[]);
}
//call this function when the entity names changes, and we need to update the default dtos
@@ -418,7 +442,7 @@ export class ModuleDtoService extends BlockTypeService<
entity: Entity,
relatedField: EntityField,
relatedEntity: Entity,
- moduleId: string,
+ moduleId: ModuleDto["id"],
user: User
): Promise {
if (!this.customActionsEnabled) {
@@ -431,7 +455,7 @@ export class ModuleDtoService extends BlockTypeService<
//We only need to create default DTOs for many-to-one relations
if (!properties.allowMultipleSelection) {
- return null;
+ return [];
}
//Check if a default dto already exists for this relation
@@ -449,42 +473,57 @@ export class ModuleDtoService extends BlockTypeService<
}
);
- if (existingDefaultDto.length > 0) {
- return existingDefaultDto;
- }
-
const defaultDtos = await getDefaultDtosForRelatedEntity(
entity as unknown as CodeGenTypes.Entity,
relatedEntity as unknown as CodeGenTypes.Entity
);
- return await Promise.all(
- Object.keys(defaultDtos).map((dto) => {
- return (
- defaultDtos[dto] &&
- super.create(
- {
- data: {
- ...defaultDtos[dto],
- displayName: defaultDtos[dto].name,
- relatedEntityId: relatedEntity.id,
- properties: [], //default DTOs do not have properties
- parentBlock: {
- connect: {
- id: moduleId,
+
+ const existingModuleDtosForRelationMap = existingDefaultDto.reduce(
+ (map, dto) => {
+ map[dto.dtoType] = dto;
+ return map;
+ },
+ {} as Record
+ );
+
+ const promisesResults = await Promise.allSettled(
+ Object.entries(defaultDtos).map(([dtoType, dtoData]) => {
+ return existingModuleDtosForRelationMap[dtoType]
+ ? null
+ : super.create(
+ {
+ data: {
+ ...dtoData,
+ displayName: dtoData.name,
+ relatedEntityId: relatedEntity.id,
+ properties: [], //default DTOs do not have properties
+ parentBlock: {
+ connect: {
+ id: moduleId,
+ },
},
- },
- resource: {
- connect: {
- id: entity.resourceId,
+ resource: {
+ connect: {
+ id: entity.resourceId,
+ },
},
},
},
- },
- user
- )
- );
+ user
+ );
})
);
+
+ return promisesResults.reduce((acc, result) => {
+ // if the promise was rejected or the moduleDto Block was already created, we return the accumulator
+ if (result.status === "rejected") {
+ const error = result.reason as Error;
+ this.logger.error(error.message, error, ModuleDtoService.name);
+ return acc;
+ }
+
+ return result.value ? [...acc, result.value] : acc;
+ }, [] as ModuleDto[]);
}
async updateDefaultDtosForRelatedEntity(
diff --git a/packages/amplication-server/src/core/workspace/dto/CreateWorkspacesResourcesDefaultCustomActionsMigrationInput.ts b/packages/amplication-server/src/core/workspace/dto/CreateWorkspacesResourcesDefaultCustomActionsMigrationInput.ts
index 73ec6e9a2e..d0f3632987 100644
--- a/packages/amplication-server/src/core/workspace/dto/CreateWorkspacesResourcesDefaultCustomActionsMigrationInput.ts
+++ b/packages/amplication-server/src/core/workspace/dto/CreateWorkspacesResourcesDefaultCustomActionsMigrationInput.ts
@@ -8,4 +8,9 @@ export class CreateWorkspacesResourcesDefaultCustomActionsMigrationInput {
export class CreateWorkspacesResourcesDefaultCustomDtosMigrationInput {
@ApiProperty()
quantity!: number;
+
+ @ApiProperty({
+ nullable: true,
+ })
+ page?: number;
}
diff --git a/packages/amplication-server/src/core/workspace/workspace.controller.ts b/packages/amplication-server/src/core/workspace/workspace.controller.ts
index 49d5df5af2..11bb14d0a1 100644
--- a/packages/amplication-server/src/core/workspace/workspace.controller.ts
+++ b/packages/amplication-server/src/core/workspace/workspace.controller.ts
@@ -1,4 +1,11 @@
-import { Body, Controller, Inject, Param, Post } from "@nestjs/common";
+import {
+ BadRequestException,
+ Body,
+ Controller,
+ Inject,
+ Param,
+ Post,
+} from "@nestjs/common";
import { AmplicationLogger } from "@amplication/util/nestjs/logging";
import { WorkspaceService } from "./workspace.service";
import { ApiTags } from "@nestjs/swagger";
@@ -68,18 +75,26 @@ export class WorkspaceController {
@Param("token") token: string,
@Body()
data: CreateWorkspacesResourcesDefaultCustomDtosMigrationInput
- ): Promise {
+ ): Promise {
this.logger.info("createWorkspacesResourcesDefaultCustomDtosMigration....");
- const { quantity } = data;
+ const { quantity, page } = data;
if (
this.configService.get("CUSTOM_ACTION_MIGRATION_TOKEN") !== token
) {
this.logger.error("InvalidToken, process aborted");
- return;
+ throw new BadRequestException("InvalidToken, process aborted");
}
- return this.workspaceService.dataMigrateWorkspacesResourcesCustomDtos(
- quantity
- );
+
+ this.workspaceService
+ .dataMigrateWorkspacesResourcesCustomDtos(quantity, page)
+ .catch((error) => {
+ this.logger.error(
+ "Error in createWorkspacesResourcesDefaultCustomDtosMigration",
+ error
+ );
+ });
+
+ return "Check logs for more information";
}
}
diff --git a/packages/amplication-server/src/core/workspace/workspace.service.ts b/packages/amplication-server/src/core/workspace/workspace.service.ts
index a9da9695e2..4d1c1f4753 100644
--- a/packages/amplication-server/src/core/workspace/workspace.service.ts
+++ b/packages/amplication-server/src/core/workspace/workspace.service.ts
@@ -58,6 +58,8 @@ import { generateRandomString } from "../auth/auth-utils";
import { AuthUser, CreatePreviewServiceSettingsArgs } from "../auth/types";
import { ModuleDtoService } from "../moduleDto/moduleDto.service";
import { types } from "@amplication/code-gen-types";
+import { DefaultModuleForEntityNotFoundError } from "../module/DefaultModuleForEntityNotFoundError";
+import { ModuleDto } from "../moduleDto/dto/ModuleDto";
const INVITATION_EXPIRATION_DAYS = 7;
@@ -697,66 +699,147 @@ export class WorkspaceService {
}
async dataMigrateWorkspacesResourcesCustomDtos(
- quantity: number
+ quantity: number,
+ page?: number
): Promise {
- const workspaces = await this.prisma.workspace.findMany({
- where: {
- projects: {
- some: {
- deletedAt: null,
- resources: {
+ this.logger.info(`Migrating started`, {
+ context: WorkspaceService.name,
+ method: this.dataMigrateWorkspacesResourcesCustomDtos.name,
+ });
+
+ let currentPage = page || 1;
+
+ let hasMore = true;
+ const processedWorkspaces: string[] = [];
+
+ do {
+ // get latest active users by chunks of quantity
+ const latestActive = await this.prisma.user.findMany({
+ skip: page ? (currentPage - 1) * quantity : 0,
+ take: quantity,
+ orderBy: {
+ lastActive: "desc",
+ },
+ where: {
+ workspace: {
+ id: { notIn: processedWorkspaces },
+ projects: {
some: {
deletedAt: null,
- archived: { not: true },
- resourceType: EnumResourceType.Service,
- blocks: { none: { blockType: EnumBlockType.ModuleDto } },
- entities: { some: { deletedAt: null } },
+ resources: {
+ some: {
+ deletedAt: null,
+ archived: { not: true },
+ resourceType: EnumResourceType.Service,
+ entities: { some: { deletedAt: null } },
+ },
+ },
},
},
},
+ lastActive: { not: null },
},
- },
- take: quantity,
- include: {
+ include: {
+ workspace: {
+ include: {
+ projects: {
+ where: {
+ deletedAt: null,
+ },
+ include: {
+ resources: {
+ include: {
+ blocks: {
+ where: {
+ blockType: EnumBlockType.ModuleDto,
+ },
+ },
+ entities: {
+ where: {
+ deletedAt: null,
+ },
+ },
+ },
+ where: {
+ resourceType: EnumResourceType.Service,
+ deletedAt: null,
+ archived: { not: true },
+ },
+ },
+ },
+ },
+ users: {
+ orderBy: {
+ lastActive: Prisma.SortOrder.desc,
+ },
+ take: 1,
+ },
+ },
+ },
+ },
+ distinct: ["workspaceId"],
+ });
+
+ const workspaces = latestActive.map((user) => user.workspace);
+ processedWorkspaces.push(...workspaces.map((workspace) => workspace.id));
+
+ this.logger.info(
+ `Migrating workspaces... currentPage: ${currentPage}, quantity: ${quantity}`,
+ {
+ context: WorkspaceService.name,
+ method: this.dataMigrateWorkspacesResourcesCustomDtos.name,
+ workspacesId: workspaces.map((workspace) => workspace.id),
+ }
+ );
+
+ hasMore = page === undefined && workspaces.length > 0;
+
+ await Promise.all(
+ workspaces.map(async (workspace) => {
+ await this.migrateWorkspacesDefaultDtos(workspace);
+ })
+ );
+
+ currentPage++;
+ } while (hasMore);
+
+ await this.prisma.$disconnect();
+
+ const relevantWorkspaces = await this.prisma.workspace.findMany({
+ where: {
users: {
- orderBy: {
- lastActive: Prisma.SortOrder.asc,
+ some: {
+ lastActive: { not: null },
},
},
projects: {
- where: {
+ some: {
deletedAt: null,
- },
- include: {
resources: {
- include: {
- entities: {
- where: {
- deletedAt: null,
- },
- },
- },
- where: {
- resourceType: EnumResourceType.Service,
+ some: {
deletedAt: null,
archived: { not: true },
+ resourceType: EnumResourceType.Service,
+ entities: { some: { deletedAt: null } },
},
},
},
},
},
+ select: {
+ id: true,
+ },
});
- let index = 1;
-
- const workspaceChunks = this.chunkArrayInGroups(workspaces, 200);
-
- for (const workspaceChunk of workspaceChunks) {
- this.logger.info(`chunk number ${index++}`);
- await this.migrateWorkspacesDefaultDtos(workspaceChunk);
- }
-
- await this.prisma.$disconnect();
+ this.logger.info(
+ `Migrating completed. Workspaces processed: ${processedWorkspaces.length} out of ${relevantWorkspaces.length}`,
+ {
+ context: WorkspaceService.name,
+ method: this.dataMigrateWorkspacesResourcesCustomDtos.name,
+ workspacesProcessed: processedWorkspaces,
+ relevantWorkspaces: relevantWorkspaces.map((workspace) => workspace.id),
+ }
+ );
return true;
}
@@ -780,12 +863,17 @@ export class WorkspaceService {
},
},
},
+ users: {
+ some: {
+ lastActive: { not: null },
+ },
+ },
},
take: quantity,
include: {
users: {
orderBy: {
- lastActive: Prisma.SortOrder.asc,
+ lastActive: Prisma.SortOrder.desc,
},
},
projects: {
@@ -884,56 +972,53 @@ export class WorkspaceService {
await Promise.all(promises);
}
- async migrateWorkspacesDefaultDtos(workspaces: Workspace[]) {
- const promises = workspaces.map(async (workspace) => {
- const workspaceUser = workspace.users[0];
+ async migrateWorkspacesDefaultDtos(workspace: Workspace, user?: User) {
+ const workspaceUser = user ?? workspace.users[0];
- for (const project of workspace.projects) {
- const resources = project.resources;
- let hasChanges = false;
+ for (const project of workspace.projects) {
+ const resources = project.resources;
+ let hasChanges = false;
- hasChanges = await this.createResourceCustomDtos(
- resources,
- workspaceUser
- );
+ hasChanges = await this.createResourceCustomDtos(
+ resources,
+ workspaceUser
+ );
- if (hasChanges) {
- try {
- await this.projectService.commit(
- {
- data: {
- message:
- "this is automatic commit to update custom actions and create default DTOs",
- project: {
- connect: {
- id: project.id,
- },
+ if (hasChanges) {
+ try {
+ await this.projectService.commit(
+ {
+ data: {
+ message:
+ "this is automatic commit to update custom actions and create default DTOs",
+ project: {
+ connect: {
+ id: project.id,
},
- user: {
- connect: {
- id: workspaceUser.id,
- },
+ },
+ user: {
+ connect: {
+ id: workspaceUser.id,
},
},
},
- workspaceUser,
- true // skip build
- );
- } catch (error) {
- this.logger.error(
- `Failed to run commit action, error: ${error} projectId: ${project.id}`
- );
- }
+ },
+ workspaceUser,
+ true // skip build
+ );
+ } catch (error) {
+ this.logger.error(
+ `Failed to run commit action, error: ${error} projectId: ${project.id}`
+ );
}
}
- const date = new Date();
- this.logger.info(
- `workspace create default dto's process complete, workspaceId: ${
- workspace.id
- }, time: ${date.toUTCString()}`
- );
- });
- await Promise.all(promises);
+ }
+ const date = new Date();
+ this.logger.info(
+ `workspace create default dto's process complete, workspaceId: ${
+ workspace.id
+ }, time: ${date.toUTCString()}`
+ );
}
async migrateWorkspace(workspace: Workspace, currentUser: User) {
@@ -1133,45 +1218,38 @@ export class WorkspaceService {
}
}
- async createEntityCustomDtos(entity: Entity, user: User): Promise {
+ private async createEntityCustomDtos(
+ entity: Entity,
+ user: User
+ ): Promise {
try {
- if (entity.name.trim() === "" || entity.name.trim() === null) return;
+ if (entity.name.trim() === "" || entity.name.trim() === null)
+ return false;
const { resourceId, id } = entity;
const entityModuleId =
await this.moduleService.getDefaultModuleIdForEntity(resourceId, id);
- const entityModule = await this.moduleService.findOne({
- where: {
- id: entityModuleId,
- },
- });
-
- if (!entityModule) {
- this.logger.warn(`entity module not found for entity: ${entity.name}`);
- return;
- }
-
- await this.moduleDtoService.createDefaultDtosForEntityModule(
- entity,
- entityModule,
- user
- );
+ const newModuleDtos =
+ await this.moduleDtoService.createDefaultDtosForEntityModule(
+ entity,
+ entityModuleId,
+ user
+ );
- const fields = (await this.prisma.entityField.findMany({
+ const relationFields = await this.prisma.entityField.findMany({
where: {
entityVersion: {
entityId: entity.id,
versionNumber: 0,
deleted: null,
},
+ dataType: { equals: EnumDataType.Lookup },
},
- })) as EntityField[];
+ });
- const relationFields = fields.filter(
- (e) => e.dataType === EnumDataType.Lookup
- );
+ const newRelatedModuleDtos: ModuleDto[] = [];
for (const field of relationFields) {
const properties = field.properties as unknown as types.Lookup;
@@ -1182,23 +1260,34 @@ export class WorkspaceService {
});
try {
- await this.moduleDtoService.createDefaultDtosForRelatedEntity(
- entity,
- field,
- relatedEntity,
- entityModule.id,
- user
+ newRelatedModuleDtos.push(
+ ...(await this.moduleDtoService.createDefaultDtosForRelatedEntity(
+ entity,
+ field,
+ relatedEntity,
+ entityModuleId,
+ user
+ ))
);
} catch (error) {
this.logger.error(`${error.message} entityId: ${entity.id}`);
- return;
+ return false;
}
}
- return true;
+ if (newModuleDtos.length > 0 || newRelatedModuleDtos.length > 0) {
+ return true;
+ }
} catch (error) {
+ if (error instanceof DefaultModuleForEntityNotFoundError) {
+ this.logger.warn(error.message);
+ return false;
+ }
+
this.logger.error(error);
return false;
}
+
+ return false;
}
async createEntityCustomActionsFix(
@@ -1287,34 +1376,29 @@ export class WorkspaceService {
return hasChanges;
}
- async createResourceCustomDtos(
+ private async createResourceCustomDtos(
resources: Resource[],
user: User
): Promise {
let hasChanges = false;
const promises = resources.map(async (resource) => {
- try {
- const resourceModuleDto = await this.prisma.block.findFirst({
- where: {
- blockType: EnumBlockType.ModuleDto,
- resourceId: resource.id,
- },
- });
- if (resourceModuleDto) return hasChanges;
- hasChanges = true;
-
- for (const entity of resource.entities) {
- await this.createEntityCustomDtos(entity, user);
+ for (const entity of resource.entities) {
+ const currentChanges = await this.createEntityCustomDtos(entity, user);
+ if (!hasChanges && currentChanges) {
+ hasChanges = currentChanges;
}
- } catch (error) {
+ }
+ });
+
+ const settledPromises = await Promise.allSettled(promises);
+ settledPromises.forEach((promise) => {
+ if (promise.status === "rejected") {
this.logger.error(
- `Failed to run createResourceCustomDtos, error: ${error} resource: ${resource.id}`
+ "Failed to run createResourceCustomDtos",
+ promise.reason
);
-
- return hasChanges;
}
});
- await Promise.allSettled(promises);
return hasChanges;
}
diff --git a/packages/amplication-server/src/enums/EnumDataType.ts b/packages/amplication-server/src/enums/EnumDataType.ts
index 9087150c62..f9ab53124a 100644
--- a/packages/amplication-server/src/enums/EnumDataType.ts
+++ b/packages/amplication-server/src/enums/EnumDataType.ts
@@ -19,6 +19,7 @@ export enum EnumDataType {
Username = "Username",
Password = "Password",
Json = "Json",
+ File = "File",
}
registerEnumType(EnumDataType, {
name: "EnumDataType",
diff --git a/packages/amplication-server/src/schema.graphql b/packages/amplication-server/src/schema.graphql
index a8c228b680..f7f57b8250 100644
--- a/packages/amplication-server/src/schema.graphql
+++ b/packages/amplication-server/src/schema.graphql
@@ -688,6 +688,7 @@ enum EnumDataType {
DateTime
DecimalNumber
Email
+ File
GeographicLocation
Id
Json
diff --git a/packages/data-service-generator/src/admin/entity/create-field-input.ts b/packages/data-service-generator/src/admin/entity/create-field-input.ts
index 1090375b23..34e1801891 100644
--- a/packages/data-service-generator/src/admin/entity/create-field-input.ts
+++ b/packages/data-service-generator/src/admin/entity/create-field-input.ts
@@ -91,6 +91,7 @@ const DATA_TYPE_TO_FIELD_INPUT: {
[EnumDataType.UpdatedAt]: (field) =>
jsxElement``,
[EnumDataType.Json]: null,
+ [EnumDataType.File]: null,
[EnumDataType.Roles]: (field) =>
jsxElement` = {}
+ ) => [
+ PrismaSchemaDSL.createScalarField(
+ field.name,
+ PrismaSchemaDSLTypes.ScalarType.Json,
+ false,
+ field.required,
+ field.unique,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ field.customAttributes
+ ),
+ ],
[EnumDataType.Lookup]: (
field: EntityField,
entity: Entity,