@memberjunction/ng-scheduling 5.30.0 → 5.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/scheduled-job-service.test.js +22 -7
- package/dist/__tests__/scheduled-job-service.test.js.map +1 -1
- package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.d.ts +2 -1
- package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.d.ts.map +1 -1
- package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.js +6 -6
- package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.js.map +1 -1
- package/dist/lib/services/scheduled-job.service.d.ts +7 -0
- package/dist/lib/services/scheduled-job.service.d.ts.map +1 -1
- package/dist/lib/services/scheduled-job.service.js +14 -4
- package/dist/lib/services/scheduled-job.service.js.map +1 -1
- package/package.json +8 -7
|
@@ -9,20 +9,35 @@ vi.mock('@angular/core', () => ({
|
|
|
9
9
|
const mockRunViewMethod = vi.fn();
|
|
10
10
|
// Shared mock for entity Load
|
|
11
11
|
const mockEntityLoad = vi.fn().mockResolvedValue(true);
|
|
12
|
-
vi.mock('@memberjunction/core', () =>
|
|
13
|
-
|
|
12
|
+
vi.mock('@memberjunction/core', () => {
|
|
13
|
+
const MockMetadata = function Metadata() {
|
|
14
14
|
return {
|
|
15
15
|
GetEntityObject: vi.fn().mockResolvedValue({
|
|
16
16
|
Load: mockEntityLoad,
|
|
17
17
|
ID: 'job-001',
|
|
18
18
|
Name: 'Test Job',
|
|
19
19
|
}),
|
|
20
|
+
Entities: [],
|
|
20
21
|
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
};
|
|
23
|
+
// Add static Provider for the migration's `?? Metadata.Provider` fallback in services.
|
|
24
|
+
// Use a getter for GetEntityObject so we don't reference top-level let/const variables
|
|
25
|
+
// at vi.mock factory hoist time.
|
|
26
|
+
MockMetadata.Provider = {
|
|
27
|
+
Entities: [],
|
|
28
|
+
GetEntityObject(..._args) {
|
|
29
|
+
return Promise.resolve({
|
|
30
|
+
Load: mockEntityLoad,
|
|
31
|
+
ID: 'job-001',
|
|
32
|
+
Name: 'Test Job',
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const MockRunView = function RunView() { return { RunView: mockRunViewMethod }; };
|
|
37
|
+
// Multi-provider migration: services now use RunView.FromMetadataProvider(...) instead of `new RunView()`.
|
|
38
|
+
MockRunView.FromMetadataProvider = (_p) => ({ RunView: mockRunViewMethod });
|
|
39
|
+
return { Metadata: MockMetadata, RunView: MockRunView };
|
|
40
|
+
});
|
|
26
41
|
vi.mock('@memberjunction/core-entities', () => ({
|
|
27
42
|
MJScheduledJobEntity: class {
|
|
28
43
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job-service.test.js","sourceRoot":"","sources":["../../src/__tests__/scheduled-job-service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, vi, beforeEach } from 'vitest';\n\n/**\n * Tests for ScheduledJobService caching, loading, and delegation logic.\n */\n\nvi.mock('@angular/core', () => ({\n Injectable: () => (target: Function) => target,\n}));\n\n// Shared mock for RunView.RunView method — we reassign per test\nconst mockRunViewMethod = vi.fn();\n// Shared mock for entity Load\nconst mockEntityLoad = vi.fn().mockResolvedValue(true);\n\nvi.mock('@memberjunction/core', () => ({\n Metadata: function Metadata() {\n return {\n GetEntityObject: vi.fn().mockResolvedValue({\n Load: mockEntityLoad,\n ID: 'job-001',\n Name: 'Test Job',\n }),\n };\n },\n RunView: function RunView() {\n return { RunView: mockRunViewMethod };\n },\n}));\n\nvi.mock('@memberjunction/core-entities', () => ({\n MJScheduledJobEntity: class {},\n MJScheduledJobTypeEntity: class {},\n MJScheduledJobRunEntity: class {},\n}));\n\nimport { ScheduledJobService } from '../lib/services/scheduled-job.service';\n\ndescribe('ScheduledJobService', () => {\n let service: ScheduledJobService;\n\n beforeEach(() => {\n vi.clearAllMocks();\n mockEntityLoad.mockResolvedValue(true);\n service = new ScheduledJobService();\n });\n\n describe('initial state', () => {\n it('should have empty JobTypes initially', () => {\n expect(service.JobTypes).toEqual([]);\n });\n });\n\n describe('LoadJobTypes', () => {\n it('should load job types from RunView on first call', async () => {\n const mockTypes = [{ ID: 't1', Name: 'Type A' }, { ID: 't2', Name: 'Type B' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockTypes });\n\n const result = await service.LoadJobTypes();\n expect(result).toEqual(mockTypes);\n expect(service.JobTypes).toEqual(mockTypes);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(1);\n });\n\n it('should return cached data on subsequent calls without re-fetching', async () => {\n const mockTypes = [{ ID: 't1', Name: 'Type A' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockTypes });\n\n await service.LoadJobTypes();\n const result2 = await service.LoadJobTypes();\n expect(result2).toEqual(mockTypes);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(1);\n });\n\n it('should deduplicate concurrent calls (promise sharing)', async () => {\n const mockTypes = [{ ID: 't1', Name: 'Type A' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockTypes });\n\n const [r1, r2] = await Promise.all([service.LoadJobTypes(), service.LoadJobTypes()]);\n expect(r1).toEqual(mockTypes);\n expect(r2).toEqual(mockTypes);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(1);\n });\n\n it('should return empty array when RunView fails', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: false, Results: [] });\n const result = await service.LoadJobTypes();\n expect(result).toEqual([]);\n });\n\n it('should call RunView with correct params', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: [] });\n await service.LoadJobTypes();\n expect(mockRunViewMethod).toHaveBeenCalledWith(expect.objectContaining({\n EntityName: 'MJ: Scheduled Job Types',\n OrderBy: 'Name',\n ResultType: 'entity_object',\n }));\n });\n });\n\n describe('ClearCache', () => {\n it('should clear cached job types', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: [{ ID: 't1' }] });\n await service.LoadJobTypes();\n expect(service.JobTypes).toHaveLength(1);\n\n service.ClearCache();\n expect(service.JobTypes).toEqual([]);\n });\n\n it('should force re-fetch after clear', async () => {\n mockRunViewMethod\n .mockResolvedValueOnce({ Success: true, Results: [{ ID: 't1' }] })\n .mockResolvedValueOnce({ Success: true, Results: [{ ID: 't1' }, { ID: 't2' }] });\n\n await service.LoadJobTypes();\n service.ClearCache();\n await service.LoadJobTypes();\n\n expect(service.JobTypes).toHaveLength(2);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(2);\n });\n });\n\n describe('LoadJob', () => {\n it('should load a single job by ID', async () => {\n const result = await service.LoadJob('job-001');\n expect(result).not.toBeNull();\n });\n\n it('should return null when job load fails', async () => {\n mockEntityLoad.mockResolvedValueOnce(false);\n const result = await service.LoadJob('nonexistent');\n expect(result).toBeNull();\n });\n });\n\n describe('LoadJobRuns', () => {\n it('should load runs with correct filter and ordering', async () => {\n const mockRuns = [{ ID: 'run-1' }, { ID: 'run-2' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockRuns });\n\n const result = await service.LoadJobRuns('job-001');\n expect(result).toEqual(mockRuns);\n expect(mockRunViewMethod).toHaveBeenCalledWith(expect.objectContaining({\n EntityName: 'MJ: Scheduled Job Runs',\n ExtraFilter: \"ScheduledJobID='job-001'\",\n OrderBy: 'StartedAt DESC',\n MaxRows: 10,\n }));\n });\n\n it('should respect custom maxRows', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: [] });\n await service.LoadJobRuns('job-001', 25);\n expect(mockRunViewMethod).toHaveBeenCalledWith(expect.objectContaining({ MaxRows: 25 }));\n });\n\n it('should return empty array on failure', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: false, Results: [] });\n const result = await service.LoadJobRuns('job-001');\n expect(result).toEqual([]);\n });\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"scheduled-job-service.test.js","sourceRoot":"","sources":["../../src/__tests__/scheduled-job-service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, vi, beforeEach } from 'vitest';\n\n/**\n * Tests for ScheduledJobService caching, loading, and delegation logic.\n */\n\nvi.mock('@angular/core', () => ({\n Injectable: () => (target: Function) => target,\n}));\n\n// Shared mock for RunView.RunView method — we reassign per test\nconst mockRunViewMethod = vi.fn();\n// Shared mock for entity Load\nconst mockEntityLoad = vi.fn().mockResolvedValue(true);\n\nvi.mock('@memberjunction/core', () => {\n const MockMetadata: any = function Metadata() {\n return {\n GetEntityObject: vi.fn().mockResolvedValue({\n Load: mockEntityLoad,\n ID: 'job-001',\n Name: 'Test Job',\n }),\n Entities: [],\n };\n };\n // Add static Provider for the migration's `?? Metadata.Provider` fallback in services.\n // Use a getter for GetEntityObject so we don't reference top-level let/const variables\n // at vi.mock factory hoist time.\n MockMetadata.Provider = {\n Entities: [],\n GetEntityObject(..._args: unknown[]) {\n return Promise.resolve({\n Load: mockEntityLoad,\n ID: 'job-001',\n Name: 'Test Job',\n });\n },\n };\n const MockRunView: any = function RunView() { return { RunView: mockRunViewMethod }; };\n // Multi-provider migration: services now use RunView.FromMetadataProvider(...) instead of `new RunView()`.\n MockRunView.FromMetadataProvider = (_p: unknown) => ({ RunView: mockRunViewMethod });\n return { Metadata: MockMetadata, RunView: MockRunView };\n});\n\nvi.mock('@memberjunction/core-entities', () => ({\n MJScheduledJobEntity: class {},\n MJScheduledJobTypeEntity: class {},\n MJScheduledJobRunEntity: class {},\n}));\n\nimport { ScheduledJobService } from '../lib/services/scheduled-job.service';\n\ndescribe('ScheduledJobService', () => {\n let service: ScheduledJobService;\n\n beforeEach(() => {\n vi.clearAllMocks();\n mockEntityLoad.mockResolvedValue(true);\n service = new ScheduledJobService();\n });\n\n describe('initial state', () => {\n it('should have empty JobTypes initially', () => {\n expect(service.JobTypes).toEqual([]);\n });\n });\n\n describe('LoadJobTypes', () => {\n it('should load job types from RunView on first call', async () => {\n const mockTypes = [{ ID: 't1', Name: 'Type A' }, { ID: 't2', Name: 'Type B' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockTypes });\n\n const result = await service.LoadJobTypes();\n expect(result).toEqual(mockTypes);\n expect(service.JobTypes).toEqual(mockTypes);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(1);\n });\n\n it('should return cached data on subsequent calls without re-fetching', async () => {\n const mockTypes = [{ ID: 't1', Name: 'Type A' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockTypes });\n\n await service.LoadJobTypes();\n const result2 = await service.LoadJobTypes();\n expect(result2).toEqual(mockTypes);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(1);\n });\n\n it('should deduplicate concurrent calls (promise sharing)', async () => {\n const mockTypes = [{ ID: 't1', Name: 'Type A' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockTypes });\n\n const [r1, r2] = await Promise.all([service.LoadJobTypes(), service.LoadJobTypes()]);\n expect(r1).toEqual(mockTypes);\n expect(r2).toEqual(mockTypes);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(1);\n });\n\n it('should return empty array when RunView fails', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: false, Results: [] });\n const result = await service.LoadJobTypes();\n expect(result).toEqual([]);\n });\n\n it('should call RunView with correct params', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: [] });\n await service.LoadJobTypes();\n expect(mockRunViewMethod).toHaveBeenCalledWith(expect.objectContaining({\n EntityName: 'MJ: Scheduled Job Types',\n OrderBy: 'Name',\n ResultType: 'entity_object',\n }));\n });\n });\n\n describe('ClearCache', () => {\n it('should clear cached job types', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: [{ ID: 't1' }] });\n await service.LoadJobTypes();\n expect(service.JobTypes).toHaveLength(1);\n\n service.ClearCache();\n expect(service.JobTypes).toEqual([]);\n });\n\n it('should force re-fetch after clear', async () => {\n mockRunViewMethod\n .mockResolvedValueOnce({ Success: true, Results: [{ ID: 't1' }] })\n .mockResolvedValueOnce({ Success: true, Results: [{ ID: 't1' }, { ID: 't2' }] });\n\n await service.LoadJobTypes();\n service.ClearCache();\n await service.LoadJobTypes();\n\n expect(service.JobTypes).toHaveLength(2);\n expect(mockRunViewMethod).toHaveBeenCalledTimes(2);\n });\n });\n\n describe('LoadJob', () => {\n it('should load a single job by ID', async () => {\n const result = await service.LoadJob('job-001');\n expect(result).not.toBeNull();\n });\n\n it('should return null when job load fails', async () => {\n mockEntityLoad.mockResolvedValueOnce(false);\n const result = await service.LoadJob('nonexistent');\n expect(result).toBeNull();\n });\n });\n\n describe('LoadJobRuns', () => {\n it('should load runs with correct filter and ordering', async () => {\n const mockRuns = [{ ID: 'run-1' }, { ID: 'run-2' }];\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: mockRuns });\n\n const result = await service.LoadJobRuns('job-001');\n expect(result).toEqual(mockRuns);\n expect(mockRunViewMethod).toHaveBeenCalledWith(expect.objectContaining({\n EntityName: 'MJ: Scheduled Job Runs',\n ExtraFilter: \"ScheduledJobID='job-001'\",\n OrderBy: 'StartedAt DESC',\n MaxRows: 10,\n }));\n });\n\n it('should respect custom maxRows', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: true, Results: [] });\n await service.LoadJobRuns('job-001', 25);\n expect(mockRunViewMethod).toHaveBeenCalledWith(expect.objectContaining({ MaxRows: 25 }));\n });\n\n it('should return empty array on failure', async () => {\n mockRunViewMethod.mockResolvedValue({ Success: false, Results: [] });\n const result = await service.LoadJobRuns('job-001');\n expect(result).toEqual([]);\n });\n });\n});\n"]}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EventEmitter, OnInit } from '@angular/core';
|
|
2
2
|
import { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';
|
|
3
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
|
-
export declare class ScheduledJobEditorComponent implements OnInit {
|
|
5
|
+
export declare class ScheduledJobEditorComponent extends BaseAngularComponent implements OnInit {
|
|
5
6
|
private cdr;
|
|
6
7
|
private scheduledJobService;
|
|
7
8
|
private _scheduledJobID;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job-editor.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGZ,MAAM,EAET,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduled-job-editor.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGZ,MAAM,EAET,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAsBrE,qBAOa,2BAA4B,SAAQ,oBAAqB,YAAW,MAAM;IACnF,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,mBAAmB,CAA+B;IAG1D,OAAO,CAAC,eAAe,CAAuB;IAE9C,IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAOtC;IACD,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAEQ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,WAAW,UAAS;IAGnB,KAAK,qCAA4C;IACjD,OAAO,uBAA8B;IACrC,SAAS,qBAA4B;IAGxC,SAAS,UAAS;IAClB,QAAQ,UAAS;IACjB,KAAK,UAAS;IACd,iBAAiB,UAAS;IAE1B,GAAG,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACxC,QAAQ,EAAE,wBAAwB,EAAE,CAAM;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAoB;IAGvC,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,iBAAiB,SAAM;IACvB,cAAc,SAAM;IACpB,QAAQ,SAAS;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAY;IAC5E,eAAe,SAAU;IACzB,aAAa,SAAM;IACnB,eAAe,UAAS;IACxB,eAAe,UAAQ;IAGvB,SAAS,SAAK;IACd,WAAW,SAAK;IAChB,UAAU,SAAK;IAEtB,SAAgB,aAAa,WAA+C;IAC5E,SAAgB,kBAAkB,WAAmC;IAErE,OAAO,CAAC,aAAa,CAAS;IAE9B,IAAW,OAAO,IAAI,OAAO,CAI5B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B,aAAa,IAAI,IAAI;IAKrB,YAAY,IAAI,IAAI;IAKd,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBhC,MAAM,IAAI,IAAI;YAMP,eAAe;YAQf,eAAe;IAW7B,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,iBAAiB;YAaX,iBAAiB;YAUjB,YAAY;yCAlOjB,2BAA2B;2CAA3B,2BAA2B;CAwOvC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, inject, } from '@angular/core';
|
|
2
|
-
import { Metadata } from '@memberjunction/core';
|
|
3
2
|
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
3
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
4
4
|
import { ScheduledJobService } from '../../services/scheduled-job.service';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
import * as i1 from "@angular/forms";
|
|
@@ -23,7 +23,7 @@ const COMMON_TIMEZONES = [
|
|
|
23
23
|
'Asia/Kolkata',
|
|
24
24
|
'Australia/Sydney',
|
|
25
25
|
];
|
|
26
|
-
export class ScheduledJobEditorComponent {
|
|
26
|
+
export class ScheduledJobEditorComponent extends BaseAngularComponent {
|
|
27
27
|
cdr = inject(ChangeDetectorRef);
|
|
28
28
|
scheduledJobService = inject(ScheduledJobService);
|
|
29
29
|
// ── Inputs ────────────────────────────────────────────────
|
|
@@ -214,8 +214,8 @@ export class ScheduledJobEditorComponent {
|
|
|
214
214
|
if (this.Job && !this.IsNew) {
|
|
215
215
|
return this.Job;
|
|
216
216
|
}
|
|
217
|
-
const md =
|
|
218
|
-
const job = await md.GetEntityObject('MJ: Scheduled Jobs');
|
|
217
|
+
const md = this.ProviderToUse;
|
|
218
|
+
const job = await md.GetEntityObject('MJ: Scheduled Jobs', md.CurrentUser);
|
|
219
219
|
job.NewRecord();
|
|
220
220
|
return job;
|
|
221
221
|
}
|
|
@@ -225,8 +225,8 @@ export class ScheduledJobEditorComponent {
|
|
|
225
225
|
this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;
|
|
226
226
|
this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;
|
|
227
227
|
}
|
|
228
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, deps:
|
|
229
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobEditorComponent, isStandalone: false, selector: "mj-scheduled-job-editor", inputs: { ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Saved: "Saved", Deleted: "Deleted", Cancelled: "Cancelled" }, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 16px;\n background: #fff;\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: #334155;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: #0076b6;\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n margin-bottom: 4px;\n}\n\n.required {\n color: #ef4444;\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n font-size: 13px;\n color: #1e293b;\n background: #fff;\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: #0076b6;\n box-shadow: 0 0 0 2px rgba(0, 118, 182, 0.15);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: #94a3b8;\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #334155;\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: #334155;\n}\n\n.stat-value.success {\n color: #10b981;\n}\n\n.stat-value.error {\n color: #ef4444;\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: #94a3b8;\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid #e2e8f0;\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: #0076b6;\n color: #fff;\n}\n\n.btn-primary:hover:not(:disabled) {\n background: #005a8c;\n}\n\n.btn-secondary {\n background: #f1f5f9;\n color: #334155;\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: #e2e8f0;\n}\n\n.btn-danger {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.btn-danger:hover:not(:disabled) {\n background: #fecaca;\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: #fff;\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: #334155;\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
228
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
229
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobEditorComponent, isStandalone: false, selector: "mj-scheduled-job-editor", inputs: { ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Saved: "Saved", Deleted: "Deleted", Cancelled: "Cancelled" }, usesInheritance: true, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 16px;\n background: #fff;\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: #334155;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: #0076b6;\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n margin-bottom: 4px;\n}\n\n.required {\n color: #ef4444;\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n font-size: 13px;\n color: #1e293b;\n background: #fff;\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: #0076b6;\n box-shadow: 0 0 0 2px rgba(0, 118, 182, 0.15);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: #94a3b8;\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #334155;\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: #334155;\n}\n\n.stat-value.success {\n color: #10b981;\n}\n\n.stat-value.error {\n color: #ef4444;\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: #94a3b8;\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid #e2e8f0;\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: #0076b6;\n color: #fff;\n}\n\n.btn-primary:hover:not(:disabled) {\n background: #005a8c;\n}\n\n.btn-secondary {\n background: #f1f5f9;\n color: #334155;\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: #e2e8f0;\n}\n\n.btn-danger {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.btn-danger:hover:not(:disabled) {\n background: #fecaca;\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: #fff;\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: #334155;\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
230
230
|
}
|
|
231
231
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, decorators: [{
|
|
232
232
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job-editor.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts","../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n OnInit,\n inject,\n} from '@angular/core';\nimport { Metadata } from '@memberjunction/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n/** IANA timezone list subset for the dropdown */\nconst COMMON_TIMEZONES = [\n 'UTC',\n 'America/New_York',\n 'America/Chicago',\n 'America/Denver',\n 'America/Los_Angeles',\n 'America/Phoenix',\n 'America/Anchorage',\n 'Pacific/Honolulu',\n 'Europe/London',\n 'Europe/Paris',\n 'Europe/Berlin',\n 'Asia/Tokyo',\n 'Asia/Shanghai',\n 'Asia/Kolkata',\n 'Australia/Sydney',\n];\n\n@Component({\n selector: 'mj-scheduled-job-editor',\n standalone: false,\n templateUrl: './scheduled-job-editor.component.html',\n styleUrls: ['./scheduled-job-editor.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobEditorComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\n // ── Inputs ────────────────────────────────────────────────\n private _scheduledJobID: string | null = null;\n\n @Input()\n set ScheduledJobID(value: string | null) {\n if (value !== this._scheduledJobID) {\n this._scheduledJobID = value;\n if (this.isInitialized) {\n this.loadOrCreateJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n // ── Outputs ───────────────────────────────────────────────\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n @Output() Cancelled = new EventEmitter<void>();\n\n // ── State ─────────────────────────────────────────────────\n public IsLoading = false;\n public IsSaving = false;\n public IsNew = false;\n public ShowDeleteConfirm = false;\n\n public Job: MJScheduledJobEntity | null = null;\n public JobTypes: MJScheduledJobTypeEntity[] = [];\n public Timezones: string[] = COMMON_TIMEZONES;\n\n // Form fields\n public Name = '';\n public Description = '';\n public SelectedJobTypeID = '';\n public CronExpression = '';\n public Timezone = 'UTC';\n public Status: 'Active' | 'Disabled' | 'Expired' | 'Paused' | 'Pending' = 'Active';\n public ConcurrencyMode = 'Skip';\n public Configuration = '';\n public NotifyOnSuccess = false;\n public NotifyOnFailure = true;\n\n // Stats (edit mode)\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n\n public readonly StatusOptions = ['Pending', 'Active', 'Paused', 'Disabled'];\n public readonly ConcurrencyOptions = ['Skip', 'Queue', 'Concurrent'];\n\n private isInitialized = false;\n\n public get CanSave(): boolean {\n return this.Name.trim().length > 0\n && this.SelectedJobTypeID.length > 0\n && this.CronExpression.trim().length > 0;\n }\n\n async ngOnInit(): Promise<void> {\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.JobTypes = await this.scheduledJobService.LoadJobTypes();\n await this.loadOrCreateJob();\n\n this.isInitialized = true;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n public async Save(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const job = await this.getOrCreateEntity();\n this.applyFormToEntity(job);\n\n const saved = await job.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(\n this.IsNew ? 'Scheduled job created successfully' : 'Scheduled job updated successfully',\n 'success', 3000\n );\n this.Saved.emit(job);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification(\n `Failed to save: ${job.LatestResult?.Message ?? 'Unknown error'}`,\n 'error', 5000\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error saving: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public ConfirmDelete(): void {\n this.ShowDeleteConfirm = true;\n this.cdr.markForCheck();\n }\n\n public CancelDelete(): void {\n this.ShowDeleteConfirm = false;\n this.cdr.markForCheck();\n }\n\n public async DeleteJob(): Promise<void> {\n if (!this.Job || this.IsNew) return;\n\n this.ShowDeleteConfirm = false;\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const jobID = this.Job.ID;\n const deleted = await this.Job.Delete();\n if (deleted) {\n MJNotificationService.Instance.CreateSimpleNotification('Scheduled job deleted', 'success', 3000);\n this.Deleted.emit(jobID);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification('Failed to delete job', 'error', 5000);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error deleting: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n // ── Private ───────────────────────────────────────────────\n\n private async loadOrCreateJob(): Promise<void> {\n if (this._scheduledJobID) {\n await this.loadExistingJob(this._scheduledJobID);\n } else {\n this.initNewJob();\n }\n }\n\n private async loadExistingJob(jobID: string): Promise<void> {\n this.IsNew = false;\n const job = await this.scheduledJobService.LoadJob(jobID);\n if (job) {\n this.Job = job;\n this.populateFormFromEntity(job);\n await this.loadRunStats(jobID);\n }\n this.cdr.markForCheck();\n }\n\n private initNewJob(): void {\n this.IsNew = true;\n this.Job = null;\n this.Name = '';\n this.Description = '';\n this.SelectedJobTypeID = this.JobTypeID ?? '';\n this.CronExpression = '';\n this.Timezone = 'UTC';\n this.Status = 'Active';\n this.ConcurrencyMode = 'Skip';\n this.Configuration = this.DefaultConfiguration ?? '';\n this.NotifyOnSuccess = false;\n this.NotifyOnFailure = true;\n this.TotalRuns = 0;\n this.SuccessRuns = 0;\n this.FailedRuns = 0;\n this.cdr.markForCheck();\n }\n\n private populateFormFromEntity(job: MJScheduledJobEntity): void {\n this.Name = job.Name ?? '';\n this.Description = job.Description ?? '';\n this.SelectedJobTypeID = job.JobTypeID ?? '';\n this.CronExpression = job.CronExpression ?? '';\n this.Timezone = job.Timezone ?? 'UTC';\n this.Status = job.Status ?? 'Active';\n this.ConcurrencyMode = job.Get('ConcurrencyMode') as string ?? 'Skip';\n this.Configuration = job.Configuration ?? '';\n this.NotifyOnSuccess = job.Get('NotifyOnSuccess') as boolean ?? false;\n this.NotifyOnFailure = job.Get('NotifyOnFailure') as boolean ?? true;\n }\n\n private applyFormToEntity(job: MJScheduledJobEntity): void {\n job.Name = this.Name.trim();\n job.Description = this.Description.trim();\n job.JobTypeID = this.SelectedJobTypeID;\n job.CronExpression = this.CronExpression.trim();\n job.Timezone = this.Timezone;\n job.Status = this.Status;\n job.Set('ConcurrencyMode', this.ConcurrencyMode);\n job.Configuration = this.Configuration;\n job.Set('NotifyOnSuccess', this.NotifyOnSuccess);\n job.Set('NotifyOnFailure', this.NotifyOnFailure);\n }\n\n private async getOrCreateEntity(): Promise<MJScheduledJobEntity> {\n if (this.Job && !this.IsNew) {\n return this.Job;\n }\n const md = new Metadata();\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs');\n job.NewRecord();\n return job;\n }\n\n private async loadRunStats(jobID: string): Promise<void> {\n const runs = await this.scheduledJobService.LoadJobRuns(jobID, 100);\n this.TotalRuns = runs.length;\n this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;\n this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;\n }\n}\n","@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scheduled-job-editor.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts","../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n OnInit,\n inject,\n} from '@angular/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n/** IANA timezone list subset for the dropdown */\nconst COMMON_TIMEZONES = [\n 'UTC',\n 'America/New_York',\n 'America/Chicago',\n 'America/Denver',\n 'America/Los_Angeles',\n 'America/Phoenix',\n 'America/Anchorage',\n 'Pacific/Honolulu',\n 'Europe/London',\n 'Europe/Paris',\n 'Europe/Berlin',\n 'Asia/Tokyo',\n 'Asia/Shanghai',\n 'Asia/Kolkata',\n 'Australia/Sydney',\n];\n\n@Component({\n selector: 'mj-scheduled-job-editor',\n standalone: false,\n templateUrl: './scheduled-job-editor.component.html',\n styleUrls: ['./scheduled-job-editor.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobEditorComponent extends BaseAngularComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\n // ── Inputs ────────────────────────────────────────────────\n private _scheduledJobID: string | null = null;\n\n @Input()\n set ScheduledJobID(value: string | null) {\n if (value !== this._scheduledJobID) {\n this._scheduledJobID = value;\n if (this.isInitialized) {\n this.loadOrCreateJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n // ── Outputs ───────────────────────────────────────────────\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n @Output() Cancelled = new EventEmitter<void>();\n\n // ── State ─────────────────────────────────────────────────\n public IsLoading = false;\n public IsSaving = false;\n public IsNew = false;\n public ShowDeleteConfirm = false;\n\n public Job: MJScheduledJobEntity | null = null;\n public JobTypes: MJScheduledJobTypeEntity[] = [];\n public Timezones: string[] = COMMON_TIMEZONES;\n\n // Form fields\n public Name = '';\n public Description = '';\n public SelectedJobTypeID = '';\n public CronExpression = '';\n public Timezone = 'UTC';\n public Status: 'Active' | 'Disabled' | 'Expired' | 'Paused' | 'Pending' = 'Active';\n public ConcurrencyMode = 'Skip';\n public Configuration = '';\n public NotifyOnSuccess = false;\n public NotifyOnFailure = true;\n\n // Stats (edit mode)\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n\n public readonly StatusOptions = ['Pending', 'Active', 'Paused', 'Disabled'];\n public readonly ConcurrencyOptions = ['Skip', 'Queue', 'Concurrent'];\n\n private isInitialized = false;\n\n public get CanSave(): boolean {\n return this.Name.trim().length > 0\n && this.SelectedJobTypeID.length > 0\n && this.CronExpression.trim().length > 0;\n }\n\n async ngOnInit(): Promise<void> {\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.JobTypes = await this.scheduledJobService.LoadJobTypes();\n await this.loadOrCreateJob();\n\n this.isInitialized = true;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n public async Save(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const job = await this.getOrCreateEntity();\n this.applyFormToEntity(job);\n\n const saved = await job.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(\n this.IsNew ? 'Scheduled job created successfully' : 'Scheduled job updated successfully',\n 'success', 3000\n );\n this.Saved.emit(job);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification(\n `Failed to save: ${job.LatestResult?.Message ?? 'Unknown error'}`,\n 'error', 5000\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error saving: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public ConfirmDelete(): void {\n this.ShowDeleteConfirm = true;\n this.cdr.markForCheck();\n }\n\n public CancelDelete(): void {\n this.ShowDeleteConfirm = false;\n this.cdr.markForCheck();\n }\n\n public async DeleteJob(): Promise<void> {\n if (!this.Job || this.IsNew) return;\n\n this.ShowDeleteConfirm = false;\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const jobID = this.Job.ID;\n const deleted = await this.Job.Delete();\n if (deleted) {\n MJNotificationService.Instance.CreateSimpleNotification('Scheduled job deleted', 'success', 3000);\n this.Deleted.emit(jobID);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification('Failed to delete job', 'error', 5000);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error deleting: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n // ── Private ───────────────────────────────────────────────\n\n private async loadOrCreateJob(): Promise<void> {\n if (this._scheduledJobID) {\n await this.loadExistingJob(this._scheduledJobID);\n } else {\n this.initNewJob();\n }\n }\n\n private async loadExistingJob(jobID: string): Promise<void> {\n this.IsNew = false;\n const job = await this.scheduledJobService.LoadJob(jobID);\n if (job) {\n this.Job = job;\n this.populateFormFromEntity(job);\n await this.loadRunStats(jobID);\n }\n this.cdr.markForCheck();\n }\n\n private initNewJob(): void {\n this.IsNew = true;\n this.Job = null;\n this.Name = '';\n this.Description = '';\n this.SelectedJobTypeID = this.JobTypeID ?? '';\n this.CronExpression = '';\n this.Timezone = 'UTC';\n this.Status = 'Active';\n this.ConcurrencyMode = 'Skip';\n this.Configuration = this.DefaultConfiguration ?? '';\n this.NotifyOnSuccess = false;\n this.NotifyOnFailure = true;\n this.TotalRuns = 0;\n this.SuccessRuns = 0;\n this.FailedRuns = 0;\n this.cdr.markForCheck();\n }\n\n private populateFormFromEntity(job: MJScheduledJobEntity): void {\n this.Name = job.Name ?? '';\n this.Description = job.Description ?? '';\n this.SelectedJobTypeID = job.JobTypeID ?? '';\n this.CronExpression = job.CronExpression ?? '';\n this.Timezone = job.Timezone ?? 'UTC';\n this.Status = job.Status ?? 'Active';\n this.ConcurrencyMode = job.Get('ConcurrencyMode') as string ?? 'Skip';\n this.Configuration = job.Configuration ?? '';\n this.NotifyOnSuccess = job.Get('NotifyOnSuccess') as boolean ?? false;\n this.NotifyOnFailure = job.Get('NotifyOnFailure') as boolean ?? true;\n }\n\n private applyFormToEntity(job: MJScheduledJobEntity): void {\n job.Name = this.Name.trim();\n job.Description = this.Description.trim();\n job.JobTypeID = this.SelectedJobTypeID;\n job.CronExpression = this.CronExpression.trim();\n job.Timezone = this.Timezone;\n job.Status = this.Status;\n job.Set('ConcurrencyMode', this.ConcurrencyMode);\n job.Configuration = this.Configuration;\n job.Set('NotifyOnSuccess', this.NotifyOnSuccess);\n job.Set('NotifyOnFailure', this.NotifyOnFailure);\n }\n\n private async getOrCreateEntity(): Promise<MJScheduledJobEntity> {\n if (this.Job && !this.IsNew) {\n return this.Job;\n }\n const md = this.ProviderToUse;\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', md.CurrentUser);\n job.NewRecord();\n return job;\n }\n\n private async loadRunStats(jobID: string): Promise<void> {\n const runs = await this.scheduledJobService.LoadJobRuns(jobID, 100);\n this.TotalRuns = runs.length;\n this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;\n this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;\n }\n}\n","@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n"]}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { IMetadataProvider } from '@memberjunction/core';
|
|
1
2
|
import { MJScheduledJobEntity, MJScheduledJobTypeEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';
|
|
2
3
|
import * as i0 from "@angular/core";
|
|
3
4
|
/**
|
|
4
5
|
* Service for loading and caching scheduled job data.
|
|
5
6
|
* Safe to call multiple times — caches job types after first load.
|
|
7
|
+
*
|
|
8
|
+
* Multi-provider note: callers under a non-default provider should set
|
|
9
|
+
* `service.Provider = component.ProviderToUse` before invoking any methods.
|
|
6
10
|
*/
|
|
7
11
|
export declare class ScheduledJobService {
|
|
8
12
|
private _jobTypes;
|
|
9
13
|
private _jobTypesLoading;
|
|
14
|
+
private _provider;
|
|
15
|
+
get Provider(): IMetadataProvider;
|
|
16
|
+
set Provider(value: IMetadataProvider | null);
|
|
10
17
|
/** Cached list of all scheduled job types */
|
|
11
18
|
get JobTypes(): MJScheduledJobTypeEntity[];
|
|
12
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scheduled-job.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAqB,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;;AAExH;;;;;;GAMG;AACH,qBACa,mBAAmB;IAC5B,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,gBAAgB,CAAoD;IAC5E,OAAO,CAAC,SAAS,CAAkC;IAEnD,IAAW,QAAQ,IAAI,iBAAiB,CAEvC;IACD,IAAW,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAElD;IAED,6CAA6C;IAC7C,IAAW,QAAQ,IAAI,wBAAwB,EAAE,CAEhD;IAED;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAiBhE;;;OAGG;IACU,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAOzE;;OAEG;IACU,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAYjG,6DAA6D;IACtD,UAAU,IAAI,IAAI;YAKX,aAAa;yCAtElB,mBAAmB;6CAAnB,mBAAmB;CAgF/B"}
|
|
@@ -4,10 +4,20 @@ import * as i0 from "@angular/core";
|
|
|
4
4
|
/**
|
|
5
5
|
* Service for loading and caching scheduled job data.
|
|
6
6
|
* Safe to call multiple times — caches job types after first load.
|
|
7
|
+
*
|
|
8
|
+
* Multi-provider note: callers under a non-default provider should set
|
|
9
|
+
* `service.Provider = component.ProviderToUse` before invoking any methods.
|
|
7
10
|
*/
|
|
8
11
|
export class ScheduledJobService {
|
|
9
12
|
_jobTypes = [];
|
|
10
13
|
_jobTypesLoading = null;
|
|
14
|
+
_provider = null;
|
|
15
|
+
get Provider() {
|
|
16
|
+
return this._provider ?? Metadata.Provider;
|
|
17
|
+
}
|
|
18
|
+
set Provider(value) {
|
|
19
|
+
this._provider = value;
|
|
20
|
+
}
|
|
11
21
|
/** Cached list of all scheduled job types */
|
|
12
22
|
get JobTypes() {
|
|
13
23
|
return this._jobTypes;
|
|
@@ -37,8 +47,8 @@ export class ScheduledJobService {
|
|
|
37
47
|
* Returns null if not found or load fails.
|
|
38
48
|
*/
|
|
39
49
|
async LoadJob(jobID) {
|
|
40
|
-
const md =
|
|
41
|
-
const job = await md.GetEntityObject('MJ: Scheduled Jobs');
|
|
50
|
+
const md = this.Provider;
|
|
51
|
+
const job = await md.GetEntityObject('MJ: Scheduled Jobs', md.CurrentUser);
|
|
42
52
|
const loaded = await job.Load(jobID);
|
|
43
53
|
return loaded ? job : null;
|
|
44
54
|
}
|
|
@@ -46,7 +56,7 @@ export class ScheduledJobService {
|
|
|
46
56
|
* Load recent runs for a given job, ordered by most recent first.
|
|
47
57
|
*/
|
|
48
58
|
async LoadJobRuns(jobID, maxRows = 10) {
|
|
49
|
-
const rv =
|
|
59
|
+
const rv = RunView.FromMetadataProvider(this.Provider);
|
|
50
60
|
const result = await rv.RunView({
|
|
51
61
|
EntityName: 'MJ: Scheduled Job Runs',
|
|
52
62
|
ExtraFilter: `ScheduledJobID='${jobID}'`,
|
|
@@ -62,7 +72,7 @@ export class ScheduledJobService {
|
|
|
62
72
|
this._jobTypesLoading = null;
|
|
63
73
|
}
|
|
64
74
|
async fetchJobTypes() {
|
|
65
|
-
const rv =
|
|
75
|
+
const rv = RunView.FromMetadataProvider(this.Provider);
|
|
66
76
|
const result = await rv.RunView({
|
|
67
77
|
EntityName: 'MJ: Scheduled Job Types',
|
|
68
78
|
ExtraFilter: '',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job.service.js","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"scheduled-job.service.js","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { IMetadataProvider, Metadata, RunView } from '@memberjunction/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';\n\n/**\n * Service for loading and caching scheduled job data.\n * Safe to call multiple times — caches job types after first load.\n *\n * Multi-provider note: callers under a non-default provider should set\n * `service.Provider = component.ProviderToUse` before invoking any methods.\n */\n@Injectable({ providedIn: 'root' })\nexport class ScheduledJobService {\n private _jobTypes: MJScheduledJobTypeEntity[] = [];\n private _jobTypesLoading: Promise<MJScheduledJobTypeEntity[]> | null = null;\n private _provider: IMetadataProvider | null = null;\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n /** Cached list of all scheduled job types */\n public get JobTypes(): MJScheduledJobTypeEntity[] {\n return this._jobTypes;\n }\n\n /**\n * Load and cache all job types. Safe to call multiple times —\n * returns cached data after first successful load.\n */\n public async LoadJobTypes(): Promise<MJScheduledJobTypeEntity[]> {\n if (this._jobTypes.length > 0) {\n return this._jobTypes;\n }\n if (this._jobTypesLoading) {\n return this._jobTypesLoading;\n }\n\n this._jobTypesLoading = this.fetchJobTypes();\n try {\n this._jobTypes = await this._jobTypesLoading;\n return this._jobTypes;\n } finally {\n this._jobTypesLoading = null;\n }\n }\n\n /**\n * Load a single scheduled job by ID.\n * Returns null if not found or load fails.\n */\n public async LoadJob(jobID: string): Promise<MJScheduledJobEntity | null> {\n const md = this.Provider;\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', md.CurrentUser);\n const loaded = await job.Load(jobID);\n return loaded ? job : null;\n }\n\n /**\n * Load recent runs for a given job, ordered by most recent first.\n */\n public async LoadJobRuns(jobID: string, maxRows: number = 10): Promise<MJScheduledJobRunEntity[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const result = await rv.RunView<MJScheduledJobRunEntity>({\n EntityName: 'MJ: Scheduled Job Runs',\n ExtraFilter: `ScheduledJobID='${jobID}'`,\n OrderBy: 'StartedAt DESC',\n MaxRows: maxRows,\n ResultType: 'entity_object',\n });\n return result.Success ? result.Results : [];\n }\n\n /** Clear cached data so next LoadJobTypes call re-fetches */\n public ClearCache(): void {\n this._jobTypes = [];\n this._jobTypesLoading = null;\n }\n\n private async fetchJobTypes(): Promise<MJScheduledJobTypeEntity[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const result = await rv.RunView<MJScheduledJobTypeEntity>({\n EntityName: 'MJ: Scheduled Job Types',\n ExtraFilter: '',\n OrderBy: 'Name',\n ResultType: 'entity_object',\n });\n return result.Success ? result.Results : [];\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-scheduling",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.31.0",
|
|
4
4
|
"description": "MemberJunction: Reusable Angular components for viewing and editing Scheduled Jobs - panels, slide-in, dialog, and service",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -31,12 +31,13 @@
|
|
|
31
31
|
"@angular/forms": "21.1.3"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@memberjunction/core": "5.
|
|
35
|
-
"@memberjunction/
|
|
36
|
-
"@memberjunction/
|
|
37
|
-
"@memberjunction/
|
|
38
|
-
"@memberjunction/ng-
|
|
39
|
-
"@memberjunction/ng-
|
|
34
|
+
"@memberjunction/core": "5.31.0",
|
|
35
|
+
"@memberjunction/ng-base-types": "5.31.0",
|
|
36
|
+
"@memberjunction/core-entities": "5.31.0",
|
|
37
|
+
"@memberjunction/global": "5.31.0",
|
|
38
|
+
"@memberjunction/ng-notifications": "5.31.0",
|
|
39
|
+
"@memberjunction/ng-shared-generic": "5.31.0",
|
|
40
|
+
"@memberjunction/ng-ui-components": "5.31.0",
|
|
40
41
|
"tslib": "^2.8.1",
|
|
41
42
|
"rxjs": "^7.8.2"
|
|
42
43
|
},
|