@mongoosejs/studio 0.3.6 → 0.3.8
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/Dashboard/getDashboard.js +2 -1
- package/backend/actions/Task/getTasksOverTime.js +66 -0
- package/backend/actions/Task/index.js +1 -0
- package/backend/integrations/callLLM.js +2 -1
- package/backend/integrations/streamLLM.js +2 -1
- package/backend/netlify.js +2 -1
- package/backend/next.js +2 -1
- package/constants.js +7 -0
- package/express.js +2 -1
- package/frontend/index.js +2 -1
- package/frontend/public/app.js +541 -88
- package/frontend/public/tw.css +14 -12
- package/frontend/src/api.js +6 -0
- package/frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.html +2 -2
- package/frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.js +13 -1
- package/frontend/src/dashboard-result/dashboard-result.html +1 -1
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +21 -1
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +52 -0
- package/frontend/src/detail-date/detail-date.html +1 -0
- package/frontend/src/detail-date/detail-date.js +123 -0
- package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html +26 -0
- package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js +41 -0
- package/frontend/src/document-details/document-property/document-property.html +13 -5
- package/frontend/src/document-details/document-property/document-property.js +14 -1
- package/frontend/src/tasks/tasks.html +34 -20
- package/frontend/src/tasks/tasks.js +158 -5
- package/local.js +2 -1
- package/package.json +1 -1
|
@@ -4,6 +4,39 @@ const template = require('./tasks.html');
|
|
|
4
4
|
const api = require('../api');
|
|
5
5
|
const { DATE_FILTERS, getDateRangeForRange } = require('../_util/dateRange');
|
|
6
6
|
|
|
7
|
+
/** Returns the bucket size in ms for the given date range. */
|
|
8
|
+
function getBucketSizeMs(range) {
|
|
9
|
+
switch (range) {
|
|
10
|
+
case 'last_hour': return 5 * 60 * 1000; // 5 minutes
|
|
11
|
+
case 'today':
|
|
12
|
+
case 'yesterday': return 60 * 60 * 1000; // 1 hour
|
|
13
|
+
case 'thisWeek':
|
|
14
|
+
case 'lastWeek': return 24 * 60 * 60 * 1000; // 1 day
|
|
15
|
+
case 'thisMonth':
|
|
16
|
+
case 'lastMonth': return 24 * 60 * 60 * 1000; // 1 day
|
|
17
|
+
default: return 5 * 60 * 1000;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Formats a bucket timestamp for the x-axis label based on the date range. */
|
|
22
|
+
function formatBucketLabel(timestamp, range) {
|
|
23
|
+
const date = new Date(timestamp);
|
|
24
|
+
switch (range) {
|
|
25
|
+
case 'last_hour':
|
|
26
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
27
|
+
case 'today':
|
|
28
|
+
case 'yesterday':
|
|
29
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
30
|
+
case 'thisWeek':
|
|
31
|
+
case 'lastWeek':
|
|
32
|
+
case 'thisMonth':
|
|
33
|
+
case 'lastMonth':
|
|
34
|
+
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
|
35
|
+
default:
|
|
36
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
module.exports = app => app.component('tasks', {
|
|
8
41
|
data: () => ({
|
|
9
42
|
status: 'init',
|
|
@@ -31,10 +64,22 @@ module.exports = app => app.component('tasks', {
|
|
|
31
64
|
scheduledAt: '',
|
|
32
65
|
parameters: '',
|
|
33
66
|
repeatInterval: ''
|
|
34
|
-
}
|
|
67
|
+
},
|
|
68
|
+
// Chart over time
|
|
69
|
+
overTimeChart: null,
|
|
70
|
+
overTimeBuckets: [],
|
|
71
|
+
// Toggled with v-if on the canvas so Chart.js is torn down and remounted on
|
|
72
|
+
// filter changes. Updating Chart.js in place during a big Vue re-render was
|
|
73
|
+
// freezing the page (dropdowns unresponsive, chart stale).
|
|
74
|
+
showOverTimeChart: true
|
|
35
75
|
}),
|
|
36
76
|
methods: {
|
|
37
77
|
async getTasks() {
|
|
78
|
+
// Hide chart canvas + teardown Chart.js immediately on filter changes
|
|
79
|
+
// (see showOverTimeChart + v-if on the canvas in tasks.html).
|
|
80
|
+
this.showOverTimeChart = false;
|
|
81
|
+
this.destroyOverTimeChart();
|
|
82
|
+
|
|
38
83
|
const params = {};
|
|
39
84
|
if (this.selectedStatus == 'all') {
|
|
40
85
|
params.status = null;
|
|
@@ -53,9 +98,105 @@ module.exports = app => app.component('tasks', {
|
|
|
53
98
|
params.name = this.searchQuery.trim();
|
|
54
99
|
}
|
|
55
100
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
101
|
+
const [overviewResult, overTimeResult] = await Promise.all([
|
|
102
|
+
api.Task.getTaskOverview(params),
|
|
103
|
+
api.Task.getTasksOverTime({
|
|
104
|
+
start: params.start,
|
|
105
|
+
end: params.end,
|
|
106
|
+
bucketSizeMs: getBucketSizeMs(this.selectedRange)
|
|
107
|
+
})
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
this.statusCounts = overviewResult.statusCounts || this.statusCounts;
|
|
111
|
+
this.tasksByName = overviewResult.tasksByName || [];
|
|
112
|
+
this.overTimeBuckets = overTimeResult || [];
|
|
113
|
+
if (this.overTimeBuckets.length === 0) {
|
|
114
|
+
this.showOverTimeChart = false;
|
|
115
|
+
this.destroyOverTimeChart();
|
|
116
|
+
} else {
|
|
117
|
+
this.showOverTimeChart = true;
|
|
118
|
+
await this.$nextTick();
|
|
119
|
+
this.renderOverTimeChart();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/** Build or update the stacked bar chart showing tasks over time. */
|
|
124
|
+
renderOverTimeChart() {
|
|
125
|
+
const Chart = typeof window !== 'undefined' && window.Chart;
|
|
126
|
+
if (!Chart) {
|
|
127
|
+
throw new Error('Chart.js not found');
|
|
128
|
+
}
|
|
129
|
+
const canvas = this.$refs.overTimeChart;
|
|
130
|
+
if (!canvas || typeof canvas.getContext !== 'function') return;
|
|
131
|
+
|
|
132
|
+
const buckets = this.overTimeBuckets;
|
|
133
|
+
const labels = buckets.map(b => formatBucketLabel(b.timestamp, this.selectedRange));
|
|
134
|
+
const succeeded = buckets.map(b => b.succeeded || 0);
|
|
135
|
+
const failed = buckets.map(b => b.failed || 0);
|
|
136
|
+
const cancelled = buckets.map(b => b.cancelled || 0);
|
|
137
|
+
|
|
138
|
+
const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
|
|
139
|
+
const tickColor = isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.6)';
|
|
140
|
+
const gridColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
|
|
141
|
+
|
|
142
|
+
const chartData = {
|
|
143
|
+
labels,
|
|
144
|
+
datasets: [
|
|
145
|
+
{ label: 'Succeeded', data: succeeded, backgroundColor: '#22c55e', stack: 'tasks' },
|
|
146
|
+
{ label: 'Failed', data: failed, backgroundColor: '#ef4444', stack: 'tasks' },
|
|
147
|
+
{ label: 'Cancelled', data: cancelled, backgroundColor: '#6b7280', stack: 'tasks' }
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (this.overTimeChart) {
|
|
152
|
+
try {
|
|
153
|
+
this.overTimeChart.data.labels = labels;
|
|
154
|
+
this.overTimeChart.data.datasets[0].data = succeeded;
|
|
155
|
+
this.overTimeChart.data.datasets[1].data = failed;
|
|
156
|
+
this.overTimeChart.data.datasets[2].data = cancelled;
|
|
157
|
+
this.overTimeChart.update('none');
|
|
158
|
+
} finally {
|
|
159
|
+
this.destroyOverTimeChart();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.overTimeChart = new Chart(canvas, {
|
|
164
|
+
type: 'bar',
|
|
165
|
+
data: chartData,
|
|
166
|
+
options: {
|
|
167
|
+
responsive: true,
|
|
168
|
+
maintainAspectRatio: false,
|
|
169
|
+
animation: false,
|
|
170
|
+
scales: {
|
|
171
|
+
x: {
|
|
172
|
+
stacked: true,
|
|
173
|
+
ticks: { color: tickColor, maxRotation: 45, minRotation: 0 },
|
|
174
|
+
grid: { color: gridColor }
|
|
175
|
+
},
|
|
176
|
+
y: {
|
|
177
|
+
stacked: true,
|
|
178
|
+
beginAtZero: true,
|
|
179
|
+
ticks: { color: tickColor, precision: 0 },
|
|
180
|
+
grid: { color: gridColor }
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
plugins: {
|
|
184
|
+
legend: {
|
|
185
|
+
display: true,
|
|
186
|
+
position: 'top',
|
|
187
|
+
labels: { color: tickColor }
|
|
188
|
+
},
|
|
189
|
+
tooltip: { mode: 'index', intersect: false }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
destroyOverTimeChart() {
|
|
196
|
+
if (this.overTimeChart) {
|
|
197
|
+
this.overTimeChart.destroy();
|
|
198
|
+
this.overTimeChart = null;
|
|
199
|
+
}
|
|
59
200
|
},
|
|
60
201
|
openTaskGroupDetails(group) {
|
|
61
202
|
const query = { dateRange: this.selectedRange || 'last_hour' };
|
|
@@ -231,10 +372,22 @@ module.exports = app => app.component('tasks', {
|
|
|
231
372
|
}
|
|
232
373
|
},
|
|
233
374
|
mounted: async function() {
|
|
375
|
+
// Load initial data while showing the loader state.
|
|
234
376
|
await this.updateDateRange();
|
|
235
|
-
|
|
377
|
+
|
|
378
|
+
// Once data is loaded, switch to the main view.
|
|
236
379
|
this.status = 'loaded';
|
|
380
|
+
await this.$nextTick();
|
|
381
|
+
|
|
382
|
+
// Ensure the chart renders now that the canvas exists in the DOM.
|
|
383
|
+
if (this.showOverTimeChart && this.overTimeBuckets.length > 0) {
|
|
384
|
+
this.renderOverTimeChart();
|
|
385
|
+
}
|
|
386
|
+
|
|
237
387
|
this.setDefaultCreateTaskValues();
|
|
238
388
|
},
|
|
389
|
+
beforeUnmount() {
|
|
390
|
+
this.destroyOverTimeChart();
|
|
391
|
+
},
|
|
239
392
|
template: template
|
|
240
393
|
});
|
package/local.js
CHANGED
|
@@ -29,7 +29,8 @@ async function run() {
|
|
|
29
29
|
__watch: process.env.WATCH,
|
|
30
30
|
_mothershipUrl: 'http://localhost:7777/.netlify/functions',
|
|
31
31
|
apiKey: 'TACO',
|
|
32
|
-
openAIAPIKey: process.env.OPENAI_API_KEY
|
|
32
|
+
openAIAPIKey: process.env.OPENAI_API_KEY,
|
|
33
|
+
googleGeminiAPIKey: process.env.GEMINI_API_KEY
|
|
33
34
|
})
|
|
34
35
|
);
|
|
35
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|