@mongoosejs/studio 0.3.0 → 0.3.1
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/backend/actions/ChatThread/createChatMessage.js +2 -0
- package/backend/actions/ChatThread/streamChatMessage.js +2 -0
- package/backend/actions/Dashboard/updateDashboard.js +2 -2
- package/backend/actions/Task/getTaskOverview.js +102 -0
- package/backend/actions/Task/getTasks.js +85 -45
- package/backend/actions/Task/index.js +1 -0
- package/frontend/public/app.js +309 -145
- package/frontend/public/tw.css +82 -0
- package/frontend/src/_util/dateRange.js +82 -0
- package/frontend/src/ace-editor/ace-editor.js +6 -0
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +11 -16
- package/frontend/src/chat/chat-message-script/chat-message-script.js +0 -6
- package/frontend/src/dashboard/dashboard.js +10 -1
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +4 -3
- package/frontend/src/dashboard-result/dashboard-result.js +3 -0
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +34 -0
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +37 -0
- package/frontend/src/models/models.js +8 -3
- package/frontend/src/task-by-name/task-by-name.html +77 -7
- package/frontend/src/task-by-name/task-by-name.js +84 -9
- package/frontend/src/tasks/task-details/task-details.html +8 -8
- package/frontend/src/tasks/task-details/task-details.js +2 -1
- package/frontend/src/tasks/tasks.js +25 -118
- package/package.json +2 -1
|
@@ -134,6 +134,8 @@ If the user's query is best answered with a chart, return a Chart.js 4 configura
|
|
|
134
134
|
|
|
135
135
|
If the user\'s query is best answered by a map, return an object { $featureCollection } which contains a GeoJSON FeatureCollection
|
|
136
136
|
|
|
137
|
+
If the user's query is best answered by a table, return an object { $table: { columns: string[], rows: any[][] } }
|
|
138
|
+
|
|
137
139
|
Example output:
|
|
138
140
|
|
|
139
141
|
The following script counts the number of users which are not deleted.
|
|
@@ -143,6 +143,8 @@ const systemPrompt = `
|
|
|
143
143
|
|
|
144
144
|
If the user\'s query is best answered by a map, return an object { $featureCollection } which contains a GeoJSON FeatureCollection
|
|
145
145
|
|
|
146
|
+
If the user's query is best answered by a table, return an object { $table: { columns: string[], rows: any[][] } }
|
|
147
|
+
|
|
146
148
|
Example output:
|
|
147
149
|
|
|
148
150
|
The following script counts the number of users which are not deleted.
|
|
@@ -32,11 +32,11 @@ module.exports = ({ db }) => async function updateDashboard(params) {
|
|
|
32
32
|
|
|
33
33
|
const updateObj = { code };
|
|
34
34
|
|
|
35
|
-
if (title) {
|
|
35
|
+
if (title != null) {
|
|
36
36
|
updateObj.title = title;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
if (description) {
|
|
39
|
+
if (description != null) {
|
|
40
40
|
updateObj.description = description;
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const escape = require('regexp.escape');
|
|
5
|
+
|
|
6
|
+
const GetTaskOverviewParams = new Archetype({
|
|
7
|
+
start: { $type: Date },
|
|
8
|
+
end: { $type: Date },
|
|
9
|
+
status: { $type: 'string' },
|
|
10
|
+
name: { $type: 'string' }
|
|
11
|
+
}).compile('GetTaskOverviewParams');
|
|
12
|
+
|
|
13
|
+
/** Statuses shown on the Task overview page. */
|
|
14
|
+
const OVERVIEW_STATUSES = ['pending', 'succeeded', 'failed', 'cancelled'];
|
|
15
|
+
|
|
16
|
+
function buildMatch(params) {
|
|
17
|
+
const { start, end, status, name } = params;
|
|
18
|
+
const match = {};
|
|
19
|
+
if (start != null && end != null) {
|
|
20
|
+
match.scheduledAt = { $gte: start, $lt: end };
|
|
21
|
+
} else if (start != null) {
|
|
22
|
+
match.scheduledAt = { $gte: start };
|
|
23
|
+
}
|
|
24
|
+
const statusVal = typeof status === 'string' ? status.trim() : status;
|
|
25
|
+
if (statusVal != null && statusVal !== '') {
|
|
26
|
+
match.status = statusVal;
|
|
27
|
+
} else {
|
|
28
|
+
match.status = { $in: ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'] };
|
|
29
|
+
}
|
|
30
|
+
if (name != null && name !== '') {
|
|
31
|
+
const nameStr = typeof name === 'string' ? name.trim() : String(name);
|
|
32
|
+
match.name = { $regex: escape(nameStr), $options: 'i' };
|
|
33
|
+
}
|
|
34
|
+
return match;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = ({ db }) => async function getTaskOverview(params) {
|
|
38
|
+
params = new GetTaskOverviewParams(params);
|
|
39
|
+
if (typeof params.status === 'string') params.status = params.status.trim();
|
|
40
|
+
if (typeof params.name === 'string') params.name = params.name.trim();
|
|
41
|
+
const { Task } = db.models;
|
|
42
|
+
const match = buildMatch(params);
|
|
43
|
+
|
|
44
|
+
const defaultCounts = OVERVIEW_STATUSES.map(s => ({ k: s, v: 0 }));
|
|
45
|
+
|
|
46
|
+
const pipeline = [
|
|
47
|
+
{ $match: match },
|
|
48
|
+
{
|
|
49
|
+
$facet: {
|
|
50
|
+
statusCounts: [
|
|
51
|
+
{ $group: { _id: { $ifNull: ['$status', 'unknown'] }, count: { $sum: 1 } } },
|
|
52
|
+
{ $group: { _id: null, counts: { $push: { k: '$_id', v: '$count' } } } },
|
|
53
|
+
{
|
|
54
|
+
$project: {
|
|
55
|
+
statusCounts: {
|
|
56
|
+
$arrayToObject: {
|
|
57
|
+
$concatArrays: [{ $literal: defaultCounts }, '$counts']
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{ $replaceRoot: { newRoot: '$statusCounts' } }
|
|
63
|
+
],
|
|
64
|
+
tasksByName: [
|
|
65
|
+
{
|
|
66
|
+
$group: {
|
|
67
|
+
_id: '$name',
|
|
68
|
+
totalCount: { $sum: 1 },
|
|
69
|
+
lastRun: { $max: '$scheduledAt' },
|
|
70
|
+
pending: { $sum: { $cond: [{ $eq: ['$status', 'pending'] }, 1, 0] } },
|
|
71
|
+
succeeded: { $sum: { $cond: [{ $eq: ['$status', 'succeeded'] }, 1, 0] } },
|
|
72
|
+
failed: { $sum: { $cond: [{ $eq: ['$status', 'failed'] }, 1, 0] } },
|
|
73
|
+
cancelled: { $sum: { $cond: [{ $eq: ['$status', 'cancelled'] }, 1, 0] } }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
$project: {
|
|
78
|
+
_id: 0,
|
|
79
|
+
name: '$_id',
|
|
80
|
+
totalCount: 1,
|
|
81
|
+
lastRun: 1,
|
|
82
|
+
statusCounts: {
|
|
83
|
+
pending: '$pending',
|
|
84
|
+
succeeded: '$succeeded',
|
|
85
|
+
failed: '$failed',
|
|
86
|
+
cancelled: '$cancelled'
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{ $sort: { name: 1 } }
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const [result] = await Task.aggregate(pipeline);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
statusCounts: result.statusCounts?.[0] ?? {},
|
|
100
|
+
tasksByName: result.tasksByName || []
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -1,65 +1,105 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const escape = require('regexp.escape');
|
|
4
5
|
|
|
5
6
|
const GetTasksParams = new Archetype({
|
|
6
|
-
start: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
status: {
|
|
14
|
-
$type: 'string'
|
|
15
|
-
},
|
|
16
|
-
name: {
|
|
17
|
-
$type: 'string'
|
|
18
|
-
}
|
|
7
|
+
start: { $type: Date },
|
|
8
|
+
end: { $type: Date },
|
|
9
|
+
status: { $type: 'string' },
|
|
10
|
+
name: { $type: 'string' },
|
|
11
|
+
skip: { $type: 'number', $default: 0 },
|
|
12
|
+
limit: { $type: 'number', $default: 100 }
|
|
19
13
|
}).compile('GetTasksParams');
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
const ALL_STATUSES = ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'];
|
|
16
|
+
|
|
17
|
+
/** Status keys for statusCounts (same shape as getTaskOverview). */
|
|
18
|
+
const STATUS_COUNT_KEYS = ['pending', 'succeeded', 'failed', 'cancelled'];
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
/** Max documents per request to avoid excessive memory and response size. */
|
|
21
|
+
const MAX_LIMIT = 2000;
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
function buildMatch(params) {
|
|
24
|
+
const { start, end, status, name } = params;
|
|
25
|
+
const match = {};
|
|
26
|
+
if (start != null && end != null) {
|
|
27
|
+
match.scheduledAt = { $gte: start, $lt: end };
|
|
28
|
+
} else if (start != null) {
|
|
29
|
+
match.scheduledAt = { $gte: start };
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
const statusVal = typeof status === 'string' ? status.trim() : status;
|
|
32
|
+
if (statusVal != null && statusVal !== '') {
|
|
33
|
+
match.status = statusVal;
|
|
35
34
|
} else {
|
|
36
|
-
|
|
35
|
+
match.status = { $in: ALL_STATUSES };
|
|
37
36
|
}
|
|
38
|
-
if (name) {
|
|
39
|
-
|
|
37
|
+
if (name != null && name !== '') {
|
|
38
|
+
const nameStr = typeof name === 'string' ? name.trim() : String(name);
|
|
39
|
+
match.name = { $regex: escape(nameStr), $options: 'i' };
|
|
40
40
|
}
|
|
41
|
+
return match;
|
|
42
|
+
}
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
/** Projection done in aggregation: only fields needed by frontend, payload → parameters, _id → id. */
|
|
45
|
+
const TASK_PROJECT_STAGE = {
|
|
46
|
+
_id: 1,
|
|
47
|
+
id: '$_id',
|
|
48
|
+
name: 1,
|
|
49
|
+
status: 1,
|
|
50
|
+
scheduledAt: 1,
|
|
51
|
+
createdAt: 1,
|
|
52
|
+
startedAt: 1,
|
|
53
|
+
completedAt: 1,
|
|
54
|
+
error: 1,
|
|
55
|
+
parameters: '$payload'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports = ({ db }) => async function getTasks(params) {
|
|
59
|
+
params = new GetTasksParams(params);
|
|
60
|
+
if (typeof params.status === 'string') params.status = params.status.trim();
|
|
61
|
+
if (typeof params.name === 'string') params.name = params.name.trim();
|
|
43
62
|
|
|
44
|
-
|
|
45
|
-
const
|
|
63
|
+
const skip = Math.max(0, Number(params.skip) || 0);
|
|
64
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Number(params.limit) || 100));
|
|
65
|
+
const { Task } = db.models;
|
|
66
|
+
const match = buildMatch(params);
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
const groupedTasks = allStatuses.reduce((groups, status) => {
|
|
49
|
-
groups[status] = [];
|
|
50
|
-
return groups;
|
|
51
|
-
}, {});
|
|
68
|
+
const defaultCounts = STATUS_COUNT_KEYS.map(s => ({ k: s, v: 0 }));
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
const pipeline = [
|
|
71
|
+
{ $match: match },
|
|
72
|
+
{
|
|
73
|
+
$facet: {
|
|
74
|
+
tasks: [
|
|
75
|
+
{ $sort: { scheduledAt: -1 } },
|
|
76
|
+
{ $skip: skip },
|
|
77
|
+
{ $limit: limit },
|
|
78
|
+
{ $project: TASK_PROJECT_STAGE }
|
|
79
|
+
],
|
|
80
|
+
count: [{ $count: 'total' }],
|
|
81
|
+
statusCounts: [
|
|
82
|
+
{ $group: { _id: { $ifNull: ['$status', 'unknown'] }, count: { $sum: 1 } } },
|
|
83
|
+
{ $group: { _id: null, counts: { $push: { k: '$_id', v: '$count' } } } },
|
|
84
|
+
{
|
|
85
|
+
$project: {
|
|
86
|
+
statusCounts: {
|
|
87
|
+
$arrayToObject: {
|
|
88
|
+
$concatArrays: [{ $literal: defaultCounts }, '$counts']
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{ $replaceRoot: { newRoot: '$statusCounts' } }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
58
96
|
}
|
|
59
|
-
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const [result] = await Task.aggregate(pipeline);
|
|
100
|
+
const tasks = result.tasks || [];
|
|
101
|
+
const numDocs = (result.count && result.count[0] && result.count[0].total) || 0;
|
|
102
|
+
const statusCounts = result.statusCounts?.[0] ?? {};
|
|
60
103
|
|
|
61
|
-
return {
|
|
62
|
-
tasks,
|
|
63
|
-
groupedTasks
|
|
64
|
-
};
|
|
104
|
+
return { tasks, numDocs, statusCounts };
|
|
65
105
|
};
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
exports.cancelTask = require('./cancelTask');
|
|
4
4
|
exports.createTask = require('./createTask');
|
|
5
5
|
exports.getTasks = require('./getTasks');
|
|
6
|
+
exports.getTaskOverview = require('./getTaskOverview');
|
|
6
7
|
exports.rescheduleTask = require('./rescheduleTask');
|
|
7
8
|
exports.runTask = require('./runTask');
|