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

Add Jobs for Product Creation, Builds, and Publishing #1060

Open
wants to merge 44 commits into
base: feature/svelte
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d7ca5f4
Add jobs for product creation, build, and publish
FyreByrd Nov 4, 2024
0fbfa6e
Hook into jobs
FyreByrd Nov 4, 2024
1d09d2c
Add product creation to frontend
FyreByrd Oct 16, 2024
a4336ea
Delete Product from BuildEngine
FyreByrd Nov 4, 2024
f44b90a
Imitate correct build/publish failure output
FyreByrd Oct 18, 2024
d35b59b
Handle post-build artifact creation
FyreByrd Oct 18, 2024
7c6a22d
Fix database write for artifacts
FyreByrd Oct 18, 2024
d39dd03
Handle special artifact types in build
FyreByrd Oct 18, 2024
d0d7683
Update ProductPublications in publish
FyreByrd Oct 18, 2024
a287253
Move workflow creation call to products.create
FyreByrd Oct 23, 2024
d7ce614
Update Project DateActive
FyreByrd Oct 25, 2024
3a927c5
Fix product form closing
FyreByrd Oct 31, 2024
9469889
Handle no available product definitions
FyreByrd Oct 31, 2024
f0dc97a
Properly handle GOOGLE_PLAY_UPLOADED
FyreByrd Nov 6, 2024
da74e07
Add environment population functions from DWKit
FyreByrd Nov 7, 2024
7612fc3
Assign targets and env for build
FyreByrd Nov 7, 2024
e01a3fc
Assign targets and env for publish
FyreByrd Nov 7, 2024
34f6b81
Add PWA properties to workflow definitions
FyreByrd Nov 8, 2024
65605e6
Fix environment merging
FyreByrd Nov 8, 2024
28bccba
remove async from workflow send
FyreByrd Nov 8, 2024
83bf977
Add missing writes to WorkflowType
FyreByrd Nov 11, 2024
ed9915e
Fix getWorkflowParameters with null Properties
FyreByrd Nov 11, 2024
842fa03
Use transitions for Project.DateActive
FyreByrd Nov 18, 2024
5f960ea
Product icon in selector
FyreByrd Nov 20, 2024
311f58c
Remove unneeded TODO
FyreByrd Nov 20, 2024
9ec25d8
Investigate blank activity name at startup
FyreByrd Nov 20, 2024
766dd71
Preserve artifact history
FyreByrd Nov 21, 2024
abcc666
Rename WorkflowContextBase to WorkflowInstanceContext
FyreByrd Dec 6, 2024
7150d27
Fix product creation workflow options init
FyreByrd Dec 6, 2024
d02dc19
Create WorkflowInstance in database on create
FyreByrd Dec 6, 2024
e9b3765
Log artifacts in build
FyreByrd Dec 6, 2024
0b5cb5f
Fix lint errors in getWorkflowParameters
FyreByrd Dec 6, 2024
4b7492e
Fix null description bug
FyreByrd Dec 6, 2024
44c04b5
Fix check errors
FyreByrd Dec 6, 2024
e652364
Fix errors in publish job
FyreByrd Dec 12, 2024
2441afa
Add close button to modal
FyreByrd Dec 12, 2024
76410f3
Don't close modal when going back from store
FyreByrd Dec 12, 2024
6ff899f
Prevent cancel button from submitting form
FyreByrd Dec 12, 2024
b02e888
Fix styling in creation form
FyreByrd Dec 12, 2024
2891e11
Fix storeLanguage check in Product validation
FyreByrd Dec 12, 2024
47640b6
Remove store language from UI product creation
FyreByrd Dec 12, 2024
6aa48f0
Bump dev tsconfig for node-server to NodeNext
FyreByrd Dec 12, 2024
4052c39
Remove check for 'expired' status
FyreByrd Dec 12, 2024
17e93a5
Switch updateProjectDateActive back
FyreByrd Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions scripts/DB/default_workflow.sql
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ DO UPDATE SET
"Type" = excluded."Type",
"ProductType" = excluded."ProductType";

INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "ProductType") VALUES
(9, 'pwa_cloud', '1', 'SIL Default Workflow for Publishing PWA to Cloud', 'SIL_Default_AppBuilders_Pwa_Cloud', 'SIL_AppBuilders_Web_Flow', 3, 1, 3)
INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "Properties", "ProductType") VALUES
(9, 'pwa_cloud', '1', 'SIL Default Workflow for Publishing PWA to Cloud', 'SIL_Default_AppBuilders_Pwa_Cloud', 'SIL_AppBuilders_Web_Flow', 3, 1, '{ "build:targets" : "pwa" }', 3)
ON CONFLICT ("Id")
DO UPDATE SET
"Name" = excluded."Name",
Expand All @@ -112,8 +112,8 @@ DO UPDATE SET
"Type" = excluded."Type",
"ProductType" = excluded."ProductType";

INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "ProductType") VALUES
(10, 'pwa_cloud_rebuild', '1', 'SIL Default Workflow for Rebuilding PWA to Cloud', 'SIL_Default_AppBuilders_Pwa_Cloud_Rebuild', 'SIL_AppBuilders_Web_Flow', 3, 2, 3)
INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "Properties", "ProductType") VALUES
(10, 'pwa_cloud_rebuild', '1', 'SIL Default Workflow for Rebuilding PWA to Cloud', 'SIL_Default_AppBuilders_Pwa_Cloud_Rebuild', 'SIL_AppBuilders_Web_Flow', 3, 2, '{ "build:targets" : "pwa" }', 3)
ON CONFLICT ("Id")
DO UPDATE SET
"Name" = excluded."Name",
Expand Down Expand Up @@ -151,8 +151,8 @@ DO UPDATE SET
"Type" = excluded."Type",
"ProductType" = excluded."ProductType";

INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "Properties", "ProductType") VALUES
(13, 'asset_package', '1', 'SIL Default Workflow for Publishing Asset Packages', 'SIL_NoAdmin_AppBuilders_Android_S3', 'SIL_AppBuilders_AssetPackage_Flow', 2, 1, '{ "build:targets" : "asset-package" }', 2)
INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "ProductType") VALUES
(13, 'asset_package', '1', 'SIL Default Workflow for Publishing Asset Packages', 'SIL_NoAdmin_AppBuilders_Android_S3', 'SIL_AppBuilders_AssetPackage_Flow', 2, 1, 2)
ON CONFLICT ("Id")
DO UPDATE SET
"Name" = excluded."Name",
Expand All @@ -165,8 +165,8 @@ DO UPDATE SET
"Properties" = excluded."Properties",
"ProductType" = excluded."ProductType";

INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "Properties", "ProductType") VALUES
(14, 'asset_package_rebuild', '1', 'SIL Default Workflow for Rebuilding Asset Packages', 'SIL_Default_AppBuilders_Android_S3_Rebuild', 'SIL_AppBuilders_AssetPackage_Flow', 2, 2, '{ "build:targets" : "asset-package" }', 3)
INSERT INTO "WorkflowDefinitions" ("Id", "Name", "Enabled", "Description", "WorkflowScheme", "WorkflowBusinessFlow", "StoreTypeId", "Type", "ProductType") VALUES
(14, 'asset_package_rebuild', '1', 'SIL Default Workflow for Rebuilding Asset Packages', 'SIL_Default_AppBuilders_Android_S3_Rebuild', 'SIL_AppBuilders_AssetPackage_Flow', 2, 2, 3)
ON CONFLICT ("Id")
DO UPDATE SET
"Name" = excluded."Name",
Expand Down
24 changes: 24 additions & 0 deletions source/SIL.AppBuilder.Portal/common/bullmq/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,36 @@ import { Queue } from 'bullmq';
import type { Job } from './types.js';
import { QueueName } from './types.js';

/** Queue for Product Builds */
export const Builds = new Queue<Job>(QueueName.Builds, {
connection: {
host: process.env.NODE_ENV === 'development' ? 'localhost' : 'redis'
}
});
/** Queue for default recurring jobs such as the BuildEngine status check */
export const DefaultRecurring = new Queue<Job>(QueueName.DefaultRecurring, {
connection: {
host: process.env.NODE_ENV === 'development' ? 'localhost' : 'redis'
}
});
/** Queue for miscellaneous jobs such as Product and Project Creation */
export const Miscellaneous = new Queue<Job>(QueueName.Miscellaneous, {
connection: {
host: process.env.NODE_ENV === 'development' ? 'localhost' : 'redis'
}
});
/** Queue for Product Publishing */
export const Publishing = new Queue<Job>(QueueName.Publishing, {
connection: {
host: process.env.NODE_ENV === 'development' ? 'localhost' : 'redis'
}
});
/** Queue for jobs that poll BuildEngine, such as checking the status of a build */
export const RemotePolling = new Queue<Job>(QueueName.RemotePolling, {
connection: {
host: process.env.NODE_ENV === 'development' ? 'localhost' : 'redis'
}
});
/** Queue for operations on UserTasks */
export const UserTasks = new Queue<Job>(QueueName.UserTasks, {
connection: {
Expand Down
87 changes: 87 additions & 0 deletions source/SIL.AppBuilder.Portal/common/bullmq/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type { Channels } from '../build-engine-api/types.js';
import { RoleId } from '../public/prisma.js';

interface RetryOptions {
Expand All @@ -17,18 +18,97 @@ export const Retry5e5: RetryOptions = {
}
};

interface RepeatOptions {
readonly repeat: {
readonly pattern: string;
};
}
/** Repeat a job every minute */
export const RepeatEveryMinute: RepeatOptions = {
repeat: {
pattern: '*/1 * * * *' // every minute
}
};

export enum QueueName {
Builds = 'Builds',
DefaultRecurring = 'Default Recurring',
Miscellaneous = 'Miscellaneous',
Publishing = 'Publishing',
RemotePolling = 'Remote Polling',
UserTasks = 'User Tasks'
}

export enum JobType {
// Build Tasks
Build_Product = 'Build Product',
Build_Check = 'Check Product Build',
// Product Tasks
Product_Create = 'Create Product',
Product_Delete = 'Delete Product',
Product_GetVersionCode = 'Get VersionCode for Uploaded Product',
// Publishing Tasks
Publish_Product = 'Publish Product',
Publish_Check = 'Check Product Publish',
// System Tasks
System_CheckStatuses = 'Check System Statuses',
// UserTasks
UserTasks_Modify = 'Modify UserTasks'
}

export namespace Build {
export interface Product {
type: JobType.Build_Product;
productId: string;
targets: string;
environment: { [key: string]: string };
}
export interface Check {
type: JobType.Build_Check;
organizationId: number;
productId: string;
jobId: number;
buildId: number;
productBuildId: number;
}
}

export namespace Product {
export interface Create {
type: JobType.Product_Create;
productId: string;
}
export interface Delete {
type: JobType.Product_Delete;
organizationId: number;
workflowJobId: number;
}
export interface GetVersionCode {
type: JobType.Product_GetVersionCode;
productId: string;
}
}

export namespace Publish {
export interface Product {
type: JobType.Publish_Product;
productId: string;
channel: Channels;
targets: string;
environment: { [key: string]: string };
}

export interface Check {
type: JobType.Publish_Check;
organizationId: number;
productId: string;
jobId: number;
buildId: number;
releaseId: number;
publicationId: number;
}
}

export namespace System {
export interface CheckStatuses {
type: JobType.System_CheckStatuses;
Expand Down Expand Up @@ -75,6 +155,13 @@ export namespace UserTasks {
export type Job = JobTypeMap[keyof JobTypeMap];

export type JobTypeMap = {
[JobType.Build_Product]: Build.Product;
[JobType.Build_Check]: Build.Check;
[JobType.Product_Create]: Product.Create;
[JobType.Product_Delete]: Product.Delete;
[JobType.Product_GetVersionCode]: Product.GetVersionCode;
[JobType.Publish_Product]: Publish.Product;
[JobType.Publish_Check]: Publish.Check;
[JobType.System_CheckStatuses]: System.CheckStatuses;
[JobType.UserTasks_Modify]: UserTasks.Modify;
// Add more mappings here as needed
Expand Down
119 changes: 110 additions & 9 deletions source/SIL.AppBuilder.Portal/common/databaseProxy/Products.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { Prisma } from '@prisma/client';
import { Workflow } from 'sil.appbuilder.portal.common';
import { BullMQ, Queues } from '../index.js';
import prisma from '../prisma.js';
import { WorkflowType } from '../public/prisma.js';
import { update as projectUpdate } from './Projects.js';
import type { RequirePrimitive } from './utility.js';

export async function create(
Expand All @@ -10,7 +14,7 @@ export async function create(
productData.ProjectId,
productData.ProductDefinitionId,
productData.StoreId,
productData.StoreLanguageId
productData.StoreLanguageId ?? undefined
))
)
return false;
Expand All @@ -21,6 +25,36 @@ export async function create(
const res = await prisma.products.create({
data: productData
});

if (res) {
const flowDefinition = (
await prisma.productDefinitions.findUnique({
where: {
Id: productData.ProductDefinitionId
},
select: {
Workflow: {
select: {
Id: true,
Type: true,
ProductType: true,
WorkflowOptions: true
}
}
}
})
)?.Workflow;

if (flowDefinition?.Type === WorkflowType.Startup) {
await Workflow.create(res.Id, {
productType: flowDefinition.ProductType,
options: new Set(flowDefinition.WorkflowOptions)
});
updateProjectDateActive(productData.ProjectId);
}
await updateProjectDateActive(productData.ProjectId);
}

return res.Id;
} catch (e) {
return false;
Expand Down Expand Up @@ -55,16 +89,40 @@ export async function update(
},
data: productData
});
await updateProjectDateActive(projectId);
// TODO: Are there any other updates that need to be done?
} catch (e) {
return false;
}
return true;
}

function deleteProduct(productId: string) {
async function deleteProduct(productId: string) {
// Delete all userTasks for this product, and delete the product
return prisma.$transaction([
const product = await prisma.products.findUnique({
where: {
Id: productId
},
select: {
Project: {
select: {
Id: true,
OrganizationId: true
}
},
WorkflowJobId: true
}
});
await Queues.Miscellaneous.add(
`Delete Product #${productId} from BuildEngine`,
{
type: BullMQ.JobType.Product_Delete,
organizationId: product!.Project.OrganizationId,
workflowJobId: product!.WorkflowJobId
},
BullMQ.Retry5e5
);
const res = prisma.$transaction([
prisma.workflowInstances.deleteMany({
where: {
ProductId: productId
Expand All @@ -81,7 +139,8 @@ function deleteProduct(productId: string) {
}
})
]);
// TODO: delete from BuildEngine
await updateProjectDateActive(product!.Project.Id);
return res;
}
export { deleteProduct as delete };

Expand Down Expand Up @@ -140,7 +199,7 @@ async function validateProductBase(
Id: true,
// StoreLanguage must be allowed by Store, if the StoreLanguage is defined
StoreLanguages:
storeLanguageId === undefined || storeLanguageId === null
storeLanguageId === undefined
? undefined
: {
where: {
Expand All @@ -166,7 +225,6 @@ async function validateProductBase(
}
}
});

// 3. The store is allowed by the organization
return (
(project?.Organization.OrganizationStores.length ?? 0) > 0 &&
Expand All @@ -175,11 +233,54 @@ async function validateProductBase(
project?.Organization.OrganizationStores[0].Store.StoreType.Id &&
// 2. The project has a WorkflowProjectUrl
// handled by query
// 4. The language is allowed by the store
(storeLanguageId ??
// 4. The language, if specified, is allowed by the store
((storeLanguageId &&
(project?.Organization.OrganizationStores[0].Store.StoreType.StoreLanguages.length ?? 0) >
0) &&
0) ||
storeLanguageId === undefined) &&
// 5. The product type is allowed by the organization
(project?.Organization.OrganizationProductDefinitions.length ?? 0) > 0
);
}

async function updateProjectDateActive(projectId: number) {
const project = await prisma.projects.findUnique({
where: {
Id: projectId
},
select: {
Products: {
select: {
WorkflowInstance: {
select: {
Id: true
}
},
DateUpdated: true
}
},
DateActive: true
}
});

const projectDateActive = project.DateActive;

let dateActive = new Date(0);
project.Products.forEach((product) => {
if (product.WorkflowInstance) {
if (product.DateUpdated > dateActive) {
dateActive = product.DateUpdated;
}
}
});

if (dateActive > new Date(0)) {
project.DateActive = dateActive;
} else {
project.DateActive = null;
}

if (project.DateActive != projectDateActive) {
await projectUpdate(projectId, { DateActive: project.DateActive });
}
}
2 changes: 2 additions & 0 deletions source/SIL.AppBuilder.Portal/common/public/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export enum WorkflowType {
Rebuild,
Republish
}

export const WorkflowTypeString = ['', 'Startup', 'Rebuild', 'Republish'];
Loading
Loading