@mongoosejs/studio 0.5.4 → 0.5.5
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/Model/analyzeSchema.js +202 -0
- package/backend/actions/Model/index.js +2 -0
- package/backend/actions/Model/validateDocument.js +35 -0
- package/backend/authorize.js +2 -0
- package/backend/helpers/validateDocumentWithTimeout.js +39 -0
- package/frontend/public/app.js +639 -99
- package/frontend/public/tw.css +221 -28
- package/frontend/src/api.js +12 -0
- package/frontend/src/array-utils.js +60 -2
- package/frontend/src/detail-array/detail-array.html +98 -14
- package/frontend/src/detail-array/detail-array.js +73 -3
- package/frontend/src/document/document.html +51 -0
- package/frontend/src/document/document.js +13 -0
- package/frontend/src/document-details/document-details.html +10 -3
- package/frontend/src/document-details/document-details.js +28 -0
- package/frontend/src/document-details/document-property/document-property.html +36 -44
- package/frontend/src/document-details/document-property/document-property.js +78 -14
- package/frontend/src/edit-array/edit-array.html +143 -8
- package/frontend/src/edit-array/edit-array.js +104 -2
- package/frontend/src/models/analyze-schema-modal/analyze-schema-modal.html +169 -0
- package/frontend/src/models/analyze-schema-modal/analyze-schema-modal.js +149 -0
- package/frontend/src/models/models.html +25 -13
- package/frontend/src/models/models.js +5 -13
- package/package.json +2 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const api = require('../../api');
|
|
4
|
+
const template = require('./analyze-schema-modal.html');
|
|
5
|
+
const xss = require('xss');
|
|
6
|
+
|
|
7
|
+
const TYPE_COLORS = {
|
|
8
|
+
string: '#315f5b',
|
|
9
|
+
number: '#465b86',
|
|
10
|
+
objectid: '#2f6381',
|
|
11
|
+
boolean: '#5b6540',
|
|
12
|
+
date: '#5a5278',
|
|
13
|
+
array: '#6b573d',
|
|
14
|
+
object: '#3f6648',
|
|
15
|
+
mixed: '#62546a',
|
|
16
|
+
buffer: '#5a5f50',
|
|
17
|
+
decimal128: '#4a6474',
|
|
18
|
+
map: '#4e665d',
|
|
19
|
+
null: '#3f4650',
|
|
20
|
+
undefined: '#5b626b'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const FALLBACK_TYPE_COLORS = [
|
|
24
|
+
'#315f5b',
|
|
25
|
+
'#465b86',
|
|
26
|
+
'#2f6381',
|
|
27
|
+
'#5b6540',
|
|
28
|
+
'#5a5278',
|
|
29
|
+
'#6b573d',
|
|
30
|
+
'#3f6648',
|
|
31
|
+
'#62546a'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
module.exports = app => app.component('analyze-schema-modal', {
|
|
35
|
+
template,
|
|
36
|
+
props: ['currentModel'],
|
|
37
|
+
emits: ['close'],
|
|
38
|
+
data: () => ({
|
|
39
|
+
schemaAnalysis: null,
|
|
40
|
+
fieldSearch: '',
|
|
41
|
+
showInvalidDocuments: false,
|
|
42
|
+
typeDisplayMode: 'bars'
|
|
43
|
+
}),
|
|
44
|
+
computed: {
|
|
45
|
+
filteredPaths() {
|
|
46
|
+
if (!this.schemaAnalysis) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const search = this.fieldSearch.trim().toLowerCase();
|
|
50
|
+
if (!search) {
|
|
51
|
+
return this.schemaAnalysis.paths;
|
|
52
|
+
}
|
|
53
|
+
return this.schemaAnalysis.paths.filter(path => path.path.toLowerCase().includes(search));
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
async mounted() {
|
|
57
|
+
window.analyzeSchemaModal = this;
|
|
58
|
+
const { analysis } = await api.Model.analyzeSchema({ model: this.currentModel });
|
|
59
|
+
this.schemaAnalysis = analysis;
|
|
60
|
+
this.$nextTick(() => {
|
|
61
|
+
this.$refs.fieldSearch?.focus();
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
unmounted() {
|
|
65
|
+
window.analyzeSchemaModal = null;
|
|
66
|
+
},
|
|
67
|
+
methods: {
|
|
68
|
+
close() {
|
|
69
|
+
this.$emit('close');
|
|
70
|
+
},
|
|
71
|
+
formatNumber(value) {
|
|
72
|
+
if (typeof value !== 'number') {
|
|
73
|
+
return 'Unknown';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return value.toLocaleString();
|
|
77
|
+
},
|
|
78
|
+
formatPercent(value, total) {
|
|
79
|
+
if (!total) {
|
|
80
|
+
return '0%';
|
|
81
|
+
}
|
|
82
|
+
return `${Math.round((value / total) * 1000) / 10}%`;
|
|
83
|
+
},
|
|
84
|
+
typePercentValue(value, total) {
|
|
85
|
+
if (!total) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
return Math.round((value / total) * 1000) / 10;
|
|
89
|
+
},
|
|
90
|
+
pathValueCount(path) {
|
|
91
|
+
return typeof path?.valueCount === 'number' && path.valueCount > 0 ?
|
|
92
|
+
path.valueCount :
|
|
93
|
+
this.schemaAnalysis.sampleSize;
|
|
94
|
+
},
|
|
95
|
+
primaryType(path) {
|
|
96
|
+
if (!path || !Array.isArray(path.types) || path.types.length === 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return path.types.find(typeInfo => typeInfo.type !== 'null' && typeInfo.type !== 'undefined') || path.types[0];
|
|
101
|
+
},
|
|
102
|
+
typeColor(type) {
|
|
103
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
104
|
+
if (TYPE_COLORS[normalizedType]) {
|
|
105
|
+
return TYPE_COLORS[normalizedType];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let hash = 0;
|
|
109
|
+
for (let i = 0; i < normalizedType.length; ++i) {
|
|
110
|
+
hash = ((hash << 5) - hash) + normalizedType.charCodeAt(i);
|
|
111
|
+
hash |= 0;
|
|
112
|
+
}
|
|
113
|
+
const index = Math.abs(hash) % FALLBACK_TYPE_COLORS.length;
|
|
114
|
+
return FALLBACK_TYPE_COLORS[index % FALLBACK_TYPE_COLORS.length];
|
|
115
|
+
},
|
|
116
|
+
typeBadgeStyle(type) {
|
|
117
|
+
return {
|
|
118
|
+
backgroundColor: this.typeColor(type),
|
|
119
|
+
color: '#fff'
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
typeSegmentStyle(type, count, total) {
|
|
123
|
+
return {
|
|
124
|
+
width: `${this.typePercentValue(count, total)}%`,
|
|
125
|
+
backgroundColor: this.typeColor(type),
|
|
126
|
+
color: '#fff'
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
documentRoute(documentId) {
|
|
130
|
+
return `/model/${this.currentModel}/document/${documentId}`;
|
|
131
|
+
},
|
|
132
|
+
highlightPath(path) {
|
|
133
|
+
const search = this.fieldSearch.trim();
|
|
134
|
+
if (!search) {
|
|
135
|
+
return xss(path);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const index = path.toLowerCase().indexOf(search.toLowerCase());
|
|
139
|
+
if (index === -1) {
|
|
140
|
+
return xss(path);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const before = path.slice(0, index);
|
|
144
|
+
const match = path.slice(index, index + search.length);
|
|
145
|
+
const after = path.slice(index + search.length);
|
|
146
|
+
return `${xss(before)}<strong>${xss(match)}</strong>${xss(after)}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
@@ -177,37 +177,34 @@
|
|
|
177
177
|
Indexes
|
|
178
178
|
</button>
|
|
179
179
|
<button
|
|
180
|
-
@click="
|
|
180
|
+
@click="openAnalyzeSchemaModal"
|
|
181
181
|
type="button"
|
|
182
182
|
class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
|
|
183
183
|
>
|
|
184
|
-
|
|
184
|
+
Analyze Schema
|
|
185
185
|
</button>
|
|
186
186
|
<button
|
|
187
|
-
@click="
|
|
187
|
+
@click="openCollectionInfo"
|
|
188
188
|
type="button"
|
|
189
189
|
class="block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted"
|
|
190
190
|
>
|
|
191
|
-
|
|
192
|
-
</button>
|
|
193
|
-
<button
|
|
194
|
-
@click="openDropCollectionModal"
|
|
195
|
-
type="button"
|
|
196
|
-
class="block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50"
|
|
197
|
-
>
|
|
198
|
-
Drop Collection
|
|
191
|
+
Collection Info
|
|
199
192
|
</button>
|
|
200
193
|
<button
|
|
201
194
|
@click="toggleRowNumbers()"
|
|
195
|
+
:disabled="outputType !== 'table'"
|
|
202
196
|
type="button"
|
|
203
|
-
class="block w-full px-4 py-2 text-left text-sm
|
|
197
|
+
class="block w-full px-4 py-2 text-left text-sm"
|
|
198
|
+
:class="outputType === 'table' ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'"
|
|
204
199
|
>
|
|
205
200
|
{{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}
|
|
206
201
|
</button>
|
|
207
202
|
<button
|
|
208
203
|
@click="resetFilter()"
|
|
204
|
+
:disabled="searchText.trim().length === 0"
|
|
209
205
|
type="button"
|
|
210
|
-
class="block w-full px-4 py-2 text-left text-sm
|
|
206
|
+
class="block w-full px-4 py-2 text-left text-sm"
|
|
207
|
+
:class="searchText.trim().length > 0 ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'"
|
|
211
208
|
>
|
|
212
209
|
Reset Filter
|
|
213
210
|
</button>
|
|
@@ -235,6 +232,13 @@
|
|
|
235
232
|
>
|
|
236
233
|
Default Projection
|
|
237
234
|
</async-button>
|
|
235
|
+
<button
|
|
236
|
+
@click="openDropCollectionModal"
|
|
237
|
+
type="button"
|
|
238
|
+
class="block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50"
|
|
239
|
+
>
|
|
240
|
+
Drop Collection
|
|
241
|
+
</button>
|
|
238
242
|
</div>
|
|
239
243
|
</div>
|
|
240
244
|
</div>
|
|
@@ -551,6 +555,14 @@
|
|
|
551
555
|
</div>
|
|
552
556
|
</template>
|
|
553
557
|
</modal>
|
|
558
|
+
<modal v-if="shouldShowAnalyzeSchemaModal" containerClass="md:!w-[70vw]">
|
|
559
|
+
<template v-slot:body>
|
|
560
|
+
<analyze-schema-modal
|
|
561
|
+
:current-model="currentModel"
|
|
562
|
+
@close="shouldShowAnalyzeSchemaModal = false"
|
|
563
|
+
></analyze-schema-modal>
|
|
564
|
+
</template>
|
|
565
|
+
</modal>
|
|
554
566
|
<modal v-if="shouldShowCollectionInfoModal">
|
|
555
567
|
<template v-slot:body>
|
|
556
568
|
<div class="modal-exit" @click="shouldShowCollectionInfoModal = false">×</div>
|
|
@@ -167,6 +167,7 @@ module.exports = app => app.component('models', {
|
|
|
167
167
|
addFieldFilterText: '',
|
|
168
168
|
showAddFieldDropdown: false,
|
|
169
169
|
shouldShowIndexModal: false,
|
|
170
|
+
shouldShowAnalyzeSchemaModal: false,
|
|
170
171
|
shouldShowCollectionInfoModal: false,
|
|
171
172
|
shouldShowDropCollectionModal: false,
|
|
172
173
|
dropCollectionConfirmName: '',
|
|
@@ -889,6 +890,10 @@ module.exports = app => app.component('models', {
|
|
|
889
890
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
890
891
|
this.schemaIndexes = schemaIndexes;
|
|
891
892
|
},
|
|
893
|
+
openAnalyzeSchemaModal() {
|
|
894
|
+
this.closeActionsMenu();
|
|
895
|
+
this.shouldShowAnalyzeSchemaModal = true;
|
|
896
|
+
},
|
|
892
897
|
toggleActionsMenu() {
|
|
893
898
|
this.showActionsMenu = !this.showActionsMenu;
|
|
894
899
|
},
|
|
@@ -949,19 +954,6 @@ module.exports = app => app.component('models', {
|
|
|
949
954
|
this.$toast.success(`Collection ${this.currentModel} dropped.`);
|
|
950
955
|
await this.getDocuments();
|
|
951
956
|
},
|
|
952
|
-
async findOldestDocument() {
|
|
953
|
-
this.closeActionsMenu();
|
|
954
|
-
const { docs } = await api.Model.getDocuments({
|
|
955
|
-
model: this.currentModel,
|
|
956
|
-
limit: 1,
|
|
957
|
-
sortKey: '_id',
|
|
958
|
-
sortDirection: 1
|
|
959
|
-
});
|
|
960
|
-
if (!Array.isArray(docs) || docs.length === 0) {
|
|
961
|
-
throw new Error('No documents found');
|
|
962
|
-
}
|
|
963
|
-
this.openDocument(docs[0]);
|
|
964
|
-
},
|
|
965
957
|
isTTLIndex(index) {
|
|
966
958
|
return index != null && index.expireAfterSeconds != null;
|
|
967
959
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"ejson": "^2.2.3",
|
|
20
20
|
"extrovert": "^0.2.0",
|
|
21
21
|
"marked": "15.0.12",
|
|
22
|
+
"mpath": "^0.9.0",
|
|
22
23
|
"node-inspect-extracted": "3.x",
|
|
23
24
|
"regexp.escape": "^2.0.1",
|
|
24
25
|
"tailwindcss": "3.4.0",
|