expressa 1.1.2 → 1.2.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.
Files changed (94) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/README.md +4 -3
  3. package/controllers/collections.js +53 -32
  4. package/controllers/install.js +34 -27
  5. package/db/cached.js +3 -0
  6. package/db/file.js +3 -0
  7. package/db/memory.js +3 -0
  8. package/db/mongo.js +7 -8
  9. package/db/postgres.js +13 -0
  10. package/index.js +6 -5
  11. package/listeners_validation.js +4 -0
  12. package/modules/admin/.babelrc +13 -12
  13. package/modules/admin/build/webpack.prod.conf.js +5 -3
  14. package/modules/admin/config/index.js +2 -2
  15. package/modules/admin/dist/index.html +1 -1
  16. package/modules/admin/dist/index.html.gz +0 -0
  17. package/modules/admin/dist/static/css/app.9dd57eea.css.gz +0 -0
  18. package/modules/admin/dist/static/css/chunk-1dff.39c7634b.css +1 -0
  19. package/modules/admin/dist/static/css/chunk-1dff.39c7634b.css.gz +0 -0
  20. package/modules/admin/dist/static/css/chunk-3bc2.89632b9d.css.gz +0 -0
  21. package/modules/admin/dist/static/css/{chunk-5cee.ce0e4bdb.css → chunk-5cee.9685afb8.css} +0 -0
  22. package/modules/admin/dist/static/css/chunk-5cee.9685afb8.css.gz +0 -0
  23. package/modules/admin/dist/static/css/chunk-621e.f3d0eab6.css +1 -0
  24. package/modules/admin/dist/static/css/chunk-621e.f3d0eab6.css.gz +0 -0
  25. package/modules/admin/dist/static/css/chunk-7291.13514e45.css.gz +0 -0
  26. package/modules/admin/dist/static/css/chunk-a99c.c4b0e273.css +1 -0
  27. package/modules/admin/dist/static/css/chunk-a99c.c4b0e273.css.gz +0 -0
  28. package/modules/admin/dist/static/css/chunk-b423.5f239333.css.gz +0 -0
  29. package/modules/admin/dist/static/css/{chunk-bd5f.aefa49d7.css → chunk-bd5f.6ab011ba.css} +0 -0
  30. package/modules/admin/dist/static/css/chunk-bd5f.6ab011ba.css.gz +0 -0
  31. package/modules/admin/dist/static/css/chunk-e375.1d849865.css +1 -0
  32. package/modules/admin/dist/static/css/chunk-e375.1d849865.css.gz +0 -0
  33. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css.gz +0 -0
  34. package/modules/admin/dist/static/css/{chunk-759b.3a895a1e.css → chunk-f075.3a895a1e.css} +0 -0
  35. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css.gz +0 -0
  36. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css +14 -0
  37. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css.gz +0 -0
  38. package/modules/admin/dist/static/js/app.947e8310.js +1 -0
  39. package/modules/admin/dist/static/js/app.947e8310.js.gz +0 -0
  40. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js +2 -0
  41. package/modules/admin/dist/static/js/{chunk-23bd.5fa24c85.js.LICENSE.txt → chunk-09d8.561880e0.js.LICENSE.txt} +1 -1
  42. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt.gz +0 -0
  43. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.gz +0 -0
  44. package/modules/admin/dist/static/js/chunk-1dff.b5ee003b.js +1 -0
  45. package/modules/admin/dist/static/js/chunk-1dff.b5ee003b.js.gz +0 -0
  46. package/modules/admin/dist/static/js/chunk-3bc2.b0270c10.js +1 -0
  47. package/modules/admin/dist/static/js/chunk-3bc2.b0270c10.js.gz +0 -0
  48. package/modules/admin/dist/static/js/{chunk-5cee.a3b03f7a.js → chunk-5cee.a6f0e769.js} +0 -0
  49. package/modules/admin/dist/static/js/chunk-5cee.a6f0e769.js.gz +0 -0
  50. package/modules/admin/dist/static/js/chunk-621e.c63db96c.js +1 -0
  51. package/modules/admin/dist/static/js/chunk-621e.c63db96c.js.gz +0 -0
  52. package/modules/admin/dist/static/js/chunk-7291.f914fca7.js +1 -0
  53. package/modules/admin/dist/static/js/chunk-7291.f914fca7.js.gz +0 -0
  54. package/modules/admin/dist/static/js/chunk-a99c.3f48f127.js +1 -0
  55. package/modules/admin/dist/static/js/chunk-a99c.3f48f127.js.gz +0 -0
  56. package/modules/admin/dist/static/js/chunk-b423.387c504c.js.gz +0 -0
  57. package/modules/admin/dist/static/js/{chunk-bd5f.e9509f10.js → chunk-bd5f.af5f2197.js} +0 -0
  58. package/modules/admin/dist/static/js/chunk-bd5f.af5f2197.js.gz +0 -0
  59. package/modules/admin/dist/static/js/chunk-e375.7ba1b61f.js +1 -0
  60. package/modules/admin/dist/static/js/chunk-e375.7ba1b61f.js.gz +0 -0
  61. package/modules/admin/dist/static/js/{chunk-e7c3.e5df8ceb.js → chunk-e7c3.a8659352.js} +1 -1
  62. package/modules/admin/dist/static/js/chunk-e7c3.a8659352.js.gz +0 -0
  63. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js.gz +0 -0
  64. package/modules/admin/dist/static/js/chunk-f075.6b230660.js +1 -0
  65. package/modules/admin/dist/static/js/chunk-f075.6b230660.js.gz +0 -0
  66. package/modules/admin/dist/static/js/chunk-libs.5637556a.js +2 -0
  67. package/modules/admin/dist/static/js/{chunk-libs.b6f27cfe.js.LICENSE.txt → chunk-libs.5637556a.js.LICENSE.txt} +1 -1
  68. package/modules/admin/dist/static/js/chunk-libs.5637556a.js.LICENSE.txt.gz +0 -0
  69. package/modules/admin/dist/static/js/chunk-libs.5637556a.js.gz +0 -0
  70. package/modules/admin/package.json +4 -3
  71. package/modules/admin/src/main.js +2 -2
  72. package/modules/admin/src/router/index.js +185 -185
  73. package/modules/admin/src/utils/request.js +47 -48
  74. package/modules/admin/src/views/ListDocuments2.vue +407 -309
  75. package/package.json +5 -5
  76. package/test/collections.js +2 -3
  77. package/test/collections.querying.js +18 -4
  78. package/test/test.js +4 -0
  79. package/util.js +19 -0
  80. package/modules/admin/dist/static/css/chunk-36ca.e7e72230.css +0 -1
  81. package/modules/admin/dist/static/css/chunk-62f2.538cfd24.css +0 -1
  82. package/modules/admin/dist/static/css/chunk-7869.220d4af5.css +0 -1
  83. package/modules/admin/dist/static/css/chunk-e80a.5ee55031.css +0 -1
  84. package/modules/admin/dist/static/css/chunk-libs.68e8d170.css +0 -14
  85. package/modules/admin/dist/static/js/app.12d7fe96.js +0 -1
  86. package/modules/admin/dist/static/js/chunk-23bd.5fa24c85.js +0 -2
  87. package/modules/admin/dist/static/js/chunk-36ca.eda87655.js +0 -1
  88. package/modules/admin/dist/static/js/chunk-3bc2.028748b7.js +0 -1
  89. package/modules/admin/dist/static/js/chunk-62f2.ee07e11b.js +0 -1
  90. package/modules/admin/dist/static/js/chunk-7291.590524e9.js +0 -1
  91. package/modules/admin/dist/static/js/chunk-759b.89a889b6.js +0 -1
  92. package/modules/admin/dist/static/js/chunk-7869.b7ab2c47.js +0 -1
  93. package/modules/admin/dist/static/js/chunk-e80a.cc0d79f4.js +0 -1
  94. package/modules/admin/dist/static/js/chunk-libs.b6f27cfe.js +0 -2
@@ -1,309 +1,407 @@
1
- <template>
2
- <div class="px-3">
3
- <div class="d-flex align-items-center py-2">
4
- <h3 class="mb-0 text-capitalize">
5
- {{ collectionName }}
6
- </h3>
7
-
8
- <span class="mx-auto" />
9
-
10
- <el-checkbox
11
- v-model="isFiltersVisible"
12
- class="mb-0 mr-4"
13
- >
14
- Show Filters
15
- </el-checkbox>
16
-
17
- <el-pagination
18
- :page-size="pageSize"
19
- :total="count"
20
- :page-sizes="pageSizes"
21
- :current-page.sync="page"
22
- layout="sizes, prev, pager, next"
23
- @current-change="update()"
24
- @size-change="pageSize = $event, update()"
25
- />
26
- </div>
27
-
28
- <el-table
29
- :data="tableRows"
30
- element-loading-text="Loading"
31
- border
32
- fit
33
- highlight-current-row
34
- >
35
- <el-table-column v-for="(name, i) in listedProperties" :key="name" :label="name" align="center">
36
- <template slot-scope="scope">
37
- <div v-if="scope.$index === 0 && isFiltersVisible" class="text-left">
38
- <el-input
39
- v-model="searchFilters[name]"
40
- :placeholder="`Search by ${name}`"
41
- :type="getFieldType(name) === 'number' ? 'number' : 'text'"
42
- @change="update()"
43
- />
44
-
45
- <el-checkbox
46
- v-model="exactSearches[name]"
47
- :disabled="getFieldType(name) === 'number'"
48
- class="mt-3"
49
- @change="update()"
50
- >
51
- Exact match
52
- </el-checkbox>
53
- </div>
54
-
55
- <template v-else>
56
- <router-link v-if="i === 0 && collectionName !== 'requestlog'" :to="'/edit/'+collectionName+'/'+scope.row._id">
57
- {{ getPath(scope.row, name) }}
58
- </router-link>
59
- <router-link v-if="i === 0 && collectionName === 'requestlog'" :to="'/dev/viewrequest/'+scope.row._id">
60
- {{ getPath(scope.row, name) }}
61
- </router-link>
62
- <span v-if="i !== 0">{{ getPath(scope.row, name) }}</span>
63
- </template>
64
- </template>
65
- </el-table-column>
66
- </el-table>
67
-
68
- <div class="d-flex align-items-center my-3">
69
- <router-link v-if="showAddButton" :to="'/edit/' + collectionName + '/create'" class="mr-2">
70
- <button class="btn btn-primary">
71
- Add
72
- </button>
73
- </router-link>
74
-
75
- <button class="btn btn-secondary download-button mr-2" @click="downloadCSV()">
76
- Download
77
- </button>
78
-
79
- <button class="btn btn-secondary download-button" @click="downloadAllCSV()">
80
- Download All
81
- </button>
82
-
83
- <span class="mx-auto" />
84
-
85
- <el-pagination
86
- :page-size="pageSize"
87
- :total="count"
88
- :page-sizes="pageSizes"
89
- :current-page.sync="page"
90
- layout="sizes, prev, pager, next"
91
- @current-change="update()"
92
- @size-change="pageSize = $event, update()"
93
- />
94
- </div>
95
- </div>
96
- </template>
97
-
98
- <script>
99
- import request from '@/utils/request'
100
- import saveAs from 'file-saver'
101
- import objectPath from 'object-path'
102
-
103
- const pageSizes = [25, 50, 100, 500]
104
-
105
- export default {
106
- name: 'ListDocuments',
107
- props: {
108
- collectionName: {
109
- type: String,
110
- default: 'collection'
111
- },
112
- filter: {
113
- type: Object,
114
- default: () => {}
115
- },
116
- columns: {
117
- type: Array,
118
- default: () => undefined
119
- },
120
- showAddButton: {
121
- type: Boolean,
122
- default: true
123
- },
124
- showPaginateOnBottom: {
125
- type: Boolean,
126
- default: true
127
- },
128
- },
129
- data: () => ({
130
- start: 0,
131
- page: 1,
132
- count: 0,
133
- data: [],
134
- schema: {},
135
- exactSearches: {},
136
- searchFilters: {},
137
- listedProperties: [],
138
- pageSizes: pageSizes,
139
- pageSize: pageSizes[0],
140
- isFiltersVisible: true,
141
- }),
142
- computed: {
143
- tableRows() {
144
- if (!this.isFiltersVisible) {
145
- return this.data
146
- }
147
-
148
- return [
149
- // INFO: This empty row is used to display the search filters.
150
- {},
151
- ...this.data
152
- ]
153
- },
154
- appliedSearchFilters() {
155
- return Object.keys(this.searchFilters).reduce((filter, fieldName) => {
156
- const searchKeyword = this.searchFilters[fieldName]
157
-
158
- const fieldtype = this.getFieldType(fieldName)
159
-
160
- if (!searchKeyword || fieldtype === 'custom') {
161
- return filter
162
- }
163
-
164
- const queryBuilder = this.getQueryBuilder(fieldtype)
165
-
166
- return {
167
- ...filter,
168
- [fieldName]: queryBuilder({ fieldName, searchKeyword })
169
- }
170
- }, {})
171
- },
172
- allFilters() {
173
- const params = {
174
- page: this.page,
175
- limit: this.pageSize,
176
- orderby: '{"meta.created":-1}',
177
- ...this.filter,
178
- query: { ...this.appliedSearchFilters }
179
- }
180
-
181
- const columns = this.columns || (this.collection.admin && this.collection.admin.columns)
182
-
183
- if (columns) {
184
- params.fields = columns.reduce((map, field) => {
185
- map[field] = 1
186
- return map
187
- }, {})
188
- }
189
-
190
- return params
191
- },
192
- },
193
- watch: {
194
- collectionName: {
195
- handler: 'update'
196
- },
197
- '$route.params.collectionName': {
198
- handler: 'resetFilters'
199
- }
200
- },
201
- mounted() {
202
- this.update()
203
- },
204
- methods: {
205
- getPath(obj, path, defaultValue) {
206
- const result = String.prototype.split.call(path, /[,[\].]+?/)
207
- .filter(Boolean)
208
- .reduce((res, key) => (res !== null && res !== undefined) ? res[key] : res, obj)
209
- return (result === undefined || result === obj) ? defaultValue : result
210
- },
211
- async update() {
212
- if (this.$route.params.collectionName) {
213
- this.collectionName = this.$route.params.collectionName
214
- }
215
- // Avoid refetching schema info if name hasn't changed
216
- if (!this.collection || this.collection._id !== this.collectionName) {
217
- this.collection = (await request({ url: `/collection/${this.collectionName}` })).data
218
- this.schema = this.collection.schema
219
- }
220
- const params = { ...this.allFilters }
221
- const columns = this.columns || (this.collection.admin && this.collection.admin.columns)
222
- const info = (await request({ url: `/${this.collectionName}/`, params })).data
223
- this.count = info.itemsTotal
224
- this.data = this.applyCustomColumnFilter(info.data)
225
- this.listedProperties = columns || Object.keys(this.schema.properties)
226
- },
227
- applyCustomColumnFilter(tableData) {
228
- const customColumnSearches = Object.keys(this.searchFilters)
229
- .filter((fieldName) => this.getFieldType(fieldName) === 'custom')
230
-
231
- return tableData.filter((row) => {
232
- return customColumnSearches.every(fieldName => {
233
- const searchKeyword = this.searchFilters[fieldName]
234
-
235
- if (!searchKeyword) {
236
- return true
237
- }
238
-
239
- const stringified = row[fieldName].toString()
240
-
241
- if (this.exactSearches[fieldName]) {
242
- return stringified === searchKeyword
243
- } else {
244
- return stringified.toLowerCase().includes(searchKeyword.toLowerCase())
245
- }
246
- })
247
- })
248
- },
249
- downloadCSV(data = this.data) {
250
- const collection = this.collectionName
251
- let text = this.listedProperties.map((name) => '"' + name + '"').join(',') + '\n'
252
- if (data) {
253
- text += data.map((row) =>
254
- this.listedProperties.map((key) => '"' + String(objectPath.get(row, key) || '').replace(/"/g, '""') + '"')
255
- ).join('\n')
256
- console.log(text)
257
- const blob = new Blob([text], { type: 'text/plain;charset=utf-8' })
258
- saveAs(blob, collection + '.csv')
259
- }
260
- },
261
- async downloadAllCSV() {
262
- let rows = []
263
- let page = 1
264
-
265
- while (this.count > rows.length) {
266
- const params = {
267
- ...this.allFilters,
268
- page: page,
269
- limit: 500,
270
- }
271
-
272
- const response = (await request({ url: `/${this.collectionName}/`, params })).data
273
- rows = [...response.data, ...rows]
274
- page += 1
275
- }
276
-
277
- this.downloadCSV(this.applyCustomColumnFilter(rows))
278
- },
279
- getFieldType(fieldName) {
280
- return (this.schema.properties[fieldName] &&
281
- this.schema.properties[fieldName].type) || 'custom'
282
- },
283
- getQueryBuilder(fieldType) {
284
- const queryBuilders = {
285
- 'string': this.getStringSearchQuery,
286
- 'number': (payload) => parseInt(payload.searchKeyword)
287
- }
288
-
289
- return queryBuilders[fieldType] || this.getStringSearchQuery
290
- },
291
- getStringSearchQuery({ fieldName, searchKeyword }) {
292
- const regexQuery = { '$regex': searchKeyword, '$options': 'i' }
293
- return this.exactSearches[fieldName] ? searchKeyword : regexQuery
294
- },
295
- resetFilters() {
296
- this.exactSearches = {}
297
- this.searchFilters = {}
298
- }
299
- }
300
- }
301
- </script>
302
-
303
- <style scoped>
304
- .el-table >>> th {
305
- background-color: #343a40 !important;
306
- border-color: #454d55;
307
- color: #fff;
308
- }
309
- </style>
1
+ <template>
2
+ <div class="px-3">
3
+ <div class="d-flex align-items-center py-2">
4
+ <h3 class="mb-0 text-capitalize">
5
+ {{ collectionName }}
6
+ </h3>
7
+
8
+ <span class="mx-auto" />
9
+
10
+ <el-dropdown
11
+ multiple
12
+ class="mr-4"
13
+ :hide-on-click="false"
14
+ >
15
+ <span class="el-dropdown-link">
16
+ Select columns
17
+ <i class="el-icon-arrow-down el-icon--right" />
18
+ </span>
19
+
20
+ <el-dropdown-menu slot="dropdown" style="max-height: 300px; overflow: auto">
21
+ <el-checkbox-group v-model="selectedColumns" @input="update">
22
+ <el-dropdown-item
23
+ v-for="column in allPossibleColumns"
24
+ :key="column"
25
+ :label="column"
26
+ :value="column"
27
+ >
28
+ <el-checkbox
29
+ class="mb-0 mr-4"
30
+ :label="column"
31
+ :disabled="selectedColumns.length === 1 && selectedColumns[0] === column"
32
+ />
33
+ </el-dropdown-item>
34
+ </el-checkbox-group>
35
+ </el-dropdown-menu>
36
+ </el-dropdown>
37
+
38
+ <el-checkbox
39
+ v-model="isFiltersVisible"
40
+ class="mb-0 mr-4"
41
+ >
42
+ Show Filters
43
+ </el-checkbox>
44
+
45
+ <el-pagination
46
+ :page-size="pageSize"
47
+ :total="count"
48
+ :page-sizes="pageSizes"
49
+ :current-page.sync="page"
50
+ layout="sizes, prev, pager, next"
51
+ @current-change="update()"
52
+ @size-change="pageSize = $event, update()"
53
+ />
54
+ </div>
55
+
56
+ <el-table
57
+ :data="tableRows"
58
+ element-loading-text="Loading"
59
+ border
60
+ fit
61
+ highlight-current-row
62
+ @sort-change="handleSortChange"
63
+ >
64
+ <el-table-column
65
+ v-for="(name, i) in selectedColumns"
66
+ :key="name"
67
+ :label="name"
68
+ :prop="name"
69
+ align="center"
70
+ :sortable="sortableFieldTypes.includes(getFieldType(name)) ? 'custom': undefined"
71
+ >
72
+ <template slot-scope="scope">
73
+ <div v-if="scope.$index === 0 && isFiltersVisible" class="text-left">
74
+ <el-input
75
+ v-model="searchFilters[name]"
76
+ :placeholder="`Search by ${name}`"
77
+ :type="getFieldType(name) === 'number' ? 'number' : 'text'"
78
+ @change="update()"
79
+ />
80
+
81
+ <el-checkbox
82
+ v-model="exactSearches[name]"
83
+ :disabled="getFieldType(name) === 'number'"
84
+ class="mt-3"
85
+ @change="update()"
86
+ >
87
+ Exact match
88
+ </el-checkbox>
89
+ </div>
90
+
91
+ <template v-else>
92
+ <router-link v-if="i === 0 && collectionName !== 'requestlog'" :to="'/edit/'+collectionName+'/'+scope.row._id">
93
+ {{ getPath(scope.row, name) }}
94
+ </router-link>
95
+ <router-link v-if="i === 0 && collectionName === 'requestlog'" :to="'/dev/viewrequest/'+scope.row._id">
96
+ {{ getPath(scope.row, name) }}
97
+ </router-link>
98
+ <span v-if="i !== 0">{{ getPath(scope.row, name) }}</span>
99
+ </template>
100
+ </template>
101
+ </el-table-column>
102
+ </el-table>
103
+
104
+ <div class="d-flex align-items-center my-3">
105
+ <router-link v-if="showAddButton" :to="'/edit/' + collectionName + '/create'" class="mr-2">
106
+ <button class="btn btn-primary">
107
+ Add
108
+ </button>
109
+ </router-link>
110
+
111
+ <button class="btn btn-secondary download-button mr-2" @click="downloadCSV()">
112
+ Download
113
+ </button>
114
+
115
+ <button class="btn btn-secondary download-button" @click="downloadAllCSV()">
116
+ Download All
117
+ </button>
118
+
119
+ <span class="mx-auto" />
120
+
121
+ <el-pagination
122
+ :page-size="pageSize"
123
+ :total="count"
124
+ :page-sizes="pageSizes"
125
+ :current-page.sync="page"
126
+ layout="sizes, prev, pager, next"
127
+ @current-change="update()"
128
+ @size-change="pageSize = $event, update()"
129
+ />
130
+ </div>
131
+ </div>
132
+ </template>
133
+
134
+ <script>
135
+ import request from '@/utils/request'
136
+ import saveAs from 'file-saver'
137
+ import objectPath from 'object-path'
138
+
139
+ const pageSizes = [25, 50, 100, 500]
140
+
141
+ export default {
142
+ name: 'ListDocuments',
143
+ props: {
144
+ collectionName: {
145
+ type: String,
146
+ default: 'collection'
147
+ },
148
+ filter: {
149
+ type: Object,
150
+ default: () => {}
151
+ },
152
+ columns: {
153
+ type: Array,
154
+ default: () => undefined
155
+ },
156
+ showAddButton: {
157
+ type: Boolean,
158
+ default: true
159
+ },
160
+ showPaginateOnBottom: {
161
+ type: Boolean,
162
+ default: true
163
+ },
164
+ },
165
+ data: () => ({
166
+ page: 1,
167
+ count: 0,
168
+ data: [],
169
+ schema: {},
170
+ exactSearches: {},
171
+ searchFilters: {},
172
+ pageSizes: pageSizes,
173
+ pageSize: pageSizes[0],
174
+ isFiltersVisible: true,
175
+ sortableFieldTypes: ['number', 'string', 'boolean'],
176
+ orderBy: { order: null, prop: null },
177
+ selectedColumns: [],
178
+ allPossibleColumns: []
179
+ }),
180
+ computed: {
181
+ tableRows() {
182
+ if (!this.isFiltersVisible) {
183
+ return this.data
184
+ }
185
+
186
+ return [
187
+ // INFO: This empty row is used to display the search filters.
188
+ {},
189
+ ...this.data
190
+ ]
191
+ },
192
+ appliedSearchFilters() {
193
+ return Object.keys(this.searchFilters).reduce((filter, fieldName) => {
194
+ const searchKeyword = this.searchFilters[fieldName]
195
+
196
+ const fieldtype = this.getFieldType(fieldName)
197
+
198
+ if (!searchKeyword || fieldtype === 'custom') {
199
+ return filter
200
+ }
201
+
202
+ const queryBuilder = this.getQueryBuilder(fieldtype)
203
+
204
+ return {
205
+ ...filter,
206
+ [fieldName]: queryBuilder({ fieldName, searchKeyword })
207
+ }
208
+ }, {})
209
+ },
210
+ allFilters() {
211
+ let orderby = '{"meta.created":-1}'
212
+
213
+ if (!!this.orderBy.prop && !!this.orderBy.order) {
214
+ const propName = this.orderBy.prop
215
+ const direction = this.orderBy.order === 'descending' ? '-1' : '1'
216
+ orderby = `{"${propName}": ${direction}}`
217
+ }
218
+
219
+ const params = {
220
+ orderby,
221
+ page: this.page,
222
+ limit: this.pageSize,
223
+ ...this.filter,
224
+ query: { ...this.appliedSearchFilters }
225
+ }
226
+
227
+ const columns = this.selectedColumns
228
+
229
+ if (columns) {
230
+ params.fields = columns.reduce((map, field) => {
231
+ map[field] = 1
232
+ return map
233
+ }, {})
234
+ }
235
+
236
+ return params
237
+ },
238
+ },
239
+ watch: {
240
+ collectionName: {
241
+ handler: 'update'
242
+ },
243
+ '$route.params.collectionName': {
244
+ handler: 'resetTable'
245
+ }
246
+ },
247
+ mounted() {
248
+ this.update()
249
+ },
250
+ methods: {
251
+ getPath(obj, path, defaultValue) {
252
+ const result = String.prototype.split.call(path, /[,[\].]+?/)
253
+ .filter(Boolean)
254
+ .reduce((res, key) => (res !== null && res !== undefined) ? res[key] : res, obj)
255
+ return (result === undefined || result === obj) ? defaultValue : result
256
+ },
257
+ handleSortChange(orderBy) {
258
+ this.orderBy = orderBy
259
+ this.update()
260
+ },
261
+ async update() {
262
+ if (this.$route.params.collectionName) {
263
+ this.collectionName = this.$route.params.collectionName
264
+ }
265
+
266
+ // Fetch and Set schema info if name hasn't changed
267
+ if (!this.collection || this.collection._id !== this.collectionName) {
268
+ this.collection = (await request({ url: `/collection/${this.collectionName}` })).data
269
+ this.schema = this.collection.schema
270
+ }
271
+
272
+ // Set columns data if not set.
273
+ if (!this.selectedColumns.length) {
274
+ const columns = this.columns || (this.collection.admin && this.collection.admin.columns)
275
+ this.allPossibleColumns = this.getAllPossibleColumns()
276
+ this.selectedColumns = columns || Object.keys(this.schema.properties)
277
+ }
278
+
279
+ // Fetch and Set table data
280
+ const params = { ...this.allFilters }
281
+ const info = (await request({ url: `/${this.collectionName}/`, params })).data
282
+ this.count = info.itemsTotal
283
+ this.data = this.applyCustomColumnFilter(info.data)
284
+ },
285
+ getAllPossibleColumns() {
286
+ const configuredCols = (this.collection.admin && this.collection.admin.columns) || []
287
+ const allProperties = this.getSchemaProperties(this.schema)
288
+ return Array.from(new Set(configuredCols.concat(allProperties).sort()))
289
+ },
290
+ getSchemaProperties(obj, parent) {
291
+ let keys = []
292
+ const properties = obj.properties
293
+ Object.keys(properties).forEach(key => {
294
+ const fullKey = `${parent ? parent + '.' : ''}${key}`
295
+ if (properties[key].properties) {
296
+ keys = keys.concat(this.getSchemaProperties(properties[key], fullKey))
297
+ } else {
298
+ keys.push(fullKey)
299
+ }
300
+ })
301
+ return keys
302
+ },
303
+ getSchemaProperty(obj, path) {
304
+ const parts = path.split('.')
305
+ let partObj = obj
306
+ for (const part of parts) {
307
+ if (partObj.properties && partObj.properties[part]) {
308
+ partObj = partObj.properties[part]
309
+ } else {
310
+ return
311
+ }
312
+ }
313
+ return partObj
314
+ },
315
+ applyCustomColumnFilter(tableData) {
316
+ const customColumnSearches = Object.keys(this.searchFilters)
317
+ .filter((fieldName) => this.getFieldType(fieldName) === 'custom')
318
+
319
+ return tableData.filter((row) => {
320
+ return customColumnSearches.every(fieldName => {
321
+ const searchKeyword = this.searchFilters[fieldName]
322
+
323
+ if (!searchKeyword) {
324
+ return true
325
+ }
326
+
327
+ const stringified = row[fieldName].toString()
328
+
329
+ if (this.exactSearches[fieldName]) {
330
+ return stringified === searchKeyword
331
+ } else {
332
+ return stringified.toLowerCase().includes(searchKeyword.toLowerCase())
333
+ }
334
+ })
335
+ })
336
+ },
337
+ downloadCSV(data = this.data) {
338
+ const collection = this.collectionName
339
+ let text = this.selectedColumns.map((name) => '"' + name + '"').join(',') + '\n'
340
+ if (data) {
341
+ text += data.map((row) =>
342
+ this.selectedColumns.map((key) => '"' + String(objectPath.get(row, key) || '').replace(/"/g, '""') + '"')
343
+ ).join('\n')
344
+ console.log(text)
345
+ const blob = new Blob([text], { type: 'text/plain;charset=utf-8' })
346
+ saveAs(blob, collection + '.csv')
347
+ }
348
+ },
349
+ async downloadAllCSV() {
350
+ let rows = []
351
+ let page = 1
352
+
353
+ while (this.count > rows.length) {
354
+ const params = {
355
+ ...this.allFilters,
356
+ page: page,
357
+ limit: 500,
358
+ }
359
+
360
+ const response = (await request({ url: `/${this.collectionName}/`, params })).data
361
+ rows = [...response.data, ...rows]
362
+ page += 1
363
+ }
364
+
365
+ this.downloadCSV(this.applyCustomColumnFilter(rows))
366
+ },
367
+ getFieldType(fieldName) {
368
+ const property = this.getSchemaProperty(this.schema, fieldName)
369
+ return (property && property.type) || 'custom'
370
+ },
371
+ getQueryBuilder(fieldType) {
372
+ const queryBuilders = {
373
+ 'string': this.getStringSearchQuery,
374
+ 'number': (payload) => parseInt(payload.searchKeyword)
375
+ }
376
+
377
+ return queryBuilders[fieldType] || this.getStringSearchQuery
378
+ },
379
+ getStringSearchQuery({ fieldName, searchKeyword }) {
380
+ const regexQuery = { '$regex': searchKeyword, '$options': 'i' }
381
+ return this.exactSearches[fieldName] ? searchKeyword : regexQuery
382
+ },
383
+ resetTable() {
384
+ // Reset Filters
385
+ this.exactSearches = {}
386
+ this.searchFilters = {}
387
+ this.selectedColumns = []
388
+
389
+ // Reset pagination
390
+ this.page = 0
391
+ this.pageSize = pageSizes[0]
392
+
393
+ // Reset table data
394
+ this.data = []
395
+ this.count = 0
396
+ }
397
+ }
398
+ }
399
+ </script>
400
+
401
+ <style scoped>
402
+ .el-table >>> th {
403
+ background-color: #343a40 !important;
404
+ border-color: #454d55;
405
+ color: #fff;
406
+ }
407
+ </style>