sanity-plugin-workflow 1.0.0-beta.4 → 1.0.0-beta.6
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 +13 -6
- package/lib/index.d.ts +1 -0
- package/lib/index.esm.js +166 -107
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +164 -105
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/actions/UpdateWorkflow.tsx +22 -24
- package/src/components/DocumentCard/Validate.tsx +21 -0
- package/src/components/DocumentCard/index.tsx +91 -103
- package/src/components/DocumentList.tsx +1 -1
- package/src/components/StateTitle/index.tsx +31 -26
- package/src/components/{Validators.tsx → Verify.tsx} +29 -2
- package/src/components/WorkflowTool.tsx +15 -6
- package/src/constants/index.ts +1 -1
- package/src/hooks/useWorkflowDocuments.tsx +8 -5
- package/src/types/index.ts +1 -3
- package/src/actions/RequestReviewAction.js +0 -55
- package/src/actions/index.js +0 -21
package/package.json
CHANGED
|
@@ -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,8 +1,12 @@
|
|
|
1
1
|
/* eslint-disable react/prop-types */
|
|
2
2
|
import {DragHandleIcon} from '@sanity/icons'
|
|
3
3
|
import {Box, Card, CardTone, Flex, Stack, useTheme} from '@sanity/ui'
|
|
4
|
-
import {useEffect, useMemo} from 'react'
|
|
5
|
-
import {
|
|
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
12
|
import {SanityDocumentWithMetadata, State, User} from '../../types'
|
|
@@ -11,6 +15,7 @@ import CompleteButton from './CompleteButton'
|
|
|
11
15
|
import {DraftStatus} from './core/DraftStatus'
|
|
12
16
|
import {PublishedStatus} from './core/PublishedStatus'
|
|
13
17
|
import EditButton from './EditButton'
|
|
18
|
+
import Validate from './Validate'
|
|
14
19
|
import {ValidationStatus} from './ValidationStatus'
|
|
15
20
|
|
|
16
21
|
type DocumentCardProps = {
|
|
@@ -38,52 +43,25 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
38
43
|
} = props
|
|
39
44
|
const {assignees = [], documentId} = item._metadata ?? {}
|
|
40
45
|
const schema = useSchema()
|
|
41
|
-
|
|
42
|
-
// Perform document operations after State changes
|
|
43
|
-
// If State has changed and the document needs to be un/published
|
|
44
|
-
// This functionality was deemed too dangerous / unexpected
|
|
45
|
-
// Revisit with improved UX
|
|
46
|
-
// const currentState = useMemo(
|
|
47
|
-
// () => states.find((state) => state.id === item._metadata?.state),
|
|
48
|
-
// [states, item]
|
|
49
|
-
// )
|
|
50
|
-
// const ops = useDocumentOperation(documentId ?? ``, item._type)
|
|
51
|
-
// const toast = useToast()
|
|
52
|
-
|
|
53
|
-
// useEffect(() => {
|
|
54
|
-
// const isDraft = item._id.startsWith('drafts.')
|
|
55
|
-
|
|
56
|
-
// if (isDraft && currentState?.operation === 'publish' && !item?._metadata?.optimistic) {
|
|
57
|
-
// if (!ops.publish.disabled) {
|
|
58
|
-
// ops.publish.execute()
|
|
59
|
-
// toast.push({
|
|
60
|
-
// title: 'Published Document',
|
|
61
|
-
// description: documentId,
|
|
62
|
-
// status: 'success',
|
|
63
|
-
// })
|
|
64
|
-
// }
|
|
65
|
-
// } else if (
|
|
66
|
-
// !isDraft &&
|
|
67
|
-
// currentState?.operation === 'unpublish' &&
|
|
68
|
-
// !item?._metadata?.optimistic
|
|
69
|
-
// ) {
|
|
70
|
-
// if (!ops.unpublish.disabled) {
|
|
71
|
-
// ops.unpublish.execute()
|
|
72
|
-
// toast.push({
|
|
73
|
-
// title: 'Unpublished Document',
|
|
74
|
-
// description: documentId,
|
|
75
|
-
// status: 'success',
|
|
76
|
-
// })
|
|
77
|
-
// }
|
|
78
|
-
// }
|
|
79
|
-
// }, [currentState, documentId, item, ops, toast])
|
|
46
|
+
const state = states.find((s) => s.id === item._metadata?.state)
|
|
80
47
|
|
|
81
48
|
const isDarkMode = useTheme().sanity.color.dark
|
|
82
49
|
const defaultCardTone = isDarkMode ? `transparent` : `default`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
50
|
+
|
|
51
|
+
// Validation only runs if the state requests it
|
|
52
|
+
// Because it's not performant to run it on many documents simultaneously
|
|
53
|
+
// So we fake it here, and maybe set it inside <Validate />
|
|
54
|
+
const [optimisticValidation, setOptimisticValidation] =
|
|
55
|
+
useState<ValidationStatusType>({
|
|
56
|
+
isValidating: state?.requireValidation ?? false,
|
|
57
|
+
validation: [],
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const {isValidating, validation} = optimisticValidation
|
|
61
|
+
|
|
62
|
+
const handleValidation = useCallback((updates: ValidationStatusType) => {
|
|
63
|
+
setOptimisticValidation(updates)
|
|
64
|
+
}, [])
|
|
87
65
|
|
|
88
66
|
const cardTone = useMemo(() => {
|
|
89
67
|
let tone: CardTone = defaultCardTone
|
|
@@ -92,7 +70,7 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
92
70
|
if (!documentId) return tone
|
|
93
71
|
if (isDragging) tone = `positive`
|
|
94
72
|
|
|
95
|
-
if (!isValidating && validation.length > 0) {
|
|
73
|
+
if (state?.requireValidation && !isValidating && validation.length > 0) {
|
|
96
74
|
if (validation.some((v) => v.level === 'error')) {
|
|
97
75
|
tone = `critical`
|
|
98
76
|
} else {
|
|
@@ -102,13 +80,14 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
102
80
|
|
|
103
81
|
return tone
|
|
104
82
|
}, [
|
|
105
|
-
isDarkMode,
|
|
106
|
-
userRoleCanDrop,
|
|
107
83
|
defaultCardTone,
|
|
84
|
+
userRoleCanDrop,
|
|
85
|
+
isDarkMode,
|
|
108
86
|
documentId,
|
|
109
87
|
isDragging,
|
|
110
|
-
validation,
|
|
111
88
|
isValidating,
|
|
89
|
+
validation,
|
|
90
|
+
state?.requireValidation,
|
|
112
91
|
])
|
|
113
92
|
|
|
114
93
|
// Update validation status
|
|
@@ -136,63 +115,72 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
136
115
|
)
|
|
137
116
|
|
|
138
117
|
return (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
118
|
+
<>
|
|
119
|
+
{state?.requireValidation ? (
|
|
120
|
+
<Validate
|
|
121
|
+
documentId={documentId}
|
|
122
|
+
type={item._type}
|
|
123
|
+
onChange={handleValidation}
|
|
124
|
+
/>
|
|
125
|
+
) : null}
|
|
126
|
+
<Box paddingBottom={3} paddingX={3}>
|
|
127
|
+
<Card radius={2} shadow={isDragging ? 3 : 1} tone={cardTone}>
|
|
128
|
+
<Stack>
|
|
129
|
+
<Card
|
|
130
|
+
borderBottom
|
|
131
|
+
radius={2}
|
|
132
|
+
padding={3}
|
|
133
|
+
paddingLeft={2}
|
|
134
|
+
tone={cardTone}
|
|
135
|
+
style={{pointerEvents: 'none'}}
|
|
136
|
+
>
|
|
137
|
+
<Flex align="center" justify="space-between" gap={1}>
|
|
138
|
+
<Box flex={1}>
|
|
139
|
+
<Preview
|
|
140
|
+
layout="default"
|
|
141
|
+
value={item}
|
|
142
|
+
schemaType={schema.get(item._type) as SchemaType}
|
|
143
|
+
/>
|
|
144
|
+
</Box>
|
|
145
|
+
<Box style={{flexShrink: 0}}>
|
|
146
|
+
{hasError || isDragDisabled ? null : <DragHandleIcon />}
|
|
147
|
+
</Box>
|
|
148
|
+
</Flex>
|
|
149
|
+
</Card>
|
|
150
|
+
|
|
151
|
+
<Card padding={2} radius={2} tone="inherit">
|
|
152
|
+
<Flex align="center" justify="space-between" gap={3}>
|
|
153
|
+
<Box flex={1}>
|
|
154
|
+
{documentId && (
|
|
155
|
+
<UserDisplay
|
|
156
|
+
userList={userList}
|
|
157
|
+
assignees={assignees}
|
|
158
|
+
documentId={documentId}
|
|
159
|
+
disabled={!userRoleCanDrop}
|
|
160
|
+
/>
|
|
161
|
+
)}
|
|
162
|
+
</Box>
|
|
163
|
+
{validation.length > 0 ? (
|
|
164
|
+
<ValidationStatus validation={validation} />
|
|
165
|
+
) : null}
|
|
166
|
+
<DraftStatus document={item} />
|
|
167
|
+
<PublishedStatus document={item} />
|
|
168
|
+
<EditButton
|
|
169
|
+
id={item._id}
|
|
170
|
+
type={item._type}
|
|
171
|
+
disabled={!userRoleCanDrop}
|
|
156
172
|
/>
|
|
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}
|
|
173
|
+
{isLastState ? (
|
|
174
|
+
<CompleteButton
|
|
171
175
|
documentId={documentId}
|
|
172
176
|
disabled={!userRoleCanDrop}
|
|
173
177
|
/>
|
|
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>
|
|
178
|
+
) : null}
|
|
179
|
+
</Flex>
|
|
180
|
+
</Card>
|
|
181
|
+
</Stack>
|
|
182
|
+
</Card>
|
|
183
|
+
</Box>
|
|
184
|
+
</>
|
|
197
185
|
)
|
|
198
186
|
}
|
|
@@ -51,7 +51,7 @@ export default function DocumentList(props: DocumentListProps) {
|
|
|
51
51
|
getScrollElement: () => parentRef.current,
|
|
52
52
|
getItemKey: (index) => dataFiltered[index]?._metadata?.documentId ?? index,
|
|
53
53
|
estimateSize: () => 113,
|
|
54
|
-
overscan:
|
|
54
|
+
overscan: 10,
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
if (!data.length) {
|
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import {Flex, Card, Badge, BadgeTone} from '@sanity/ui'
|
|
2
1
|
import {InfoOutlineIcon, UserIcon} from '@sanity/icons'
|
|
2
|
+
import {Badge, BadgeTone, Box, Card, Flex, Text} from '@sanity/ui'
|
|
3
3
|
import styled, {css} from 'styled-components'
|
|
4
4
|
|
|
5
|
+
import {State} from '../../types'
|
|
5
6
|
import {Status} from './Status'
|
|
6
|
-
import {
|
|
7
|
-
// Operation,
|
|
8
|
-
State,
|
|
9
|
-
} from '../../types'
|
|
10
|
-
|
|
11
|
-
type StateTitleProps = {
|
|
12
|
-
state: State
|
|
13
|
-
requireAssignment: boolean
|
|
14
|
-
userRoleCanDrop: boolean
|
|
15
|
-
isDropDisabled: boolean
|
|
16
|
-
draggingFrom: string
|
|
17
|
-
// operation?: Operation
|
|
18
|
-
}
|
|
19
7
|
|
|
20
8
|
const StyledStickyCard = styled(Card)(
|
|
21
9
|
() => css`
|
|
@@ -25,8 +13,24 @@ const StyledStickyCard = styled(Card)(
|
|
|
25
13
|
`
|
|
26
14
|
)
|
|
27
15
|
|
|
16
|
+
type StateTitleProps = {
|
|
17
|
+
state: State
|
|
18
|
+
requireAssignment: boolean
|
|
19
|
+
userRoleCanDrop: boolean
|
|
20
|
+
isDropDisabled: boolean
|
|
21
|
+
draggingFrom: string
|
|
22
|
+
documentCount: number
|
|
23
|
+
}
|
|
24
|
+
|
|
28
25
|
export default function StateTitle(props: StateTitleProps) {
|
|
29
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
state,
|
|
28
|
+
requireAssignment,
|
|
29
|
+
userRoleCanDrop,
|
|
30
|
+
isDropDisabled,
|
|
31
|
+
draggingFrom,
|
|
32
|
+
documentCount,
|
|
33
|
+
} = props
|
|
30
34
|
|
|
31
35
|
let tone: BadgeTone = 'default'
|
|
32
36
|
const isSource = draggingFrom === state.id
|
|
@@ -39,7 +43,11 @@ export default function StateTitle(props: StateTitleProps) {
|
|
|
39
43
|
<StyledStickyCard paddingY={4} padding={3} tone="inherit">
|
|
40
44
|
<Flex gap={3} align="center">
|
|
41
45
|
<Badge
|
|
42
|
-
mode={
|
|
46
|
+
mode={
|
|
47
|
+
(draggingFrom && !isDropDisabled) || isSource
|
|
48
|
+
? 'default'
|
|
49
|
+
: 'outline'
|
|
50
|
+
}
|
|
43
51
|
tone={tone}
|
|
44
52
|
muted={!userRoleCanDrop || isDropDisabled}
|
|
45
53
|
>
|
|
@@ -57,16 +65,13 @@ export default function StateTitle(props: StateTitleProps) {
|
|
|
57
65
|
icon={UserIcon}
|
|
58
66
|
/>
|
|
59
67
|
) : null}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
icon={operation === 'publish' ? PublishIcon : UnpublishIcon}
|
|
68
|
-
/>
|
|
69
|
-
) : null} */}
|
|
68
|
+
<Box flex={1}>
|
|
69
|
+
{documentCount > 0 ? (
|
|
70
|
+
<Text weight="semibold" align="right" size={1}>
|
|
71
|
+
{documentCount}
|
|
72
|
+
</Text>
|
|
73
|
+
) : null}
|
|
74
|
+
</Box>
|
|
70
75
|
</Flex>
|
|
71
76
|
</StyledStickyCard>
|
|
72
77
|
)
|
|
@@ -8,13 +8,17 @@ import {API_VERSION} from '../constants'
|
|
|
8
8
|
import {SanityDocumentWithMetadata, State} from '../types'
|
|
9
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
|
|
|
@@ -47,6 +51,18 @@ export default function Validators({data, userList, states}: ValidatorsProps) {
|
|
|
47
51
|
}, [] as string[])
|
|
48
52
|
: []
|
|
49
53
|
|
|
54
|
+
const documentsWithDuplicatedOrderIds = data?.length
|
|
55
|
+
? data.reduce((acc, cur) => {
|
|
56
|
+
const {documentId, orderRank} = cur._metadata ?? {}
|
|
57
|
+
|
|
58
|
+
return orderRank &&
|
|
59
|
+
data.filter((d) => d._metadata?.orderRank === orderRank).length > 1 &&
|
|
60
|
+
documentId
|
|
61
|
+
? [...acc, documentId]
|
|
62
|
+
: acc
|
|
63
|
+
}, [] as string[])
|
|
64
|
+
: []
|
|
65
|
+
|
|
50
66
|
// Updates metadata documents to a valid, existing state
|
|
51
67
|
const correctDocuments = React.useCallback(
|
|
52
68
|
async (ids: string[]) => {
|
|
@@ -204,6 +220,17 @@ export default function Validators({data, userList, states}: ValidatorsProps) {
|
|
|
204
220
|
}
|
|
205
221
|
/>
|
|
206
222
|
) : null}
|
|
223
|
+
{documentsWithDuplicatedOrderIds.length > 0 ? (
|
|
224
|
+
<Button
|
|
225
|
+
tone="caution"
|
|
226
|
+
onClick={() => addOrderToDocuments(documentsWithDuplicatedOrderIds)}
|
|
227
|
+
text={
|
|
228
|
+
documentsWithDuplicatedOrderIds.length === 1
|
|
229
|
+
? `Set Unique Order for 1 Document`
|
|
230
|
+
: `Set Unique Order for ${documentsWithDuplicatedOrderIds.length} Documents`
|
|
231
|
+
}
|
|
232
|
+
/>
|
|
233
|
+
) : null}
|
|
207
234
|
{orphanedMetadataDocumentIds.length > 0 ? (
|
|
208
235
|
<Button
|
|
209
236
|
text="Cleanup orphaned metadata"
|
|
@@ -19,7 +19,7 @@ import {DocumentCard} from './DocumentCard'
|
|
|
19
19
|
import DocumentList from './DocumentList'
|
|
20
20
|
import Filters from './Filters'
|
|
21
21
|
import StateTitle from './StateTitle'
|
|
22
|
-
import
|
|
22
|
+
import Verify from './Verify'
|
|
23
23
|
|
|
24
24
|
type WorkflowToolProps = {
|
|
25
25
|
tool: Tool<WorkflowConfig>
|
|
@@ -107,7 +107,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
107
107
|
)
|
|
108
108
|
|
|
109
109
|
const handleDragEnd = React.useCallback(
|
|
110
|
-
(result: DropResult) => {
|
|
110
|
+
async (result: DropResult) => {
|
|
111
111
|
// Reset undroppable states
|
|
112
112
|
setUndroppableStates([])
|
|
113
113
|
setDraggingFrom(``)
|
|
@@ -155,12 +155,12 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
155
155
|
// Must be between two items
|
|
156
156
|
const itemBefore = destinationStateItems[destination.index - 1]
|
|
157
157
|
const itemBeforeRank = itemBefore?._metadata?.orderRank
|
|
158
|
-
const itemBeforeRankParsed =
|
|
158
|
+
const itemBeforeRankParsed = itemBeforeRank
|
|
159
159
|
? LexoRank.parse(itemBeforeRank)
|
|
160
160
|
: LexoRank.min()
|
|
161
161
|
const itemAfter = destinationStateItems[destination.index]
|
|
162
162
|
const itemAfterRank = itemAfter?._metadata?.orderRank
|
|
163
|
-
const itemAfterRankParsed =
|
|
163
|
+
const itemAfterRankParsed = itemAfterRank
|
|
164
164
|
? LexoRank.parse(itemAfterRank)
|
|
165
165
|
: LexoRank.max()
|
|
166
166
|
|
|
@@ -249,7 +249,8 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
249
249
|
|
|
250
250
|
return (
|
|
251
251
|
<Flex direction="column" height="fill" overflow="hidden">
|
|
252
|
-
<
|
|
252
|
+
<Verify data={data} userList={userList} states={states} />
|
|
253
|
+
|
|
253
254
|
<Filters
|
|
254
255
|
uniqueAssignedUsers={uniqueAssignedUsers}
|
|
255
256
|
selectedUserIds={selectedUserIds}
|
|
@@ -279,9 +280,16 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
279
280
|
state={state}
|
|
280
281
|
requireAssignment={state.requireAssignment ?? false}
|
|
281
282
|
userRoleCanDrop={userRoleCanDrop}
|
|
282
|
-
// operation={state.operation}
|
|
283
283
|
isDropDisabled={isDropDisabled}
|
|
284
284
|
draggingFrom={draggingFrom}
|
|
285
|
+
documentCount={
|
|
286
|
+
filterItemsAndSort(
|
|
287
|
+
data,
|
|
288
|
+
state.id,
|
|
289
|
+
selectedUserIds,
|
|
290
|
+
selectedSchemaTypes
|
|
291
|
+
).length
|
|
292
|
+
}
|
|
285
293
|
/>
|
|
286
294
|
<Box flex={1}>
|
|
287
295
|
<Droppable
|
|
@@ -289,6 +297,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
289
297
|
isDropDisabled={isDropDisabled}
|
|
290
298
|
// props required for virtualization
|
|
291
299
|
mode="virtual"
|
|
300
|
+
// TODO: Render this as a memo/callback
|
|
292
301
|
renderClone={(provided, snapshot, rubric) => {
|
|
293
302
|
const item = data.find(
|
|
294
303
|
(doc) =>
|
package/src/constants/index.ts
CHANGED
|
@@ -65,7 +65,7 @@ export function useWorkflowDocuments(schemaTypes: string[]): WorkflowDocuments {
|
|
|
65
65
|
}, [data])
|
|
66
66
|
|
|
67
67
|
const move = React.useCallback(
|
|
68
|
-
(
|
|
68
|
+
async (
|
|
69
69
|
draggedId: string,
|
|
70
70
|
destination: DraggableLocation,
|
|
71
71
|
states: State[],
|
|
@@ -122,11 +122,13 @@ export function useWorkflowDocuments(schemaTypes: string[]): WorkflowDocuments {
|
|
|
122
122
|
const {_id, _type} = document
|
|
123
123
|
|
|
124
124
|
// Metadata + useDocumentOperation always uses Published id
|
|
125
|
-
const {
|
|
125
|
+
const {documentId} = document._metadata || {}
|
|
126
126
|
|
|
127
|
-
client
|
|
127
|
+
await client
|
|
128
128
|
.patch(`workflow-metadata.${documentId}`)
|
|
129
|
-
|
|
129
|
+
// Removed because it was effecting fast-updates between columns
|
|
130
|
+
// TODO: Prevent dragging while patching instead while keeping optimistic updates and revert when patch fails
|
|
131
|
+
// .ifRevisionId(document._metadata._rev)
|
|
130
132
|
.set({state: newStateId, orderRank: newOrder})
|
|
131
133
|
.commit()
|
|
132
134
|
.then(() => {
|
|
@@ -135,12 +137,13 @@ export function useWorkflowDocuments(schemaTypes: string[]): WorkflowDocuments {
|
|
|
135
137
|
status: 'success',
|
|
136
138
|
})
|
|
137
139
|
})
|
|
138
|
-
.catch(() => {
|
|
140
|
+
.catch((err) => {
|
|
139
141
|
// Revert optimistic update
|
|
140
142
|
setLocalDocuments(currentLocalData)
|
|
141
143
|
|
|
142
144
|
return toast.push({
|
|
143
145
|
title: `Failed to move to "${newState?.title ?? newStateId}"`,
|
|
146
|
+
description: err.message,
|
|
144
147
|
status: 'error',
|
|
145
148
|
})
|
|
146
149
|
})
|
package/src/types/index.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import {SanityDocumentLike} from 'sanity'
|
|
2
2
|
|
|
3
|
-
// export type Operation = 'publish' | 'unpublish'
|
|
4
|
-
|
|
5
3
|
export type State = {
|
|
6
4
|
id: string
|
|
7
5
|
transitions: string[]
|
|
8
6
|
title: string
|
|
9
|
-
// operation?: Operation
|
|
10
7
|
roles?: string[]
|
|
11
8
|
requireAssignment?: boolean
|
|
9
|
+
requireValidation?: boolean
|
|
12
10
|
// From document badges
|
|
13
11
|
color?: 'primary' | 'success' | 'warning' | 'danger'
|
|
14
12
|
}
|