Activate/Deactivate workflow and Discard Draft (#7022)
## Setup This PR can be tested only if some feature flags have specific values: - `IsWorkflowEnabled` equals `true` - `IsQueryRunnerTwentyORMEnabled` equals `false` These feature flags weren't committed to don't break other branches. ## What this PR brings - Display buttons to activate and deactivate a workflow version and a button to discard the current draft version. I also scaffolded a "Test" button, which doesn't do anything for now. - Wired the activate, deactivate and discard draft buttons to the backend. - Made it possible to "edit" active and deactivated versions by automatically creating a new draft version when the user tries to edit the version. - Hide the "Discard Draft", button if the current version is not a draft or is the first version ever created. - On the backend, don't consider discarded drafts when checking if a new draft version can be created. - On the backend, disallow deleting the first created workflow version. Otherwise, we will end up with a blank canvas in the front end, and it will be impossible to recover from it. - On the backend, disallow running deactivation steps if the workflow version is not currently active. Previously, we were throwing, which is unnecessary as it's a valid case. ## Spotted bugs that we must dive into ### Duplicate workflow versions in Apollo cache https://github.com/user-attachments/assets/7cfffd06-11e0-417a-8da0-f9a5f43b84e2 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
75b493ba6c
commit
729c990546
@ -1,7 +1,6 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { AvatarType } from 'twenty-ui';
|
||||
|
||||
import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment';
|
||||
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
export const mockId = '8f3b2121-f194-4ba4-9fbf-2d5a37126806';
|
||||
@ -48,36 +47,38 @@ export const initialFavorites = [
|
||||
},
|
||||
];
|
||||
|
||||
export const sortedFavorites = [
|
||||
{
|
||||
id: '1',
|
||||
recordId: '2',
|
||||
position: 0,
|
||||
avatarType: 'squared',
|
||||
avatarUrl: undefined,
|
||||
labelIdentifier: 'ABC Corp',
|
||||
link: '/object/company/2',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
recordId: '4',
|
||||
position: 1,
|
||||
avatarType: 'squared',
|
||||
avatarUrl: undefined,
|
||||
labelIdentifier: 'Company Test',
|
||||
link: '/object/company/4',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
position: 2,
|
||||
key: '8f3b2121-f194-4ba4-9fbf-2d5a37126806',
|
||||
labelIdentifier: 'favoriteLabel',
|
||||
avatarUrl: 'example.com',
|
||||
avatarType: 'squared',
|
||||
link: 'example.com',
|
||||
recordId: '1',
|
||||
},
|
||||
];
|
||||
export const sortedFavorites = [
|
||||
{
|
||||
"avatarType": "rounded",
|
||||
"avatarUrl": "",
|
||||
"id": "1",
|
||||
"labelIdentifier": " ",
|
||||
"link": "/object/person/1",
|
||||
"position": 0,
|
||||
"recordId": "1",
|
||||
"workspaceMemberId": undefined,
|
||||
},
|
||||
{
|
||||
"avatarType": "rounded",
|
||||
"avatarUrl": "",
|
||||
"id": "2",
|
||||
"labelIdentifier": " ",
|
||||
"link": "/object/person/3",
|
||||
"position": 1,
|
||||
"recordId": "3",
|
||||
"workspaceMemberId": undefined,
|
||||
},
|
||||
{
|
||||
"avatarType": "squared",
|
||||
"avatarUrl": "example.com",
|
||||
"id": "3",
|
||||
"key": "8f3b2121-f194-4ba4-9fbf-2d5a37126806",
|
||||
"labelIdentifier": "favoriteLabel",
|
||||
"link": "example.com",
|
||||
"position": 2,
|
||||
"recordId": "1",
|
||||
},
|
||||
]
|
||||
|
||||
export const mocks = [
|
||||
{
|
||||
@ -86,132 +87,155 @@ export const mocks = [
|
||||
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
|
||||
createFavorite(data: $input) {
|
||||
__typename
|
||||
noteId
|
||||
taskId
|
||||
myCustomObjectId
|
||||
workspaceMemberId
|
||||
workspaceMember {
|
||||
person {
|
||||
__typename
|
||||
userId
|
||||
updatedAt
|
||||
dateFormat
|
||||
id
|
||||
locale
|
||||
avatarUrl
|
||||
timeZone
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
userEmail
|
||||
createdAt
|
||||
timeFormat
|
||||
colorScheme
|
||||
}
|
||||
companyId
|
||||
myCustomObject {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
position
|
||||
updatedAt
|
||||
name
|
||||
myCustomField
|
||||
id
|
||||
createdAt
|
||||
}
|
||||
updatedAt
|
||||
id
|
||||
opportunity {
|
||||
__typename
|
||||
companyId
|
||||
closeDate
|
||||
stage
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
id
|
||||
updatedAt
|
||||
name
|
||||
createdAt
|
||||
pointOfContactId
|
||||
amount {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
position
|
||||
}
|
||||
noteId
|
||||
note {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
position
|
||||
body
|
||||
updatedAt
|
||||
createdAt
|
||||
title
|
||||
id
|
||||
}
|
||||
personId
|
||||
task {
|
||||
__typename
|
||||
status
|
||||
assigneeId
|
||||
updatedAt
|
||||
body
|
||||
createdAt
|
||||
dueAt
|
||||
position
|
||||
id
|
||||
title
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
}
|
||||
opportunityId
|
||||
position
|
||||
createdAt
|
||||
company {
|
||||
__typename
|
||||
id
|
||||
visaSponsorship
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
domainName {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
introVideo {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
position
|
||||
annualRecurringRevenue {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
employees
|
||||
linkedinLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
workPolicy
|
||||
deletedAt
|
||||
createdAt
|
||||
updatedAt
|
||||
jobTitle
|
||||
intro
|
||||
workPrefereance
|
||||
performanceRating
|
||||
xLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
city
|
||||
companyId
|
||||
phones {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
additionalPhones
|
||||
}
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
id
|
||||
position
|
||||
emails {
|
||||
primaryEmail
|
||||
additionalEmails
|
||||
}
|
||||
avatarUrl
|
||||
whatsapp {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
additionalPhones
|
||||
}
|
||||
}
|
||||
task {
|
||||
__typename
|
||||
updatedAt
|
||||
createdAt
|
||||
deletedAt
|
||||
dueAt
|
||||
id
|
||||
status
|
||||
body
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
assigneeId
|
||||
position
|
||||
title
|
||||
}
|
||||
rocketId
|
||||
viewId
|
||||
updatedAt
|
||||
workflowId
|
||||
personId
|
||||
workspaceMemberId
|
||||
note {
|
||||
__typename
|
||||
deletedAt
|
||||
id
|
||||
position
|
||||
updatedAt
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
body
|
||||
title
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
view {
|
||||
__typename
|
||||
id
|
||||
type
|
||||
icon
|
||||
key
|
||||
isCompact
|
||||
kanbanFieldMetadataId
|
||||
objectMetadataId
|
||||
position
|
||||
createdAt
|
||||
deletedAt
|
||||
updatedAt
|
||||
name
|
||||
}
|
||||
opportunityId
|
||||
position
|
||||
deletedAt
|
||||
id
|
||||
companyId
|
||||
workflow {
|
||||
__typename
|
||||
deletedAt
|
||||
lastPublishedVersionId
|
||||
createdAt
|
||||
id
|
||||
statuses
|
||||
name
|
||||
position
|
||||
updatedAt
|
||||
}
|
||||
workspaceMember {
|
||||
__typename
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
avatarUrl
|
||||
userId
|
||||
createdAt
|
||||
timeZone
|
||||
id
|
||||
timeFormat
|
||||
updatedAt
|
||||
locale
|
||||
userEmail
|
||||
deletedAt
|
||||
colorScheme
|
||||
dateFormat
|
||||
}
|
||||
company {
|
||||
__typename
|
||||
updatedAt
|
||||
domainName {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
visaSponsorship
|
||||
address {
|
||||
addressStreet1
|
||||
addressStreet2
|
||||
@ -222,21 +246,76 @@ export const mocks = [
|
||||
addressLat
|
||||
addressLng
|
||||
}
|
||||
position
|
||||
employees
|
||||
deletedAt
|
||||
accountOwnerId
|
||||
annualRecurringRevenue {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
id
|
||||
name
|
||||
updatedAt
|
||||
xLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
myCustomField
|
||||
createdAt
|
||||
accountOwnerId
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
workPolicy
|
||||
introVideo {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
linkedinLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
tagline
|
||||
idealCustomerProfile
|
||||
}
|
||||
person {
|
||||
${PERSON_FRAGMENT}
|
||||
rocket {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
updatedAt
|
||||
name
|
||||
position
|
||||
createdAt
|
||||
id
|
||||
deletedAt
|
||||
}
|
||||
opportunity {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
amount {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
stage
|
||||
position
|
||||
closeDate
|
||||
id
|
||||
name
|
||||
pointOfContactId
|
||||
companyId
|
||||
updatedAt
|
||||
deletedAt
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -286,132 +365,155 @@ export const mocks = [
|
||||
) {
|
||||
updateFavorite(id: $idToUpdate, data: $input) {
|
||||
__typename
|
||||
noteId
|
||||
taskId
|
||||
myCustomObjectId
|
||||
workspaceMemberId
|
||||
workspaceMember {
|
||||
person {
|
||||
__typename
|
||||
userId
|
||||
updatedAt
|
||||
dateFormat
|
||||
id
|
||||
locale
|
||||
avatarUrl
|
||||
timeZone
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
userEmail
|
||||
createdAt
|
||||
timeFormat
|
||||
colorScheme
|
||||
}
|
||||
companyId
|
||||
myCustomObject {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
position
|
||||
updatedAt
|
||||
name
|
||||
myCustomField
|
||||
id
|
||||
createdAt
|
||||
}
|
||||
updatedAt
|
||||
id
|
||||
opportunity {
|
||||
__typename
|
||||
companyId
|
||||
closeDate
|
||||
stage
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
id
|
||||
updatedAt
|
||||
name
|
||||
createdAt
|
||||
pointOfContactId
|
||||
amount {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
position
|
||||
}
|
||||
noteId
|
||||
note {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
position
|
||||
body
|
||||
updatedAt
|
||||
createdAt
|
||||
title
|
||||
id
|
||||
}
|
||||
personId
|
||||
task {
|
||||
__typename
|
||||
status
|
||||
assigneeId
|
||||
updatedAt
|
||||
body
|
||||
createdAt
|
||||
dueAt
|
||||
position
|
||||
id
|
||||
title
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
}
|
||||
opportunityId
|
||||
position
|
||||
createdAt
|
||||
company {
|
||||
__typename
|
||||
id
|
||||
visaSponsorship
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
domainName {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
introVideo {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
position
|
||||
annualRecurringRevenue {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
employees
|
||||
linkedinLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
workPolicy
|
||||
deletedAt
|
||||
createdAt
|
||||
updatedAt
|
||||
jobTitle
|
||||
intro
|
||||
workPrefereance
|
||||
performanceRating
|
||||
xLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
city
|
||||
companyId
|
||||
phones {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
additionalPhones
|
||||
}
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
id
|
||||
position
|
||||
emails {
|
||||
primaryEmail
|
||||
additionalEmails
|
||||
}
|
||||
avatarUrl
|
||||
whatsapp {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
additionalPhones
|
||||
}
|
||||
}
|
||||
task {
|
||||
__typename
|
||||
updatedAt
|
||||
createdAt
|
||||
deletedAt
|
||||
dueAt
|
||||
id
|
||||
status
|
||||
body
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
assigneeId
|
||||
position
|
||||
title
|
||||
}
|
||||
rocketId
|
||||
viewId
|
||||
updatedAt
|
||||
workflowId
|
||||
personId
|
||||
workspaceMemberId
|
||||
note {
|
||||
__typename
|
||||
deletedAt
|
||||
id
|
||||
position
|
||||
updatedAt
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
body
|
||||
title
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
view {
|
||||
__typename
|
||||
id
|
||||
type
|
||||
icon
|
||||
key
|
||||
isCompact
|
||||
kanbanFieldMetadataId
|
||||
objectMetadataId
|
||||
position
|
||||
createdAt
|
||||
deletedAt
|
||||
updatedAt
|
||||
name
|
||||
}
|
||||
opportunityId
|
||||
position
|
||||
deletedAt
|
||||
id
|
||||
companyId
|
||||
workflow {
|
||||
__typename
|
||||
deletedAt
|
||||
lastPublishedVersionId
|
||||
createdAt
|
||||
id
|
||||
statuses
|
||||
name
|
||||
position
|
||||
updatedAt
|
||||
}
|
||||
workspaceMember {
|
||||
__typename
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
avatarUrl
|
||||
userId
|
||||
createdAt
|
||||
timeZone
|
||||
id
|
||||
timeFormat
|
||||
updatedAt
|
||||
locale
|
||||
userEmail
|
||||
deletedAt
|
||||
colorScheme
|
||||
dateFormat
|
||||
}
|
||||
company {
|
||||
__typename
|
||||
updatedAt
|
||||
domainName {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
visaSponsorship
|
||||
address {
|
||||
addressStreet1
|
||||
addressStreet2
|
||||
@ -422,21 +524,76 @@ export const mocks = [
|
||||
addressLat
|
||||
addressLng
|
||||
}
|
||||
position
|
||||
employees
|
||||
deletedAt
|
||||
accountOwnerId
|
||||
annualRecurringRevenue {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
id
|
||||
name
|
||||
updatedAt
|
||||
xLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
myCustomField
|
||||
createdAt
|
||||
accountOwnerId
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
workPolicy
|
||||
introVideo {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
linkedinLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
tagline
|
||||
idealCustomerProfile
|
||||
}
|
||||
person {
|
||||
${PERSON_FRAGMENT}
|
||||
rocket {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
updatedAt
|
||||
name
|
||||
position
|
||||
createdAt
|
||||
id
|
||||
deletedAt
|
||||
}
|
||||
opportunity {
|
||||
__typename
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
}
|
||||
amount {
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
stage
|
||||
position
|
||||
closeDate
|
||||
id
|
||||
name
|
||||
pointOfContactId
|
||||
companyId
|
||||
updatedAt
|
||||
deletedAt
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems';
|
||||
import {
|
||||
favoriteId,
|
||||
favoriteTargetObjectRecord,
|
||||
@ -29,8 +29,6 @@ jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
|
||||
useFindManyRecords: () => ({ records: initialFavorites }),
|
||||
}));
|
||||
|
||||
const mockObjectMetadataItems = getObjectMetadataItemsMock();
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
@ -51,7 +49,7 @@ describe('useFavorites', () => {
|
||||
setCurrentWorkspaceMember(mockWorkspaceMember);
|
||||
|
||||
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
|
||||
setMetadataItems(mockObjectMetadataItems);
|
||||
setMetadataItems(generatedMockObjectMetadataItems);
|
||||
|
||||
return useFavorites();
|
||||
},
|
||||
@ -72,7 +70,7 @@ describe('useFavorites', () => {
|
||||
setCurrentWorkspaceMember(mockWorkspaceMember);
|
||||
|
||||
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
|
||||
setMetadataItems(mockObjectMetadataItems);
|
||||
setMetadataItems(generatedMockObjectMetadataItems);
|
||||
|
||||
return useFavorites();
|
||||
},
|
||||
@ -100,7 +98,7 @@ describe('useFavorites', () => {
|
||||
setCurrentWorkspaceMember(mockWorkspaceMember);
|
||||
|
||||
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
|
||||
setMetadataItems(mockObjectMetadataItems);
|
||||
setMetadataItems(generatedMockObjectMetadataItems);
|
||||
|
||||
return useFavorites();
|
||||
},
|
||||
@ -125,7 +123,7 @@ describe('useFavorites', () => {
|
||||
setCurrentWorkspaceMember(mockWorkspaceMember);
|
||||
|
||||
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
|
||||
setMetadataItems(mockObjectMetadataItems);
|
||||
setMetadataItems(generatedMockObjectMetadataItems);
|
||||
|
||||
return useFavorites();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user