@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.
Files changed (28) hide show
  1. package/backend/actions/Dashboard/getDashboard.js +2 -1
  2. package/backend/actions/Task/getTasksOverTime.js +66 -0
  3. package/backend/actions/Task/index.js +1 -0
  4. package/backend/integrations/callLLM.js +2 -1
  5. package/backend/integrations/streamLLM.js +2 -1
  6. package/backend/netlify.js +2 -1
  7. package/backend/next.js +2 -1
  8. package/constants.js +7 -0
  9. package/express.js +2 -1
  10. package/frontend/index.js +2 -1
  11. package/frontend/public/app.js +541 -88
  12. package/frontend/public/tw.css +14 -12
  13. package/frontend/src/api.js +6 -0
  14. package/frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.html +2 -2
  15. package/frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.js +13 -1
  16. package/frontend/src/dashboard-result/dashboard-result.html +1 -1
  17. package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +21 -1
  18. package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +52 -0
  19. package/frontend/src/detail-date/detail-date.html +1 -0
  20. package/frontend/src/detail-date/detail-date.js +123 -0
  21. package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html +26 -0
  22. package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js +41 -0
  23. package/frontend/src/document-details/document-property/document-property.html +13 -5
  24. package/frontend/src/document-details/document-property/document-property.js +14 -1
  25. package/frontend/src/tasks/tasks.html +34 -20
  26. package/frontend/src/tasks/tasks.js +158 -5
  27. package/local.js +2 -1
  28. 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 { statusCounts, tasksByName } = await api.Task.getTaskOverview(params);
57
- this.statusCounts = statusCounts || this.statusCounts;
58
- this.tasksByName = tasksByName || [];
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
- await this.getTasks();
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.6",
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": {