@ossy/resources 3.10.0 → 3.11.1
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/package.json +4 -4
- package/src/CreateDirectory.jsx +47 -53
- package/src/CreateDocument.jsx +1 -0
- package/src/GenericResourceForm.jsx +49 -31
- package/src/ResourceDialogMove.jsx +99 -50
- package/src/ResourceGrid.jsx +36 -84
- package/src/ResourceList.jsx +10 -1
- package/src/ResourcePanel.jsx +96 -17
- package/src/ResourceShareDialog.jsx +50 -12
- package/src/Upload.jsx +25 -8
- package/src/batch-delete.flow.js +81 -0
- package/src/batch-download.flow.js +69 -0
- package/src/cancel-upload.action.js +4 -0
- package/src/cancel-upload.flow.js +40 -0
- package/src/close-share.action.js +4 -0
- package/src/confirm-upload.action.js +4 -0
- package/src/copy-share-link.action.js +4 -0
- package/src/create-directory.action.js +5 -0
- package/src/create-directory.flow.js +30 -0
- package/src/create-directory.form.js +5 -0
- package/src/create-directory.schema.js +9 -0
- package/src/create-document.flow.js +36 -0
- package/src/create-document.form.js +5 -0
- package/src/create-document.schema.js +11 -0
- package/src/create.action.js +5 -1
- package/src/delete-directory.flow.js +39 -0
- package/src/delete-file.flow.js +32 -0
- package/src/done-selecting.action.js +5 -0
- package/src/download-file.flow.js +31 -0
- package/src/download-resource.action.js +4 -0
- package/src/download-selected.action.js +4 -0
- package/src/en.translations.json +18 -0
- package/src/finish-upload.action.js +4 -0
- package/src/index.js +42 -0
- package/src/list.task.js +17 -6
- package/src/list.task.spec.js +81 -0
- package/src/make-private-resource.flow.js +72 -0
- package/src/make-private.action.js +4 -0
- package/src/make-public.action.js +4 -0
- package/src/move-file.flow.js +78 -0
- package/src/move.form.js +5 -0
- package/src/move.schema.js +9 -0
- package/src/open-delete-directory.action.js +7 -0
- package/src/open-move.action.js +4 -0
- package/src/open-remove-selected.action.js +5 -0
- package/src/open-rename.action.js +4 -0
- package/src/open-share.action.js +4 -0
- package/src/open-sort.action.js +5 -0
- package/src/open-view.action.js +5 -0
- package/src/rename-file.flow.js +43 -0
- package/src/rename.form.js +5 -0
- package/src/rename.schema.js +9 -0
- package/src/resource-list-query.js +182 -0
- package/src/resource-list-query.spec.js +75 -0
- package/src/resource-selection.js +25 -0
- package/src/resource-selection.spec.js +40 -0
- package/src/resource.helpers.js +8 -1
- package/src/resources-list-options.js +169 -0
- package/src/resources-list-options.spec.js +141 -0
- package/src/resources.action-media.js +6 -0
- package/src/resources.queries.js +91 -6
- package/src/search-payload.js +10 -0
- package/src/search-payload.spec.js +20 -0
- package/src/search.task.js +2 -1
- package/src/share-resource.flow.js +59 -0
- package/src/sort-ascending.action.js +5 -0
- package/src/sort-by-created.action.js +5 -0
- package/src/sort-by-last-updated.action.js +5 -0
- package/src/sort-by-name.action.js +5 -0
- package/src/sort-by-size.action.js +5 -0
- package/src/sort-by-type.action.js +5 -0
- package/src/sort-descending.action.js +5 -0
- package/src/sort-storage-created.flow.js +87 -0
- package/src/sort-storage-last-updated.flow.js +119 -0
- package/src/sort-storage-name-asc.flow.js +34 -0
- package/src/sort-storage-size-desc.flow.js +34 -0
- package/src/sort-storage-size.flow.js +87 -0
- package/src/sort-storage-type.flow.js +83 -0
- package/src/sort-storage.flow.js +86 -0
- package/src/sv.translations.json +18 -0
- package/src/toggle-selection-mode.action.js +4 -0
- package/src/upload-file.flow.js +44 -0
- package/src/useResourceList.js +94 -0
- package/src/view-grid.action.js +5 -0
- package/src/view-list.action.js +5 -0
- package/src/view-storage.flow.js +49 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_RESOURCE_LIST_LIMIT,
|
|
4
|
+
extractResourceListOptions,
|
|
5
|
+
mergeResourceListParams,
|
|
6
|
+
mongoSortForResourceList,
|
|
7
|
+
paginateResources,
|
|
8
|
+
sortResources,
|
|
9
|
+
} from './resources-list-options.js'
|
|
10
|
+
|
|
11
|
+
describe('extractResourceListOptions', () => {
|
|
12
|
+
it('leaves filter fields and strips list controls', () => {
|
|
13
|
+
const { filter, limit, offset, sortBy, sortOrder, paginated } = extractResourceListOptions({
|
|
14
|
+
location: '/docs/',
|
|
15
|
+
belongsTo: 'ws-1',
|
|
16
|
+
limit: '25',
|
|
17
|
+
offset: '50',
|
|
18
|
+
sortBy: 'created',
|
|
19
|
+
sortOrder: 'desc',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(filter).toEqual({ location: '/docs/', belongsTo: 'ws-1' })
|
|
23
|
+
expect(limit).toBe(25)
|
|
24
|
+
expect(offset).toBe(50)
|
|
25
|
+
expect(sortBy).toBe('created')
|
|
26
|
+
expect(sortOrder).toBe('desc')
|
|
27
|
+
expect(paginated).toBe(true)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('parses sort shorthand and clamps limit', () => {
|
|
31
|
+
const options = extractResourceListOptions({
|
|
32
|
+
sort: 'size:desc',
|
|
33
|
+
limit: 9999,
|
|
34
|
+
offset: -10,
|
|
35
|
+
})
|
|
36
|
+
expect(options.sortBy).toBe('size')
|
|
37
|
+
expect(options.sortOrder).toBe('desc')
|
|
38
|
+
expect(options.limit).toBe(200)
|
|
39
|
+
expect(options.offset).toBe(0)
|
|
40
|
+
expect(options.paginated).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('defaults sortBy to name when paginating without sort', () => {
|
|
44
|
+
const options = extractResourceListOptions({ limit: 10 })
|
|
45
|
+
expect(options.sortBy).toBe('name')
|
|
46
|
+
expect(options.sortOrder).toBe('asc')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('keeps sortBy null when neither sort nor pagination is requested', () => {
|
|
50
|
+
const options = extractResourceListOptions({ location: '/' })
|
|
51
|
+
expect(options.sortBy).toBeNull()
|
|
52
|
+
expect(options.paginated).toBe(false)
|
|
53
|
+
expect(options.limit).toBeNull()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('mergeResourceListParams', () => {
|
|
58
|
+
it('prefers top-level payload list controls over nested query', () => {
|
|
59
|
+
const merged = mergeResourceListParams(
|
|
60
|
+
{
|
|
61
|
+
query: { location: '/a/', limit: 10 },
|
|
62
|
+
location: '/b/',
|
|
63
|
+
limit: 20,
|
|
64
|
+
sortBy: 'name',
|
|
65
|
+
},
|
|
66
|
+
{ offset: 5 },
|
|
67
|
+
)
|
|
68
|
+
expect(merged).toEqual({
|
|
69
|
+
location: '/b/',
|
|
70
|
+
limit: 20,
|
|
71
|
+
sortBy: 'name',
|
|
72
|
+
offset: 5,
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('keeps nested query location when top-level location is absent', () => {
|
|
77
|
+
const merged = mergeResourceListParams(
|
|
78
|
+
{ query: { location: '/nested/' } },
|
|
79
|
+
{ location: '/from-req/', limit: 10 },
|
|
80
|
+
)
|
|
81
|
+
expect(merged.location).toBe('/nested/')
|
|
82
|
+
expect(merged.limit).toBe(10)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('sortResources', () => {
|
|
87
|
+
const resources = [
|
|
88
|
+
{ id: 'f2', name: 'zeta.txt', type: '@ossy/platform/schema/file', created: 2, content: { ContentLength: 10 } },
|
|
89
|
+
{ id: 'd1', name: 'Beta', type: '@ossy/platform/schema/directory', created: 3 },
|
|
90
|
+
{ id: 'f1', name: 'alpha.txt', type: '@ossy/platform/schema/file', created: 1, content: { ContentLength: 30 } },
|
|
91
|
+
{ id: 'd2', name: 'Alpha', type: 'directory', created: 4 },
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
it('keeps directories first and sorts by name ascending', () => {
|
|
95
|
+
expect(sortResources(resources).map(r => r.id)).toEqual(['d2', 'd1', 'f1', 'f2'])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('sorts files by size descending while keeping directories first', () => {
|
|
99
|
+
expect(sortResources(resources, { sortBy: 'size', sortOrder: 'desc' }).map(r => r.id))
|
|
100
|
+
.toEqual(['d2', 'd1', 'f1', 'f2'])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('sorts by created ascending', () => {
|
|
104
|
+
expect(sortResources(resources, { sortBy: 'created', sortOrder: 'asc' }).map(r => r.id))
|
|
105
|
+
.toEqual(['d1', 'd2', 'f1', 'f2'])
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('paginateResources', () => {
|
|
110
|
+
it('slices with total metadata', () => {
|
|
111
|
+
const items = Array.from({ length: 5 }, (_, i) => ({ id: String(i) }))
|
|
112
|
+
expect(paginateResources(items, { limit: 2, offset: 2 })).toEqual({
|
|
113
|
+
items: [{ id: '2' }, { id: '3' }],
|
|
114
|
+
total: 5,
|
|
115
|
+
limit: 2,
|
|
116
|
+
offset: 2,
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('falls back to default page size', () => {
|
|
121
|
+
const items = Array.from({ length: DEFAULT_RESOURCE_LIST_LIMIT + 5 }, (_, i) => ({ id: String(i) }))
|
|
122
|
+
const page = paginateResources(items)
|
|
123
|
+
expect(page.limit).toBe(DEFAULT_RESOURCE_LIST_LIMIT)
|
|
124
|
+
expect(page.items).toHaveLength(DEFAULT_RESOURCE_LIST_LIMIT)
|
|
125
|
+
expect(page.total).toBe(DEFAULT_RESOURCE_LIST_LIMIT + 5)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('mongoSortForResourceList', () => {
|
|
130
|
+
it('builds directory-first mongo sort', () => {
|
|
131
|
+
expect(mongoSortForResourceList({ sortBy: 'name', sortOrder: 'asc' })).toEqual({
|
|
132
|
+
_isDirectory: 1,
|
|
133
|
+
'state.name': 1,
|
|
134
|
+
})
|
|
135
|
+
expect(mongoSortForResourceList({ sortBy: 'size', sortOrder: 'desc' })).toEqual({
|
|
136
|
+
_isDirectory: 1,
|
|
137
|
+
'state.content.ContentLength': -1,
|
|
138
|
+
'state.name': 1,
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -26,6 +26,12 @@ export async function withResourceMedia(result, context) {
|
|
|
26
26
|
if (Array.isArray(result)) {
|
|
27
27
|
return Promise.all(result.map(r => attachOrKeep(r, context)))
|
|
28
28
|
}
|
|
29
|
+
if (result && typeof result === 'object' && Array.isArray(result.items)) {
|
|
30
|
+
return {
|
|
31
|
+
...result,
|
|
32
|
+
items: await Promise.all(result.items.map(r => attachOrKeep(r, context))),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
29
35
|
if (result && typeof result === 'object' && result.id) {
|
|
30
36
|
return attachOrKeep(result, context)
|
|
31
37
|
}
|
package/src/resources.queries.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Aggregate } from '@ossy/event-store'
|
|
2
2
|
import { createLogger } from '@ossy/observability'
|
|
3
|
+
import {
|
|
4
|
+
extractResourceListOptions,
|
|
5
|
+
isDirectoryResourceType,
|
|
6
|
+
mongoSortForResourceList,
|
|
7
|
+
sortResources,
|
|
8
|
+
} from './resources-list-options.js'
|
|
3
9
|
|
|
4
10
|
const log = createLogger('resources')
|
|
5
11
|
|
|
@@ -79,16 +85,95 @@ export class ResourcesQueries {
|
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
static GetResources(_query = {}, user = null) {
|
|
88
|
+
const {
|
|
89
|
+
filter,
|
|
90
|
+
sortBy,
|
|
91
|
+
sortOrder,
|
|
92
|
+
limit,
|
|
93
|
+
offset,
|
|
94
|
+
paginated,
|
|
95
|
+
} = extractResourceListOptions(_query)
|
|
96
|
+
|
|
82
97
|
log.info('[ResourcesQueries][GetResources] Building query')
|
|
83
|
-
const mongoQuery = ResourcesQueries.buildMongoQuery(
|
|
84
|
-
const workspaceScope =
|
|
98
|
+
const mongoQuery = ResourcesQueries.buildMongoQuery(filter, user)
|
|
99
|
+
const workspaceScope = filter.belongsTo ?? '(no workspace scope)'
|
|
85
100
|
log.info(`[ResourcesQueries][GetResources] Fetching resources for ${workspaceScope}`)
|
|
86
101
|
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
if (!paginated && !sortBy) {
|
|
103
|
+
return Aggregate.Collection.find(mongoQuery, { projection: { state: true } }).toArray()
|
|
104
|
+
.then(resources => {
|
|
105
|
+
log.info(`[ResourcesQueries][GetResources] Found ${resources.length} resources`)
|
|
106
|
+
return resources.map(resource => resource.state)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sort = mongoSortForResourceList({ sortBy: sortBy ?? 'name', sortOrder })
|
|
111
|
+
|
|
112
|
+
// Mongo find projections cannot compute fields; use aggregation when sorting/paging.
|
|
113
|
+
const pipeline = [
|
|
114
|
+
{ $match: mongoQuery },
|
|
115
|
+
{
|
|
116
|
+
$addFields: {
|
|
117
|
+
_isDirectory: {
|
|
118
|
+
$cond: [
|
|
119
|
+
{
|
|
120
|
+
$in: [
|
|
121
|
+
'$state.type',
|
|
122
|
+
['directory', '@ossy/platform/schema/directory'],
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
0,
|
|
126
|
+
1,
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{ $sort: sort },
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if (paginated) {
|
|
135
|
+
pipeline.push({ $skip: offset })
|
|
136
|
+
pipeline.push({ $limit: limit })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pipeline.push({ $project: { state: true } })
|
|
140
|
+
|
|
141
|
+
const itemsPromise = Aggregate.Collection.aggregate(pipeline).toArray()
|
|
142
|
+
.then(resources => resources.map(resource => resource.state))
|
|
143
|
+
|
|
144
|
+
if (!paginated) {
|
|
145
|
+
return itemsPromise.then(items => {
|
|
146
|
+
log.info(`[ResourcesQueries][GetResources] Found ${items.length} resources`)
|
|
147
|
+
return items
|
|
91
148
|
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return Promise.all([
|
|
152
|
+
itemsPromise,
|
|
153
|
+
Aggregate.Collection.countDocuments(mongoQuery),
|
|
154
|
+
]).then(([items, total]) => {
|
|
155
|
+
log.info(`[ResourcesQueries][GetResources] Found ${items.length}/${total} resources (limit=${limit}, offset=${offset})`)
|
|
156
|
+
return {
|
|
157
|
+
items,
|
|
158
|
+
total,
|
|
159
|
+
limit,
|
|
160
|
+
offset,
|
|
161
|
+
sortBy: sortBy ?? 'name',
|
|
162
|
+
sortOrder,
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* In-memory folder-first sort for callers that already hold a resource array.
|
|
169
|
+
* Prefer GetResources sort options for large collections.
|
|
170
|
+
*/
|
|
171
|
+
static SortResources(resources, options) {
|
|
172
|
+
return sortResources(resources, options)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static IsDirectoryType(type) {
|
|
176
|
+
return isDirectoryResourceType(type)
|
|
92
177
|
}
|
|
93
178
|
|
|
94
179
|
static GetNestedResources({ location }) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { extractResourceListOptions } from './resources-list-options.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Strip pagination/sort keys from search payloads so callers that accidentally
|
|
5
|
+
* forward list options keep receiving an array (not `{ items, total }`).
|
|
6
|
+
*/
|
|
7
|
+
export function sanitizeSearchPayload (payload = {}) {
|
|
8
|
+
const { filter } = extractResourceListOptions(payload ?? {})
|
|
9
|
+
return filter
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals'
|
|
2
|
+
import { sanitizeSearchPayload } from './search-payload.js'
|
|
3
|
+
|
|
4
|
+
describe('sanitizeSearchPayload', () => {
|
|
5
|
+
it('strips list options so search stays an array-shaped query', () => {
|
|
6
|
+
const result = sanitizeSearchPayload({
|
|
7
|
+
location: '/docs/',
|
|
8
|
+
limit: 10,
|
|
9
|
+
offset: 20,
|
|
10
|
+
sortBy: 'name',
|
|
11
|
+
sortOrder: 'desc',
|
|
12
|
+
q: 'brief',
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
location: '/docs/',
|
|
17
|
+
q: 'brief',
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
})
|
package/src/search.task.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ResourcesQueries } from './resources.queries.js'
|
|
2
2
|
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
3
|
+
import { sanitizeSearchPayload } from './search-payload.js'
|
|
3
4
|
|
|
4
5
|
export const metadata = { id: '@ossy/resources/tasks/search' }
|
|
5
6
|
|
|
6
7
|
export async function run({ payload, req }) {
|
|
7
|
-
const query =
|
|
8
|
+
const query = sanitizeSearchPayload(payload ?? {})
|
|
8
9
|
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
10
|
const user = payload?.user ?? req?.user
|
|
10
11
|
let location = query.location
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import uploadFileFlow from './upload-file.flow.js'
|
|
2
|
+
import { metadata as OpenShare } from './open-share.action.js'
|
|
3
|
+
import { metadata as MakePublic } from './make-public.action.js'
|
|
4
|
+
import { metadata as CopyShareLink } from './copy-share-link.action.js'
|
|
5
|
+
import { metadata as CloseShare } from './close-share.action.js'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
id: '@ossy/resources/flows/share-resource',
|
|
9
|
+
feature: 'resources',
|
|
10
|
+
requires: ['server', 'database'],
|
|
11
|
+
timeout: 150_000,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* After uploading a file, open the share dialog from the resource panel,
|
|
16
|
+
* make the file public, see the copy-link control plus public access marker,
|
|
17
|
+
* then close the share dialog.
|
|
18
|
+
*/
|
|
19
|
+
export default {
|
|
20
|
+
title: 'Share uploaded resource',
|
|
21
|
+
description:
|
|
22
|
+
'Signed-in user uploads a file, opens share from the panel, makes it public, sees the share link controls, then closes the dialog',
|
|
23
|
+
steps: [
|
|
24
|
+
...uploadFileFlow.steps,
|
|
25
|
+
{ click: { selector: '[data-resource-name="e2e-upload.txt"]' } },
|
|
26
|
+
{ result: { action: OpenShare, timeout: 15000 } },
|
|
27
|
+
{ action: OpenShare },
|
|
28
|
+
{
|
|
29
|
+
result: {
|
|
30
|
+
selector: '[data-region="resource-share"][data-share-status="private"]',
|
|
31
|
+
timeout: 15000,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{ result: { action: MakePublic, timeout: 10000 } },
|
|
35
|
+
{ action: MakePublic },
|
|
36
|
+
{
|
|
37
|
+
result: {
|
|
38
|
+
selector: '[data-region="resource-share"][data-share-status="public"]',
|
|
39
|
+
timeout: 20000,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{ result: { action: CopyShareLink, timeout: 10000 } },
|
|
43
|
+
{
|
|
44
|
+
result: {
|
|
45
|
+
selector: '[data-resource-name="e2e-upload.txt"][data-resource-access="public"]',
|
|
46
|
+
timeout: 20000,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{ result: { action: CloseShare, timeout: 10000 } },
|
|
50
|
+
{ action: CloseShare },
|
|
51
|
+
{
|
|
52
|
+
result: {
|
|
53
|
+
selector: '[data-region="resource-share"]',
|
|
54
|
+
hidden: true,
|
|
55
|
+
timeout: 10000,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import signUpFlow from '@ossy/authentication/sign-up.flow.js'
|
|
2
|
+
import { metadata as ConfirmUpload } from './confirm-upload.action.js'
|
|
3
|
+
import { metadata as FinishUpload } from './finish-upload.action.js'
|
|
4
|
+
import { metadata as OpenSort } from './open-sort.action.js'
|
|
5
|
+
import { metadata as SortByCreated } from './sort-by-created.action.js'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
id: '@ossy/resources/flows/sort-storage-created',
|
|
9
|
+
feature: 'resources',
|
|
10
|
+
requires: ['server', 'database'],
|
|
11
|
+
timeout: 180_000,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Upload two files whose name order is opposite their created order, open the
|
|
16
|
+
* storage sort menu, switch to created ascending, and assert list order + markers.
|
|
17
|
+
*/
|
|
18
|
+
export default {
|
|
19
|
+
title: 'Sort storage resources by created',
|
|
20
|
+
description:
|
|
21
|
+
'Signed-in user uploads two files, opens sort, chooses created, and sees created ascending order',
|
|
22
|
+
steps: [
|
|
23
|
+
...signUpFlow.steps,
|
|
24
|
+
{ page: '@upload-media', search: { location: '/' } },
|
|
25
|
+
{ result: { selector: '#upload-resources', timeout: 15000 } },
|
|
26
|
+
{ result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
|
|
27
|
+
{
|
|
28
|
+
files: {
|
|
29
|
+
selector: '#upload-resources',
|
|
30
|
+
items: [{
|
|
31
|
+
name: 'e2e-created-z.txt',
|
|
32
|
+
mimeType: 'text/plain',
|
|
33
|
+
// Uploaded first so created-asc places this before the later "a" file.
|
|
34
|
+
content: 'created first z',
|
|
35
|
+
}],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{ result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
|
|
39
|
+
{ result: { action: ConfirmUpload, timeout: 10000 } },
|
|
40
|
+
{ action: ConfirmUpload },
|
|
41
|
+
{ result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
|
|
42
|
+
{ result: { action: FinishUpload, timeout: 10000 } },
|
|
43
|
+
{ action: FinishUpload },
|
|
44
|
+
{ result: { page: '@storage/home', timeout: 20000 } },
|
|
45
|
+
{ result: { selector: '[data-resource-name="e2e-created-z.txt"]', timeout: 20000 } },
|
|
46
|
+
{ page: '@upload-media', search: { location: '/' } },
|
|
47
|
+
{ result: { selector: '#upload-resources', timeout: 15000 } },
|
|
48
|
+
{ result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
|
|
49
|
+
{
|
|
50
|
+
files: {
|
|
51
|
+
selector: '#upload-resources',
|
|
52
|
+
items: [{
|
|
53
|
+
name: 'e2e-created-a.txt',
|
|
54
|
+
mimeType: 'text/plain',
|
|
55
|
+
content: 'created second a',
|
|
56
|
+
}],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{ result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
|
|
60
|
+
{ result: { action: ConfirmUpload, timeout: 10000 } },
|
|
61
|
+
{ action: ConfirmUpload },
|
|
62
|
+
{ result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
|
|
63
|
+
{ result: { action: FinishUpload, timeout: 10000 } },
|
|
64
|
+
{ action: FinishUpload },
|
|
65
|
+
{ result: { page: '@storage/home', timeout: 20000 } },
|
|
66
|
+
{ result: { selector: '[data-resource-name="e2e-created-a.txt"]', timeout: 20000 } },
|
|
67
|
+
{ result: { selector: '[data-resource-name="e2e-created-z.txt"]', timeout: 20000 } },
|
|
68
|
+
{ result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
|
|
69
|
+
{
|
|
70
|
+
result: {
|
|
71
|
+
selector: '[data-resource-name="e2e-created-a.txt"] ~ [data-resource-name="e2e-created-z.txt"]',
|
|
72
|
+
timeout: 10000,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{ result: { action: OpenSort, timeout: 10000 } },
|
|
76
|
+
{ action: OpenSort },
|
|
77
|
+
{ result: { action: SortByCreated, timeout: 10000 } },
|
|
78
|
+
{ action: SortByCreated },
|
|
79
|
+
{ result: { selector: '[data-sort-by="created"][data-sort-order="asc"]', timeout: 10000 } },
|
|
80
|
+
{
|
|
81
|
+
result: {
|
|
82
|
+
selector: '[data-resource-name="e2e-created-z.txt"] ~ [data-resource-name="e2e-created-a.txt"]',
|
|
83
|
+
timeout: 10000,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import signUpFlow from '@ossy/authentication/sign-up.flow.js'
|
|
2
|
+
import { metadata as ConfirmUpload } from './confirm-upload.action.js'
|
|
3
|
+
import { metadata as FinishUpload } from './finish-upload.action.js'
|
|
4
|
+
import { metadata as OpenRename } from './open-rename.action.js'
|
|
5
|
+
import { metadata as OpenSort } from './open-sort.action.js'
|
|
6
|
+
import { metadata as RenameForm } from './rename.form.js'
|
|
7
|
+
import { metadata as SortByLastUpdated } from './sort-by-last-updated.action.js'
|
|
8
|
+
import { metadata as UpdateResourceName } from './update-name.action.js'
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
id: '@ossy/resources/flows/sort-storage-last-updated',
|
|
12
|
+
feature: 'resources',
|
|
13
|
+
requires: ['server', 'database'],
|
|
14
|
+
timeout: 180_000,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Upload two files in name order, rename the earlier one (a→b, still before z)
|
|
19
|
+
* to bump lastUpdated, open the storage sort menu, switch to last updated
|
|
20
|
+
* ascending, and assert list order is opposite name order.
|
|
21
|
+
*/
|
|
22
|
+
export default {
|
|
23
|
+
title: 'Sort storage resources by last updated',
|
|
24
|
+
description:
|
|
25
|
+
'Signed-in user uploads two files, renames one to bump lastUpdated, opens sort, chooses last updated, and sees last-updated ascending order',
|
|
26
|
+
steps: [
|
|
27
|
+
...signUpFlow.steps,
|
|
28
|
+
{ page: '@upload-media', search: { location: '/' } },
|
|
29
|
+
{ result: { selector: '#upload-resources', timeout: 15000 } },
|
|
30
|
+
{ result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
|
|
31
|
+
{
|
|
32
|
+
files: {
|
|
33
|
+
selector: '#upload-resources',
|
|
34
|
+
items: [{
|
|
35
|
+
name: 'e2e-lu-a.txt',
|
|
36
|
+
mimeType: 'text/plain',
|
|
37
|
+
// Uploaded first so name-asc matches created order; rename later bumps lastUpdated.
|
|
38
|
+
content: 'last updated a',
|
|
39
|
+
}],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{ result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
|
|
43
|
+
{ result: { action: ConfirmUpload, timeout: 10000 } },
|
|
44
|
+
{ action: ConfirmUpload },
|
|
45
|
+
{ result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
|
|
46
|
+
{ result: { action: FinishUpload, timeout: 10000 } },
|
|
47
|
+
{ action: FinishUpload },
|
|
48
|
+
{ result: { page: '@storage/home', timeout: 20000 } },
|
|
49
|
+
{ result: { selector: '[data-resource-name="e2e-lu-a.txt"]', timeout: 20000 } },
|
|
50
|
+
{ page: '@upload-media', search: { location: '/' } },
|
|
51
|
+
{ result: { selector: '#upload-resources', timeout: 15000 } },
|
|
52
|
+
{ result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
|
|
53
|
+
{
|
|
54
|
+
files: {
|
|
55
|
+
selector: '#upload-resources',
|
|
56
|
+
items: [{
|
|
57
|
+
name: 'e2e-lu-z.txt',
|
|
58
|
+
mimeType: 'text/plain',
|
|
59
|
+
content: 'last updated z',
|
|
60
|
+
}],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{ result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
|
|
64
|
+
{ result: { action: ConfirmUpload, timeout: 10000 } },
|
|
65
|
+
{ action: ConfirmUpload },
|
|
66
|
+
{ result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
|
|
67
|
+
{ result: { action: FinishUpload, timeout: 10000 } },
|
|
68
|
+
{ action: FinishUpload },
|
|
69
|
+
{ result: { page: '@storage/home', timeout: 20000 } },
|
|
70
|
+
{ result: { selector: '[data-resource-name="e2e-lu-a.txt"]', timeout: 20000 } },
|
|
71
|
+
{ result: { selector: '[data-resource-name="e2e-lu-z.txt"]', timeout: 20000 } },
|
|
72
|
+
{ result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
|
|
73
|
+
{
|
|
74
|
+
result: {
|
|
75
|
+
selector: '[data-resource-name="e2e-lu-a.txt"] ~ [data-resource-name="e2e-lu-z.txt"]',
|
|
76
|
+
timeout: 10000,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{ click: { selector: '[data-resource-name="e2e-lu-a.txt"]' } },
|
|
80
|
+
{ result: { action: OpenRename, timeout: 15000 } },
|
|
81
|
+
{ action: OpenRename },
|
|
82
|
+
{ result: { action: UpdateResourceName, timeout: 10000 } },
|
|
83
|
+
// Rename keeps alphabetical order before z, but bumps lastUpdated on this file.
|
|
84
|
+
{ form: RenameForm, fields: { name: 'e2e-lu-b.txt' } },
|
|
85
|
+
{ action: UpdateResourceName },
|
|
86
|
+
{
|
|
87
|
+
result: {
|
|
88
|
+
selector: '[data-resource-name="e2e-lu-b.txt"]',
|
|
89
|
+
timeout: 20000,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
result: {
|
|
94
|
+
selector: '[data-resource-name="e2e-lu-a.txt"]',
|
|
95
|
+
hidden: true,
|
|
96
|
+
timeout: 10000,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
result: {
|
|
101
|
+
selector: '[data-resource-name="e2e-lu-b.txt"] ~ [data-resource-name="e2e-lu-z.txt"]',
|
|
102
|
+
timeout: 10000,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{ press: 'Escape' },
|
|
106
|
+
{ result: { action: OpenSort, timeout: 10000 } },
|
|
107
|
+
{ action: OpenSort },
|
|
108
|
+
{ result: { action: SortByLastUpdated, timeout: 10000 } },
|
|
109
|
+
{ action: SortByLastUpdated },
|
|
110
|
+
{ result: { selector: '[data-sort-by="lastUpdated"][data-sort-order="asc"]', timeout: 10000 } },
|
|
111
|
+
{
|
|
112
|
+
result: {
|
|
113
|
+
// z was never renamed, so it sorts before the bumped b under lastUpdated asc.
|
|
114
|
+
selector: '[data-resource-name="e2e-lu-z.txt"] ~ [data-resource-name="e2e-lu-b.txt"]',
|
|
115
|
+
timeout: 10000,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { metadata as OpenSort } from './open-sort.action.js'
|
|
2
|
+
import { metadata as SortAscending } from './sort-ascending.action.js'
|
|
3
|
+
import sortStorageFlow from './sort-storage.flow.js'
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
id: '@ossy/resources/flows/sort-storage-name-asc',
|
|
7
|
+
feature: 'resources',
|
|
8
|
+
requires: ['server', 'database'],
|
|
9
|
+
timeout: 180_000,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* After name-descending sort is established, flip back to ascending and assert
|
|
14
|
+
* the original name order under asc markers.
|
|
15
|
+
*/
|
|
16
|
+
export default {
|
|
17
|
+
title: 'Sort storage resources by name ascending',
|
|
18
|
+
description:
|
|
19
|
+
'Signed-in user sorts by name descending, then switches to ascending and sees the restored name order',
|
|
20
|
+
steps: [
|
|
21
|
+
...sortStorageFlow.steps,
|
|
22
|
+
{ result: { action: OpenSort, timeout: 10000 } },
|
|
23
|
+
{ action: OpenSort },
|
|
24
|
+
{ result: { action: SortAscending, timeout: 10000 } },
|
|
25
|
+
{ action: SortAscending },
|
|
26
|
+
{ result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
|
|
27
|
+
{
|
|
28
|
+
result: {
|
|
29
|
+
selector: '[data-resource-name="e2e-sort-a.txt"] ~ [data-resource-name="e2e-sort-z.txt"]',
|
|
30
|
+
timeout: 10000,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
}
|