File size: 6,343 Bytes
b39afbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
 * Copyright (c) 2023 MERCENARIES.AI PTE. LTD.
 * All rights reserved.
 */

const script = {
  name: 'qa',

  exec: async function (ctx, payload) {
    try {
      const dbService = ctx.app.services.get('db');
      let workflowIds;

      if (Array.isArray(payload) && payload.length > 0) {
        workflowIds = payload;
      } else {
          const demoWorkflows = await dbService.find({ 'meta.template': true }, ['id']);
      
          if (!demoWorkflows || demoWorkflows.length === 0) {
              throw new Error('No recipes found with "meta.template=true" flag in the database')
          }
      
          workflowIds = demoWorkflows.map(doc => doc.id);
      }
    
      workflowIds = workflowIds.filter(id => id !== 'c0deba5e-417d-49df-96d3-8aeb8fc15402'); //exclude bugbear

      if (!workflowIds.length) throw new Error('No workflowIds provided');

      const wfIntegration = ctx.app.integrations.get('workflow');
      const user = await dbService.get(`user:${ctx.userId}`);

      ctx.app.events.onAny((event, value) => {
        if (event.startsWith('jobs.')) {
          console.log('Received jobs event:', event, value);
        }
      });

      const workflowPromises = workflowIds.map(async (workflowId) => {
        console.log(`Starting recipe with ID: ${workflowId}`);
        try {
          const workflowName = (await dbService.get(`wf:${workflowId}`))?.meta?.name || 'Unknown';
          const jobResult = await wfIntegration.startWorkflow(wfIntegration, workflowId, ctx.session, user, {});

          if (jobResult.jobId) {
            return await new Promise((resolve, reject) => {
              let hasFinished = false;
              const timeoutTime = 60000;
              let timeout;
              let startTime = Date.now();

              const startTimeout = () => {
                startTime = Date.now(); // Record start time when job_started_ event is received
            
                timeout = setTimeout(() => {
                  if (!hasFinished) {
                    const duration = Math.round((Date.now() - startTime) / 1000); // Calculate and round the duration here
                    console.log(`Timeout as no job_finished event received for recipe ID: ${workflowId}. Timeout Time: ${timeoutTime / 1000} seconds. Duration: ${duration} seconds`);
                    // eslint-disable-next-line prefer-promise-reject-errors
                    reject({
                      status: 'timeout',
                      workflowId,
                      workflowName,
                      jobId: jobResult.jobId,
                      error: `Recipe Timeout reached as no job_finished event received within ${timeoutTime / 1000} seconds`
                    });
                  }
                }, timeoutTime);
              };
              // Setting up listener for job_started_ event to start the timeout timer
              ctx.app.events.once(`jobs.job_started_${jobResult.jobId}`).then(startTimeout);

              ctx.app.events.once(`jobs.job_finished_${jobResult.jobId}`)
                .then((jobs) => {
                  clearTimeout(timeout);
                  let job;
                  if (Array.isArray(jobs) && jobs.length > 0) {
                    job = jobs[0];
                  } else {
                    job = jobs;
                  }
                  const endTime = new Date(); // Record end time
                  const duration = Math.round((endTime - startTime) / 1000); // Calculate the duration
                  const errorDetails = job.errors.length ? JSON.stringify(job.errors) : 'None';
                  console.log(`Workflow ${workflowId} finished with jobState: ${job._state}. Job details:`, job);
                  if (job._state === 'success') {
                    resolve({
                      status: 'success',
                      workflowId,
                      workflowName,
                      jobId: jobResult.jobId,
                      jobState: job._state,
                      duration
                    });
                  } else {
                    // eslint-disable-next-line prefer-promise-reject-errors
                    reject({
                      status: 'fail',
                      workflowId,
                      workflowName,
                      jobId: jobResult.jobId,
                      jobState: job._state,
                      error: errorDetails,
                      duration
                    });
                  }
                  hasFinished = true;
                });
            });
          } else {
            throw new Error('Failed to start a job for the workflow');
          }
        } catch (error) {
          console.error(`Error in recipe ${workflowId}:`, error);
          return await Promise.reject(error);
        }
      })

      const results = await Promise.allSettled(workflowPromises);

      const report = '## QA Report\n' + results.map(result => {
        if (result.status === 'fulfilled') {
          return `#### ✅ ${result.value.workflowName}\n - **Workflow ID**: ${result.value.workflowId}\n - **Job ID**: ${result.value.jobId}\n - **Status**: ${result.status} ${result.value.status} ${result.value.jobState}\n  - **Error**: ${result.value.error || 'None'}\n - **Duration**: ${result.value.duration} s`;
        } else {
          return `#### ❌ ${result.reason.workflowName}\n - **Workflow ID**: ${result.reason.workflowId}\n - **Job ID**: ${result.reason.jobId}\n - **Status**: ${result.status} ${result.reason.status} ${result.reason.jobState}\n  - **Error**: ${result.reason?.error || 'None'}\n - **Duration**: ${result.reason.duration} s`;
        }
      }).join('\n');

      void await ctx.app.blocks.runBlock(
        ctx,
        'omnitool.write_document',
        { text: report, fileName: 'qa_report.md', textType: 'text/markdown', storageType: 'Temporary' }
      );
      void await ctx.app.blocks.runBlock(
        ctx,
        'omnitool.chat_output',
        { text: 'QA Report has been generated. Please check your file manager for the QA report.'}
      );
    } catch (error) {
      console.error('Error in QA script:', error);
      await ctx.app.sendMessageToSession(ctx.session.sessionId, error.message, 'text/plain');
      throw new Error(error.message);
    }
  },
};

export default script