sanity-plugin-workflow 1.0.0-beta.3 → 1.0.0-beta.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/README.md +6 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.esm.js +1536 -1393
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1535 -1392
- package/lib/index.js.map +1 -1
- package/package.json +4 -3
- package/src/actions/UpdateWorkflow.tsx +22 -24
- package/src/components/DocumentCard/Validate.tsx +21 -0
- package/src/components/DocumentCard/core/PublishedStatus.tsx +10 -3
- package/src/components/DocumentCard/index.tsx +94 -67
- package/src/components/DocumentList.tsx +122 -0
- package/src/components/Filters.tsx +3 -3
- package/src/components/{Validators.tsx → Verify.tsx} +42 -5
- package/src/components/WorkflowTool.tsx +107 -107
- package/src/constants/index.ts +2 -2
- package/src/helpers/filterItemsAndSort.ts +2 -0
- package/src/hooks/useWorkflowDocuments.tsx +6 -6
- package/src/index.ts +5 -5
- package/src/schema/workflow/workflow.metadata.ts +1 -1
- package/src/tools/index.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/actions/RequestReviewAction.js +0 -55
- package/src/actions/index.js +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-workflow",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "A demonstration of a custom content publishing workflow using Sanity.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -49,13 +49,14 @@
|
|
|
49
49
|
"watch": "pkg-utils watch --strict"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"@hello-pangea/dnd": "^16.2.0",
|
|
52
53
|
"@sanity/icons": "^2.2.2",
|
|
53
54
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
55
|
+
"@tanstack/react-virtual": "^3.0.0-beta.54",
|
|
54
56
|
"@types/styled-components": "^5.1.26",
|
|
55
57
|
"framer-motion": "^10.6.1",
|
|
56
58
|
"groq": "^3.3.1",
|
|
57
59
|
"lexorank": "^1.0.5",
|
|
58
|
-
"react-beautiful-dnd": "^13.1.1",
|
|
59
60
|
"react-fast-compare": "^3.2.1",
|
|
60
61
|
"sanity-plugin-utils": "^1.3.0"
|
|
61
62
|
},
|
|
@@ -66,7 +67,6 @@
|
|
|
66
67
|
"@sanity/plugin-kit": "^3.1.4",
|
|
67
68
|
"@sanity/semantic-release-preset": "^4.0.1",
|
|
68
69
|
"@types/react": "^18.0.27",
|
|
69
|
-
"@types/react-beautiful-dnd": "^13.1.2",
|
|
70
70
|
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
|
71
71
|
"@typescript-eslint/parser": "^5.51.0",
|
|
72
72
|
"eslint": "^8.33.0",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"eslint-plugin-prettier": "^4.2.1",
|
|
76
76
|
"eslint-plugin-react": "^7.32.2",
|
|
77
77
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
78
|
+
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
78
79
|
"husky": "^8.0.3",
|
|
79
80
|
"lint-staged": "^13.2.0",
|
|
80
81
|
"npm-run-all": "^4.1.5",
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
import {ArrowRightIcon, ArrowLeftIcon} from '@sanity/icons'
|
|
1
|
+
import {ArrowLeftIcon, ArrowRightIcon} from '@sanity/icons'
|
|
3
2
|
import {useToast} from '@sanity/ui'
|
|
4
3
|
import {useCurrentUser, useValidationStatus} from 'sanity'
|
|
5
4
|
import {DocumentActionProps, useClient} from 'sanity'
|
|
6
5
|
|
|
7
|
-
import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
|
|
8
6
|
import {API_VERSION} from '../constants'
|
|
9
|
-
import {State} from '../types'
|
|
10
7
|
import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
|
|
8
|
+
import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
|
|
9
|
+
import {State} from '../types'
|
|
11
10
|
|
|
11
|
+
// eslint-disable-next-line complexity
|
|
12
12
|
export function UpdateWorkflow(
|
|
13
13
|
props: DocumentActionProps,
|
|
14
14
|
allStates: State[],
|
|
15
15
|
actionState: State
|
|
16
16
|
) {
|
|
17
17
|
const {id, type} = props
|
|
18
|
-
const {validation, isValidating} = useValidationStatus(id, type)
|
|
19
|
-
const hasValidationErrors =
|
|
20
|
-
!isValidating &&
|
|
21
|
-
validation?.length > 0 &&
|
|
22
|
-
validation.find((v) => v.level === 'error')
|
|
23
18
|
|
|
24
19
|
const user = useCurrentUser()
|
|
25
20
|
const client = useClient({apiVersion: API_VERSION})
|
|
@@ -30,6 +25,13 @@ export function UpdateWorkflow(
|
|
|
30
25
|
const {state: currentState} = data
|
|
31
26
|
const {assignees = []} = data?.metadata ?? {}
|
|
32
27
|
|
|
28
|
+
const {validation, isValidating} = useValidationStatus(id, type)
|
|
29
|
+
const hasValidationErrors =
|
|
30
|
+
currentState?.requireValidation &&
|
|
31
|
+
!isValidating &&
|
|
32
|
+
validation?.length > 0 &&
|
|
33
|
+
validation.find((v) => v.level === 'error')
|
|
34
|
+
|
|
33
35
|
if (error) {
|
|
34
36
|
console.error(error)
|
|
35
37
|
}
|
|
@@ -80,8 +82,6 @@ export function UpdateWorkflow(
|
|
|
80
82
|
const DirectionIcon = direction === 'promote' ? ArrowRightIcon : ArrowLeftIcon
|
|
81
83
|
const directionLabel = direction === 'promote' ? 'Promote' : 'Demote'
|
|
82
84
|
|
|
83
|
-
let title = `${directionLabel} State to "${actionState.title}"`
|
|
84
|
-
|
|
85
85
|
const userRoleCanUpdateState =
|
|
86
86
|
user?.roles?.length && actionState?.roles?.length
|
|
87
87
|
? // If the Action state is limited to specific roles
|
|
@@ -93,10 +93,6 @@ export function UpdateWorkflow(
|
|
|
93
93
|
: // No roles specified on the next state, so anyone can update
|
|
94
94
|
actionState?.roles?.length !== 0
|
|
95
95
|
|
|
96
|
-
if (!userRoleCanUpdateState) {
|
|
97
|
-
title = `Your User role cannot ${directionLabel} State to "${actionState.title}"`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
96
|
const actionStateIsAValidTransition =
|
|
101
97
|
currentState?.id && currentState.transitions.length
|
|
102
98
|
? // If the Current State limits transitions to specific States
|
|
@@ -105,10 +101,6 @@ export function UpdateWorkflow(
|
|
|
105
101
|
: // Otherwise this isn't a problem
|
|
106
102
|
true
|
|
107
103
|
|
|
108
|
-
if (!actionStateIsAValidTransition) {
|
|
109
|
-
title = `You cannot ${directionLabel} State to "${actionState.title}" from "${currentState?.title}"`
|
|
110
|
-
}
|
|
111
|
-
|
|
112
104
|
const userAssignmentCanUpdateState = actionState.requireAssignment
|
|
113
105
|
? // If the Action State requires assigned users
|
|
114
106
|
// Check the current user ID is in the assignees array
|
|
@@ -116,11 +108,17 @@ export function UpdateWorkflow(
|
|
|
116
108
|
: // Otherwise this isn't a problem
|
|
117
109
|
true
|
|
118
110
|
|
|
119
|
-
|
|
120
|
-
title = `You must be assigned to the document to ${directionLabel} State to "${actionState.title}"`
|
|
121
|
-
}
|
|
111
|
+
let title = `${directionLabel} State to "${actionState.title}"`
|
|
122
112
|
|
|
123
|
-
if (
|
|
113
|
+
if (!userRoleCanUpdateState) {
|
|
114
|
+
title = `Your User role cannot ${directionLabel} State to "${actionState.title}"`
|
|
115
|
+
} else if (!actionStateIsAValidTransition) {
|
|
116
|
+
title = `You cannot ${directionLabel} State to "${actionState.title}" from "${currentState?.title}"`
|
|
117
|
+
} else if (!userAssignmentCanUpdateState) {
|
|
118
|
+
title = `You must be assigned to the document to ${directionLabel} State to "${actionState.title}"`
|
|
119
|
+
} else if (currentState?.requireValidation && isValidating) {
|
|
120
|
+
title = `Document is validating, cannot ${directionLabel} State to "${actionState.title}"`
|
|
121
|
+
} else if (hasValidationErrors) {
|
|
124
122
|
title = `Document has validation errors, cannot ${directionLabel} State to "${actionState.title}"`
|
|
125
123
|
}
|
|
126
124
|
|
|
@@ -129,7 +127,7 @@ export function UpdateWorkflow(
|
|
|
129
127
|
disabled:
|
|
130
128
|
loading ||
|
|
131
129
|
error ||
|
|
132
|
-
isValidating ||
|
|
130
|
+
(currentState?.requireValidation && isValidating) ||
|
|
133
131
|
hasValidationErrors ||
|
|
134
132
|
!currentState ||
|
|
135
133
|
!userRoleCanUpdateState ||
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {useEffect} from 'react'
|
|
2
|
+
import {useValidationStatus, ValidationStatus} from 'sanity'
|
|
3
|
+
|
|
4
|
+
type ValidateProps = {
|
|
5
|
+
documentId: string
|
|
6
|
+
type: string
|
|
7
|
+
onChange: (validation: ValidationStatus) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Document validation is siloed into its own component
|
|
11
|
+
// Because it's not performant to run on a lot of documents
|
|
12
|
+
export default function Validate(props: ValidateProps) {
|
|
13
|
+
const {documentId, type, onChange} = props
|
|
14
|
+
const {isValidating, validation = []} = useValidationStatus(documentId, type)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
onChange({isValidating, validation})
|
|
18
|
+
}, [onChange, isValidating, validation])
|
|
19
|
+
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {PublishIcon} from '@sanity/icons'
|
|
2
2
|
import {PreviewValue, SanityDocument} from '@sanity/types'
|
|
3
3
|
import {Box, Text, Tooltip} from '@sanity/ui'
|
|
4
|
+
import {TextWithTone} from 'sanity'
|
|
4
5
|
|
|
5
6
|
import {TimeAgo} from './TimeAgo'
|
|
6
|
-
import {TextWithTone} from 'sanity'
|
|
7
7
|
|
|
8
|
-
export function PublishedStatus(props: {
|
|
8
|
+
export function PublishedStatus(props: {
|
|
9
|
+
document?: PreviewValue | Partial<SanityDocument> | null
|
|
10
|
+
}) {
|
|
9
11
|
const {document} = props
|
|
10
12
|
const updatedAt = document && '_updatedAt' in document && document._updatedAt
|
|
11
13
|
|
|
@@ -24,7 +26,12 @@ export function PublishedStatus(props: {document?: PreviewValue | Partial<Sanity
|
|
|
24
26
|
</Box>
|
|
25
27
|
}
|
|
26
28
|
>
|
|
27
|
-
<TextWithTone
|
|
29
|
+
<TextWithTone
|
|
30
|
+
tone="positive"
|
|
31
|
+
dimmed={!document}
|
|
32
|
+
muted={!document}
|
|
33
|
+
size={1}
|
|
34
|
+
>
|
|
28
35
|
<PublishIcon />
|
|
29
36
|
</TextWithTone>
|
|
30
37
|
</Tooltip>
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
/* eslint-disable react/prop-types */
|
|
2
|
-
import {useEffect, useMemo} from 'react'
|
|
3
|
-
import {Box, Card, CardTone, Flex, Stack, useTheme} from '@sanity/ui'
|
|
4
2
|
import {DragHandleIcon} from '@sanity/icons'
|
|
5
|
-
import {
|
|
3
|
+
import {Box, Card, CardTone, Flex, Stack, useTheme} from '@sanity/ui'
|
|
4
|
+
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
5
|
+
import {
|
|
6
|
+
SchemaType,
|
|
7
|
+
useSchema,
|
|
8
|
+
ValidationStatus as ValidationStatusType,
|
|
9
|
+
} from 'sanity'
|
|
6
10
|
import {Preview} from 'sanity'
|
|
7
11
|
|
|
8
|
-
import EditButton from './EditButton'
|
|
9
12
|
import {SanityDocumentWithMetadata, State, User} from '../../types'
|
|
10
13
|
import UserDisplay from '../UserDisplay'
|
|
14
|
+
import CompleteButton from './CompleteButton'
|
|
11
15
|
import {DraftStatus} from './core/DraftStatus'
|
|
12
16
|
import {PublishedStatus} from './core/PublishedStatus'
|
|
17
|
+
import EditButton from './EditButton'
|
|
18
|
+
import Validate from './Validate'
|
|
13
19
|
import {ValidationStatus} from './ValidationStatus'
|
|
14
|
-
import CompleteButton from './CompleteButton'
|
|
15
20
|
|
|
16
21
|
type DocumentCardProps = {
|
|
17
22
|
isDragDisabled: boolean
|
|
@@ -38,6 +43,7 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
38
43
|
} = props
|
|
39
44
|
const {assignees = [], documentId} = item._metadata ?? {}
|
|
40
45
|
const schema = useSchema()
|
|
46
|
+
const state = states.find((s) => s.id === item._metadata?.state)
|
|
41
47
|
|
|
42
48
|
// Perform document operations after State changes
|
|
43
49
|
// If State has changed and the document needs to be un/published
|
|
@@ -80,10 +86,21 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
80
86
|
|
|
81
87
|
const isDarkMode = useTheme().sanity.color.dark
|
|
82
88
|
const defaultCardTone = isDarkMode ? `transparent` : `default`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
|
|
90
|
+
// Validation only runs if the state requests it
|
|
91
|
+
// Because it's not performant to run it on many documents simultaneously
|
|
92
|
+
// So we fake it here, and maybe set it inside <Validate />
|
|
93
|
+
const [optimisticValidation, setOptimisticValidation] =
|
|
94
|
+
useState<ValidationStatusType>({
|
|
95
|
+
isValidating: state?.requireValidation ?? false,
|
|
96
|
+
validation: [],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const {isValidating, validation} = optimisticValidation
|
|
100
|
+
|
|
101
|
+
const handleValidation = useCallback((updates: ValidationStatusType) => {
|
|
102
|
+
setOptimisticValidation(updates)
|
|
103
|
+
}, [])
|
|
87
104
|
|
|
88
105
|
const cardTone = useMemo(() => {
|
|
89
106
|
let tone: CardTone = defaultCardTone
|
|
@@ -92,7 +109,7 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
92
109
|
if (!documentId) return tone
|
|
93
110
|
if (isDragging) tone = `positive`
|
|
94
111
|
|
|
95
|
-
if (!isValidating && validation.length > 0) {
|
|
112
|
+
if (state?.requireValidation && !isValidating && validation.length > 0) {
|
|
96
113
|
if (validation.some((v) => v.level === 'error')) {
|
|
97
114
|
tone = `critical`
|
|
98
115
|
} else {
|
|
@@ -102,13 +119,14 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
102
119
|
|
|
103
120
|
return tone
|
|
104
121
|
}, [
|
|
105
|
-
isDarkMode,
|
|
106
|
-
userRoleCanDrop,
|
|
107
122
|
defaultCardTone,
|
|
123
|
+
userRoleCanDrop,
|
|
124
|
+
isDarkMode,
|
|
108
125
|
documentId,
|
|
109
126
|
isDragging,
|
|
110
|
-
validation,
|
|
111
127
|
isValidating,
|
|
128
|
+
validation,
|
|
129
|
+
state?.requireValidation,
|
|
112
130
|
])
|
|
113
131
|
|
|
114
132
|
// Update validation status
|
|
@@ -136,63 +154,72 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
136
154
|
)
|
|
137
155
|
|
|
138
156
|
return (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
<>
|
|
158
|
+
{state?.requireValidation ? (
|
|
159
|
+
<Validate
|
|
160
|
+
documentId={documentId}
|
|
161
|
+
type={item._type}
|
|
162
|
+
onChange={handleValidation}
|
|
163
|
+
/>
|
|
164
|
+
) : null}
|
|
165
|
+
<Box paddingBottom={3} paddingX={3}>
|
|
166
|
+
<Card radius={2} shadow={isDragging ? 3 : 1} tone={cardTone}>
|
|
167
|
+
<Stack>
|
|
168
|
+
<Card
|
|
169
|
+
borderBottom
|
|
170
|
+
radius={2}
|
|
171
|
+
padding={3}
|
|
172
|
+
paddingLeft={2}
|
|
173
|
+
tone={cardTone}
|
|
174
|
+
style={{pointerEvents: 'none'}}
|
|
175
|
+
>
|
|
176
|
+
<Flex align="center" justify="space-between" gap={1}>
|
|
177
|
+
<Box flex={1}>
|
|
178
|
+
<Preview
|
|
179
|
+
layout="default"
|
|
180
|
+
value={item}
|
|
181
|
+
schemaType={schema.get(item._type) as SchemaType}
|
|
182
|
+
/>
|
|
183
|
+
</Box>
|
|
184
|
+
<Box style={{flexShrink: 0}}>
|
|
185
|
+
{hasError || isDragDisabled ? null : <DragHandleIcon />}
|
|
186
|
+
</Box>
|
|
187
|
+
</Flex>
|
|
188
|
+
</Card>
|
|
189
|
+
|
|
190
|
+
<Card padding={2} radius={2} tone="inherit">
|
|
191
|
+
<Flex align="center" justify="space-between" gap={3}>
|
|
192
|
+
<Box flex={1}>
|
|
193
|
+
{documentId && (
|
|
194
|
+
<UserDisplay
|
|
195
|
+
userList={userList}
|
|
196
|
+
assignees={assignees}
|
|
197
|
+
documentId={documentId}
|
|
198
|
+
disabled={!userRoleCanDrop}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
201
|
+
</Box>
|
|
202
|
+
{validation.length > 0 ? (
|
|
203
|
+
<ValidationStatus validation={validation} />
|
|
204
|
+
) : null}
|
|
205
|
+
<DraftStatus document={item} />
|
|
206
|
+
<PublishedStatus document={item} />
|
|
207
|
+
<EditButton
|
|
208
|
+
id={item._id}
|
|
209
|
+
type={item._type}
|
|
210
|
+
disabled={!userRoleCanDrop}
|
|
156
211
|
/>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{hasError || isDragDisabled ? null : <DragHandleIcon />}
|
|
160
|
-
</Box>
|
|
161
|
-
</Flex>
|
|
162
|
-
</Card>
|
|
163
|
-
|
|
164
|
-
<Card padding={2} radius={2} tone="inherit">
|
|
165
|
-
<Flex align="center" justify="space-between" gap={3}>
|
|
166
|
-
<Box flex={1}>
|
|
167
|
-
{documentId && (
|
|
168
|
-
<UserDisplay
|
|
169
|
-
userList={userList}
|
|
170
|
-
assignees={assignees}
|
|
212
|
+
{isLastState ? (
|
|
213
|
+
<CompleteButton
|
|
171
214
|
documentId={documentId}
|
|
172
215
|
disabled={!userRoleCanDrop}
|
|
173
216
|
/>
|
|
174
|
-
)}
|
|
175
|
-
</
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<EditButton
|
|
182
|
-
id={item._id}
|
|
183
|
-
type={item._type}
|
|
184
|
-
disabled={!userRoleCanDrop}
|
|
185
|
-
/>
|
|
186
|
-
{isLastState ? (
|
|
187
|
-
<CompleteButton
|
|
188
|
-
documentId={documentId}
|
|
189
|
-
disabled={!userRoleCanDrop}
|
|
190
|
-
/>
|
|
191
|
-
) : null}
|
|
192
|
-
</Flex>
|
|
193
|
-
</Card>
|
|
194
|
-
</Stack>
|
|
195
|
-
</Card>
|
|
196
|
-
</Box>
|
|
217
|
+
) : null}
|
|
218
|
+
</Flex>
|
|
219
|
+
</Card>
|
|
220
|
+
</Stack>
|
|
221
|
+
</Card>
|
|
222
|
+
</Box>
|
|
223
|
+
</>
|
|
197
224
|
)
|
|
198
225
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {Draggable} from '@hello-pangea/dnd'
|
|
2
|
+
import {useVirtualizer} from '@tanstack/react-virtual'
|
|
3
|
+
import {useMemo, useRef} from 'react'
|
|
4
|
+
import {CurrentUser} from 'sanity'
|
|
5
|
+
import {UserExtended} from 'sanity-plugin-utils'
|
|
6
|
+
|
|
7
|
+
import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
|
|
8
|
+
import {SanityDocumentWithMetadata, State} from '../types'
|
|
9
|
+
import {DocumentCard} from './DocumentCard'
|
|
10
|
+
|
|
11
|
+
type DocumentListProps = {
|
|
12
|
+
data: SanityDocumentWithMetadata[]
|
|
13
|
+
invalidDocumentIds: string[]
|
|
14
|
+
selectedSchemaTypes: string[]
|
|
15
|
+
selectedUserIds: string[]
|
|
16
|
+
state: State
|
|
17
|
+
states: State[]
|
|
18
|
+
toggleInvalidDocumentId: (
|
|
19
|
+
documentId: string,
|
|
20
|
+
action: 'ADD' | 'REMOVE'
|
|
21
|
+
) => void
|
|
22
|
+
user: CurrentUser | null
|
|
23
|
+
userList: UserExtended[]
|
|
24
|
+
userRoleCanDrop: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function DocumentList(props: DocumentListProps) {
|
|
28
|
+
const {
|
|
29
|
+
data = [],
|
|
30
|
+
invalidDocumentIds,
|
|
31
|
+
selectedSchemaTypes,
|
|
32
|
+
selectedUserIds,
|
|
33
|
+
state,
|
|
34
|
+
states,
|
|
35
|
+
toggleInvalidDocumentId,
|
|
36
|
+
user,
|
|
37
|
+
userList,
|
|
38
|
+
userRoleCanDrop,
|
|
39
|
+
} = props
|
|
40
|
+
|
|
41
|
+
const dataFiltered = useMemo(() => {
|
|
42
|
+
return data.length
|
|
43
|
+
? filterItemsAndSort(data, state.id, selectedUserIds, selectedSchemaTypes)
|
|
44
|
+
: []
|
|
45
|
+
}, [data, selectedSchemaTypes, selectedUserIds, state.id])
|
|
46
|
+
|
|
47
|
+
const parentRef = useRef(null)
|
|
48
|
+
|
|
49
|
+
const rowVirtualizer = useVirtualizer({
|
|
50
|
+
count: data.length,
|
|
51
|
+
getScrollElement: () => parentRef.current,
|
|
52
|
+
getItemKey: (index) => dataFiltered[index]?._metadata?.documentId ?? index,
|
|
53
|
+
estimateSize: () => 113,
|
|
54
|
+
overscan: 5,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!data.length) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
ref={parentRef}
|
|
64
|
+
style={{
|
|
65
|
+
height: `100%`,
|
|
66
|
+
overflow: 'auto',
|
|
67
|
+
paddingTop: 1,
|
|
68
|
+
// Smooths scrollbar behaviour
|
|
69
|
+
overflowAnchor: 'none',
|
|
70
|
+
scrollBehavior: 'auto',
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
{/* {dataFiltered.map((item, itemIndex) => { */}
|
|
74
|
+
{rowVirtualizer.getVirtualItems().map((virtualItem) => {
|
|
75
|
+
const item = dataFiltered[virtualItem.index]
|
|
76
|
+
|
|
77
|
+
const {documentId, assignees} = item?._metadata ?? {}
|
|
78
|
+
|
|
79
|
+
if (!documentId) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isInvalid = invalidDocumentIds.includes(documentId)
|
|
84
|
+
const meInAssignees = user?.id ? assignees?.includes(user.id) : false
|
|
85
|
+
const isDragDisabled =
|
|
86
|
+
!userRoleCanDrop ||
|
|
87
|
+
isInvalid ||
|
|
88
|
+
!(state.requireAssignment
|
|
89
|
+
? state.requireAssignment && meInAssignees
|
|
90
|
+
: true)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Draggable
|
|
94
|
+
// The metadata's documentId is always the published one to avoid rerendering
|
|
95
|
+
key={documentId}
|
|
96
|
+
draggableId={documentId}
|
|
97
|
+
index={virtualItem.index}
|
|
98
|
+
isDragDisabled={isDragDisabled}
|
|
99
|
+
>
|
|
100
|
+
{(draggableProvided, draggableSnapshot) => (
|
|
101
|
+
<div
|
|
102
|
+
ref={draggableProvided.innerRef}
|
|
103
|
+
{...draggableProvided.draggableProps}
|
|
104
|
+
{...draggableProvided.dragHandleProps}
|
|
105
|
+
>
|
|
106
|
+
<DocumentCard
|
|
107
|
+
userRoleCanDrop={userRoleCanDrop}
|
|
108
|
+
isDragDisabled={isDragDisabled}
|
|
109
|
+
isDragging={draggableSnapshot.isDragging}
|
|
110
|
+
item={item}
|
|
111
|
+
toggleInvalidDocumentId={toggleInvalidDocumentId}
|
|
112
|
+
userList={userList}
|
|
113
|
+
states={states}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</Draggable>
|
|
118
|
+
)
|
|
119
|
+
})}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ResetIcon, UserIcon} from '@sanity/icons'
|
|
2
|
+
import {Button, Card, Flex, Menu, MenuButton} from '@sanity/ui'
|
|
3
|
+
import {useCallback} from 'react'
|
|
2
4
|
import {useCurrentUser, UserAvatar, useSchema} from 'sanity'
|
|
3
|
-
import {UserIcon, ResetIcon} from '@sanity/icons'
|
|
4
5
|
import {UserExtended, UserSelectMenu} from 'sanity-plugin-utils'
|
|
5
|
-
import {useCallback} from 'react'
|
|
6
6
|
|
|
7
7
|
type FiltersProps = {
|
|
8
8
|
uniqueAssignedUsers: UserExtended[]
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
+
import {Button, useToast} from '@sanity/ui'
|
|
2
|
+
import {LexoRank} from 'lexorank'
|
|
1
3
|
import React from 'react'
|
|
2
|
-
import {useToast, Button} from '@sanity/ui'
|
|
3
4
|
import {useClient} from 'sanity'
|
|
4
5
|
import {UserExtended} from 'sanity-plugin-utils'
|
|
5
|
-
import {LexoRank} from 'lexorank'
|
|
6
6
|
|
|
7
|
-
import FloatingCard from './FloatingCard'
|
|
8
7
|
import {API_VERSION} from '../constants'
|
|
9
8
|
import {SanityDocumentWithMetadata, State} from '../types'
|
|
9
|
+
import FloatingCard from './FloatingCard'
|
|
10
10
|
|
|
11
|
-
type
|
|
11
|
+
type VerifyProps = {
|
|
12
12
|
data: SanityDocumentWithMetadata[]
|
|
13
13
|
userList: UserExtended[]
|
|
14
14
|
states: State[]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// This component checks the validity of the data in the Kanban
|
|
18
|
+
// It will only render something it there is invalid date
|
|
19
|
+
// And will render buttons to fix the data
|
|
20
|
+
export default function Verify(props: VerifyProps) {
|
|
21
|
+
const {data, userList, states} = props
|
|
18
22
|
const client = useClient({apiVersion: API_VERSION})
|
|
19
23
|
const toast = useToast()
|
|
20
24
|
|
|
@@ -143,6 +147,32 @@ export default function Validators({data, userList, states}: ValidatorsProps) {
|
|
|
143
147
|
[data, client, toast]
|
|
144
148
|
)
|
|
145
149
|
|
|
150
|
+
// A document could be deleted and the workflow metadata left behind
|
|
151
|
+
const orphanedMetadataDocumentIds = React.useMemo(() => {
|
|
152
|
+
return data.length
|
|
153
|
+
? data.filter((doc) => !doc?._id).map((doc) => doc._metadata.documentId)
|
|
154
|
+
: []
|
|
155
|
+
}, [data])
|
|
156
|
+
|
|
157
|
+
const handleOrphans = React.useCallback(() => {
|
|
158
|
+
toast.push({
|
|
159
|
+
title: 'Removing orphaned metadata...',
|
|
160
|
+
status: 'info',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const tx = client.transaction()
|
|
164
|
+
orphanedMetadataDocumentIds.forEach((id) => {
|
|
165
|
+
tx.delete(`workflow-metadata.${id}`)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
tx.commit()
|
|
169
|
+
|
|
170
|
+
toast.push({
|
|
171
|
+
title: `Removed ${orphanedMetadataDocumentIds.length} orphaned metadata documents`,
|
|
172
|
+
status: 'success',
|
|
173
|
+
})
|
|
174
|
+
}, [client, orphanedMetadataDocumentIds, toast])
|
|
175
|
+
|
|
146
176
|
return (
|
|
147
177
|
<FloatingCard>
|
|
148
178
|
{documentsWithoutValidMetadataIds.length > 0 ? (
|
|
@@ -178,6 +208,13 @@ export default function Validators({data, userList, states}: ValidatorsProps) {
|
|
|
178
208
|
}
|
|
179
209
|
/>
|
|
180
210
|
) : null}
|
|
211
|
+
{orphanedMetadataDocumentIds.length > 0 ? (
|
|
212
|
+
<Button
|
|
213
|
+
text="Cleanup orphaned metadata"
|
|
214
|
+
onClick={handleOrphans}
|
|
215
|
+
tone="caution"
|
|
216
|
+
/>
|
|
217
|
+
) : null}
|
|
181
218
|
{/* <Button
|
|
182
219
|
tone="caution"
|
|
183
220
|
onClick={() =>
|