@mongoosejs/studio 0.3.0 → 0.3.2
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/DEVGUIDE.md +8 -0
- package/backend/actions/ChatThread/createChatMessage.js +2 -0
- package/backend/actions/ChatThread/streamChatMessage.js +2 -0
- package/backend/actions/Dashboard/getDashboard.js +15 -11
- 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 +318 -148
- 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/chat/chat.js +2 -0
- package/frontend/src/dashboard/dashboard.js +13 -2
- 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 +9 -3
- package/frontend/src/navbar/navbar.js +3 -2
- 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/local.js +38 -0
- package/package.json +4 -1
- package/seed/connect.js +23 -0
- package/seed/index.js +101 -0
package/DEVGUIDE.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## Running Locally
|
|
2
|
+
|
|
3
|
+
To run this repo locally:
|
|
4
|
+
|
|
5
|
+
1. Install dependencies with `npm install`
|
|
6
|
+
2. Seed the database with `npm run seed`. Make sure you have a MongoDB instance running on `localhost:27017`.
|
|
7
|
+
3. Run backend on port 7777. Make sure to seed backend as well.
|
|
8
|
+
4. Start the server with `npm start`
|
|
@@ -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.
|
|
@@ -23,16 +23,17 @@ const GetDashboardParams = new Archetype({
|
|
|
23
23
|
}
|
|
24
24
|
}).compile('GetDashboardParams');
|
|
25
25
|
|
|
26
|
-
module.exports = ({ db }) => async function getDashboard(params) {
|
|
26
|
+
module.exports = ({ db, options }) => async function getDashboard(params) {
|
|
27
27
|
const { $workspaceId, authorization, dashboardId, evaluate, roles } = new GetDashboardParams(params);
|
|
28
28
|
const Dashboard = db.model('__Studio_Dashboard');
|
|
29
|
+
const mothershipUrl = options?._mothershipUrl ?? 'https://mongoose-js.netlify.app/.netlify/functions';
|
|
29
30
|
|
|
30
31
|
await authorize('Dashboard.getDashboard', roles);
|
|
31
32
|
|
|
32
33
|
const dashboard = await Dashboard.findOne({ _id: dashboardId });
|
|
33
34
|
if (evaluate) {
|
|
34
35
|
let result = null;
|
|
35
|
-
const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization);
|
|
36
|
+
const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization, mothershipUrl);
|
|
36
37
|
// Avoid unhandled promise rejection since we handle the promise later.
|
|
37
38
|
startExec.catch(() => {});
|
|
38
39
|
try {
|
|
@@ -47,7 +48,8 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
47
48
|
$workspaceId,
|
|
48
49
|
authorization,
|
|
49
50
|
null,
|
|
50
|
-
{ message: error.message }
|
|
51
|
+
{ message: error.message },
|
|
52
|
+
mothershipUrl
|
|
51
53
|
);
|
|
52
54
|
});
|
|
53
55
|
return { dashboard, dashboardResult, error: { message: error.message } };
|
|
@@ -62,7 +64,9 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
62
64
|
dashboardResult._id,
|
|
63
65
|
$workspaceId,
|
|
64
66
|
authorization,
|
|
65
|
-
result
|
|
67
|
+
result,
|
|
68
|
+
undefined,
|
|
69
|
+
mothershipUrl
|
|
66
70
|
);
|
|
67
71
|
});
|
|
68
72
|
|
|
@@ -71,12 +75,12 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
71
75
|
return { dashboard, error: { message: error.message } };
|
|
72
76
|
}
|
|
73
77
|
} else {
|
|
74
|
-
const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization);
|
|
78
|
+
const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization, mothershipUrl);
|
|
75
79
|
return { dashboard, dashboardResults };
|
|
76
80
|
}
|
|
77
81
|
};
|
|
78
82
|
|
|
79
|
-
async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error) {
|
|
83
|
+
async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error, mothershipUrl) {
|
|
80
84
|
if (!workspaceId) {
|
|
81
85
|
return {};
|
|
82
86
|
}
|
|
@@ -84,7 +88,7 @@ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authori
|
|
|
84
88
|
if (authorization) {
|
|
85
89
|
headers.Authorization = authorization;
|
|
86
90
|
}
|
|
87
|
-
const response = await fetch(
|
|
91
|
+
const response = await fetch(`${mothershipUrl}/completeDashboardEvaluate`, {
|
|
88
92
|
method: 'POST',
|
|
89
93
|
headers,
|
|
90
94
|
body: JSON.stringify({
|
|
@@ -106,7 +110,7 @@ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authori
|
|
|
106
110
|
return await response.json();
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
|
|
113
|
+
async function startDashboardEvaluate(dashboardId, workspaceId, authorization, _mothershipUrl) {
|
|
110
114
|
if (!workspaceId) {
|
|
111
115
|
return {};
|
|
112
116
|
}
|
|
@@ -114,7 +118,7 @@ async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
|
|
|
114
118
|
if (authorization) {
|
|
115
119
|
headers.Authorization = authorization;
|
|
116
120
|
}
|
|
117
|
-
const response = await fetch(
|
|
121
|
+
const response = await fetch(`${_mothershipUrl}/startDashboardEvaluate`, {
|
|
118
122
|
method: 'POST',
|
|
119
123
|
headers,
|
|
120
124
|
body: JSON.stringify({
|
|
@@ -134,7 +138,7 @@ async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
|
|
|
134
138
|
return await response.json();
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
async function getDashboardResults(dashboardId, workspaceId, authorization) {
|
|
141
|
+
async function getDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl) {
|
|
138
142
|
if (!workspaceId) {
|
|
139
143
|
return {};
|
|
140
144
|
}
|
|
@@ -142,7 +146,7 @@ async function getDashboardResults(dashboardId, workspaceId, authorization) {
|
|
|
142
146
|
if (authorization) {
|
|
143
147
|
headers.Authorization = authorization;
|
|
144
148
|
}
|
|
145
|
-
const response = await fetch(
|
|
149
|
+
const response = await fetch(`${mothershipUrl}/getDashboardResults`, {
|
|
146
150
|
method: 'POST',
|
|
147
151
|
headers,
|
|
148
152
|
body: JSON.stringify({
|
|
@@ -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');
|