Compare commits

..
Author SHA1 Message Date
Charles Bochet c537cb6934 chore(geo-map): fix prettier formatting in test files
Made-with: Cursor
2026-04-14 13:28:17 +02:00
Charles Bochet 9cb3c7e67a fix(address): coerce lat/lng to numbers before persist validation
Lat/lng values from the record store can arrive as strings, causing the
Zod addressSchema validation to reject them on manual address edits.
Use existing castAsNumberOrNull utility to ensure proper number types.

Made-with: Cursor
2026-04-14 13:19:24 +02:00
Charles Bochet 4e1d0ef1a2 refactor(geo-map): simplify address parsing and add unit tests
- Replace nested for+switch with flat if+continue using includes
- Extract types into separate files with GeoMap prefix
- Use isNonEmptyArray type guard instead of manual checks
- Convert sanitizePlaceDetailsResults to named object params
- Rename AddressComponents to camelCase
- Add unit tests for both sanitize utils with real Google API data

Made-with: Cursor
2026-04-14 11:57:48 +02:00
Charles Bochet ed48fd96e7 Merge branch 'main' into fix/18860-address-autocomplete-street-format 2026-04-14 11:18:31 +02:00
jeevan6996 71abe38520 chore(address): normalize generated file EOF formatting
Signed-off-by: jeevan6996 <jeevanpawar5890@gmail.com>
2026-04-04 12:35:20 +01:00
jeevan6996 984dc3cf6a chore(address): update generated metadata artifacts
Signed-off-by: jeevan6996 <jeevanpawar5890@gmail.com>
2026-04-04 00:55:49 +01:00
jeevan6996 8b128d7646 fix(address): keep street line clean after autocomplete
Signed-off-by: jeevan6996 <jeevanpawar5890@gmail.com>
2026-04-04 00:31:23 +01:00
160 changed files with 2143 additions and 3119 deletions
@@ -583,7 +583,6 @@ type ViewField {
workspaceId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isActive: Boolean!
deletedAt: DateTime
isOverridden: Boolean @deprecated(reason: "isOverridden is deprecated")
}
@@ -692,7 +691,6 @@ type ViewFieldGroup {
workspaceId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isActive: Boolean!
deletedAt: DateTime
viewFields: [ViewField!]!
isOverridden: Boolean! @deprecated(reason: "isOverridden is deprecated")
@@ -901,7 +899,6 @@ type PageLayoutWidget {
conditionalAvailabilityExpression: String
createdAt: DateTime!
updatedAt: DateTime!
isActive: Boolean!
deletedAt: DateTime
isOverridden: Boolean @deprecated(reason: "isOverridden is deprecated")
}
@@ -1237,7 +1234,6 @@ type PageLayoutTab {
layoutMode: PageLayoutTabLayoutMode
createdAt: DateTime!
updatedAt: DateTime!
isActive: Boolean!
deletedAt: DateTime
isOverridden: Boolean @deprecated(reason: "isOverridden is deprecated")
}
@@ -2197,18 +2193,30 @@ type DeletedWorkspaceMember {
userWorkspaceId: UUID
}
type Relation {
type: RelationType!
sourceObjectMetadata: Object!
targetObjectMetadata: Object!
sourceFieldMetadata: Field!
targetFieldMetadata: Field!
type BillingEntitlement {
key: BillingEntitlementKey!
value: Boolean!
}
"""Relation type"""
enum RelationType {
ONE_TO_MANY
MANY_TO_ONE
enum BillingEntitlementKey {
SSO
CUSTOM_DOMAIN
RLS
AUDIT_LOGS
}
type DomainRecord {
validationType: String!
type: String!
status: String!
key: String!
value: String!
}
type DomainValidRecords {
id: UUID!
domain: String!
records: [DomainRecord!]!
}
type IndexEdge {
@@ -2310,6 +2318,25 @@ type ObjectFieldsConnection {
edges: [FieldEdge!]!
}
type UpsertRowLevelPermissionPredicatesResult {
predicates: [RowLevelPermissionPredicate!]!
predicateGroups: [RowLevelPermissionPredicateGroup!]!
}
type Relation {
type: RelationType!
sourceObjectMetadata: Object!
targetObjectMetadata: Object!
sourceFieldMetadata: Field!
targetFieldMetadata: Field!
}
"""Relation type"""
enum RelationType {
ONE_TO_MANY
MANY_TO_ONE
}
type FieldConnection {
"""Paging information"""
pageInfo: PageInfo!
@@ -2318,37 +2345,6 @@ type FieldConnection {
edges: [FieldEdge!]!
}
type BillingEntitlement {
key: BillingEntitlementKey!
value: Boolean!
}
enum BillingEntitlementKey {
SSO
CUSTOM_DOMAIN
RLS
AUDIT_LOGS
}
type DomainRecord {
validationType: String!
type: String!
status: String!
key: String!
value: String!
}
type DomainValidRecords {
id: UUID!
domain: String!
records: [DomainRecord!]!
}
type UpsertRowLevelPermissionPredicatesResult {
predicates: [RowLevelPermissionPredicate!]!
predicateGroups: [RowLevelPermissionPredicateGroup!]!
}
type AuthToken {
token: String!
expiresAt: DateTime!
@@ -2584,6 +2580,7 @@ type Location {
}
type PlaceDetailsResult {
street: String
state: String
postcode: String
city: String
@@ -415,7 +415,6 @@ export interface ViewField {
workspaceId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isActive: Scalars['Boolean']
deletedAt?: Scalars['DateTime']
/** @deprecated isOverridden is deprecated */
isOverridden?: Scalars['Boolean']
@@ -493,7 +492,6 @@ export interface ViewFieldGroup {
workspaceId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isActive: Scalars['Boolean']
deletedAt?: Scalars['DateTime']
viewFields: ViewField[]
/** @deprecated isOverridden is deprecated */
@@ -676,7 +674,6 @@ export interface PageLayoutWidget {
conditionalAvailabilityExpression?: Scalars['String']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isActive: Scalars['Boolean']
deletedAt?: Scalars['DateTime']
/** @deprecated isOverridden is deprecated */
isOverridden?: Scalars['Boolean']
@@ -962,7 +959,6 @@ export interface PageLayoutTab {
layoutMode?: PageLayoutTabLayoutMode
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isActive: Scalars['Boolean']
deletedAt?: Scalars['DateTime']
/** @deprecated isOverridden is deprecated */
isOverridden?: Scalars['Boolean']
@@ -1883,18 +1879,29 @@ export interface DeletedWorkspaceMember {
__typename: 'DeletedWorkspaceMember'
}
export interface Relation {
type: RelationType
sourceObjectMetadata: Object
targetObjectMetadata: Object
sourceFieldMetadata: Field
targetFieldMetadata: Field
__typename: 'Relation'
export interface BillingEntitlement {
key: BillingEntitlementKey
value: Scalars['Boolean']
__typename: 'BillingEntitlement'
}
export type BillingEntitlementKey = 'SSO' | 'CUSTOM_DOMAIN' | 'RLS' | 'AUDIT_LOGS'
/** Relation type */
export type RelationType = 'ONE_TO_MANY' | 'MANY_TO_ONE'
export interface DomainRecord {
validationType: Scalars['String']
type: Scalars['String']
status: Scalars['String']
key: Scalars['String']
value: Scalars['String']
__typename: 'DomainRecord'
}
export interface DomainValidRecords {
id: Scalars['UUID']
domain: Scalars['String']
records: DomainRecord[]
__typename: 'DomainValidRecords'
}
export interface IndexEdge {
/** The node containing the Index */
@@ -1994,6 +2001,25 @@ export interface ObjectFieldsConnection {
__typename: 'ObjectFieldsConnection'
}
export interface UpsertRowLevelPermissionPredicatesResult {
predicates: RowLevelPermissionPredicate[]
predicateGroups: RowLevelPermissionPredicateGroup[]
__typename: 'UpsertRowLevelPermissionPredicatesResult'
}
export interface Relation {
type: RelationType
sourceObjectMetadata: Object
targetObjectMetadata: Object
sourceFieldMetadata: Field
targetFieldMetadata: Field
__typename: 'Relation'
}
/** Relation type */
export type RelationType = 'ONE_TO_MANY' | 'MANY_TO_ONE'
export interface FieldConnection {
/** Paging information */
pageInfo: PageInfo
@@ -2002,36 +2028,6 @@ export interface FieldConnection {
__typename: 'FieldConnection'
}
export interface BillingEntitlement {
key: BillingEntitlementKey
value: Scalars['Boolean']
__typename: 'BillingEntitlement'
}
export type BillingEntitlementKey = 'SSO' | 'CUSTOM_DOMAIN' | 'RLS' | 'AUDIT_LOGS'
export interface DomainRecord {
validationType: Scalars['String']
type: Scalars['String']
status: Scalars['String']
key: Scalars['String']
value: Scalars['String']
__typename: 'DomainRecord'
}
export interface DomainValidRecords {
id: Scalars['UUID']
domain: Scalars['String']
records: DomainRecord[]
__typename: 'DomainValidRecords'
}
export interface UpsertRowLevelPermissionPredicatesResult {
predicates: RowLevelPermissionPredicate[]
predicateGroups: RowLevelPermissionPredicateGroup[]
__typename: 'UpsertRowLevelPermissionPredicatesResult'
}
export interface AuthToken {
token: Scalars['String']
expiresAt: Scalars['DateTime']
@@ -2297,6 +2293,7 @@ export interface Location {
}
export interface PlaceDetailsResult {
street?: Scalars['String']
state?: Scalars['String']
postcode?: Scalars['String']
city?: Scalars['String']
@@ -3635,7 +3632,6 @@ export interface ViewFieldGenqlSelection{
workspaceId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isActive?: boolean | number
deletedAt?: boolean | number
/** @deprecated isOverridden is deprecated */
isOverridden?: boolean | number
@@ -3710,7 +3706,6 @@ export interface ViewFieldGroupGenqlSelection{
workspaceId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isActive?: boolean | number
deletedAt?: boolean | number
viewFields?: ViewFieldGenqlSelection
/** @deprecated isOverridden is deprecated */
@@ -3885,7 +3880,6 @@ export interface PageLayoutWidgetGenqlSelection{
conditionalAvailabilityExpression?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isActive?: boolean | number
deletedAt?: boolean | number
/** @deprecated isOverridden is deprecated */
isOverridden?: boolean | number
@@ -4198,7 +4192,6 @@ export interface PageLayoutTabGenqlSelection{
layoutMode?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isActive?: boolean | number
deletedAt?: boolean | number
/** @deprecated isOverridden is deprecated */
isOverridden?: boolean | number
@@ -5157,12 +5150,27 @@ export interface DeletedWorkspaceMemberGenqlSelection{
__scalar?: boolean | number
}
export interface RelationGenqlSelection{
export interface BillingEntitlementGenqlSelection{
key?: boolean | number
value?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DomainRecordGenqlSelection{
validationType?: boolean | number
type?: boolean | number
sourceObjectMetadata?: ObjectGenqlSelection
targetObjectMetadata?: ObjectGenqlSelection
sourceFieldMetadata?: FieldGenqlSelection
targetFieldMetadata?: FieldGenqlSelection
status?: boolean | number
key?: boolean | number
value?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DomainValidRecordsGenqlSelection{
id?: boolean | number
domain?: boolean | number
records?: DomainRecordGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -5277,6 +5285,23 @@ export interface ObjectFieldsConnectionGenqlSelection{
__scalar?: boolean | number
}
export interface UpsertRowLevelPermissionPredicatesResultGenqlSelection{
predicates?: RowLevelPermissionPredicateGenqlSelection
predicateGroups?: RowLevelPermissionPredicateGroupGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface RelationGenqlSelection{
type?: boolean | number
sourceObjectMetadata?: ObjectGenqlSelection
targetObjectMetadata?: ObjectGenqlSelection
sourceFieldMetadata?: FieldGenqlSelection
targetFieldMetadata?: FieldGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FieldConnectionGenqlSelection{
/** Paging information */
pageInfo?: PageInfoGenqlSelection
@@ -5286,38 +5311,6 @@ export interface FieldConnectionGenqlSelection{
__scalar?: boolean | number
}
export interface BillingEntitlementGenqlSelection{
key?: boolean | number
value?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DomainRecordGenqlSelection{
validationType?: boolean | number
type?: boolean | number
status?: boolean | number
key?: boolean | number
value?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DomainValidRecordsGenqlSelection{
id?: boolean | number
domain?: boolean | number
records?: DomainRecordGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface UpsertRowLevelPermissionPredicatesResultGenqlSelection{
predicates?: RowLevelPermissionPredicateGenqlSelection
predicateGroups?: RowLevelPermissionPredicateGroupGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AuthTokenGenqlSelection{
token?: boolean | number
expiresAt?: boolean | number
@@ -5616,6 +5609,7 @@ export interface LocationGenqlSelection{
}
export interface PlaceDetailsResultGenqlSelection{
street?: boolean | number
state?: boolean | number
postcode?: boolean | number
city?: boolean | number
@@ -8240,10 +8234,26 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const Relation_possibleTypes: string[] = ['Relation']
export const isRelation = (obj?: { __typename?: any } | null): obj is Relation => {
if (!obj?.__typename) throw new Error('__typename is missing in "isRelation"')
return Relation_possibleTypes.includes(obj.__typename)
const BillingEntitlement_possibleTypes: string[] = ['BillingEntitlement']
export const isBillingEntitlement = (obj?: { __typename?: any } | null): obj is BillingEntitlement => {
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingEntitlement"')
return BillingEntitlement_possibleTypes.includes(obj.__typename)
}
const DomainRecord_possibleTypes: string[] = ['DomainRecord']
export const isDomainRecord = (obj?: { __typename?: any } | null): obj is DomainRecord => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDomainRecord"')
return DomainRecord_possibleTypes.includes(obj.__typename)
}
const DomainValidRecords_possibleTypes: string[] = ['DomainValidRecords']
export const isDomainValidRecords = (obj?: { __typename?: any } | null): obj is DomainValidRecords => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDomainValidRecords"')
return DomainValidRecords_possibleTypes.includes(obj.__typename)
}
@@ -8344,38 +8354,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const FieldConnection_possibleTypes: string[] = ['FieldConnection']
export const isFieldConnection = (obj?: { __typename?: any } | null): obj is FieldConnection => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFieldConnection"')
return FieldConnection_possibleTypes.includes(obj.__typename)
}
const BillingEntitlement_possibleTypes: string[] = ['BillingEntitlement']
export const isBillingEntitlement = (obj?: { __typename?: any } | null): obj is BillingEntitlement => {
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingEntitlement"')
return BillingEntitlement_possibleTypes.includes(obj.__typename)
}
const DomainRecord_possibleTypes: string[] = ['DomainRecord']
export const isDomainRecord = (obj?: { __typename?: any } | null): obj is DomainRecord => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDomainRecord"')
return DomainRecord_possibleTypes.includes(obj.__typename)
}
const DomainValidRecords_possibleTypes: string[] = ['DomainValidRecords']
export const isDomainValidRecords = (obj?: { __typename?: any } | null): obj is DomainValidRecords => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDomainValidRecords"')
return DomainValidRecords_possibleTypes.includes(obj.__typename)
}
const UpsertRowLevelPermissionPredicatesResult_possibleTypes: string[] = ['UpsertRowLevelPermissionPredicatesResult']
export const isUpsertRowLevelPermissionPredicatesResult = (obj?: { __typename?: any } | null): obj is UpsertRowLevelPermissionPredicatesResult => {
if (!obj?.__typename) throw new Error('__typename is missing in "isUpsertRowLevelPermissionPredicatesResult"')
@@ -8384,6 +8362,22 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const Relation_possibleTypes: string[] = ['Relation']
export const isRelation = (obj?: { __typename?: any } | null): obj is Relation => {
if (!obj?.__typename) throw new Error('__typename is missing in "isRelation"')
return Relation_possibleTypes.includes(obj.__typename)
}
const FieldConnection_possibleTypes: string[] = ['FieldConnection']
export const isFieldConnection = (obj?: { __typename?: any } | null): obj is FieldConnection => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFieldConnection"')
return FieldConnection_possibleTypes.includes(obj.__typename)
}
const AuthToken_possibleTypes: string[] = ['AuthToken']
export const isAuthToken = (obj?: { __typename?: any } | null): obj is AuthToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthToken"')
@@ -9584,11 +9578,6 @@ export const enumQueueMetricsTimeRange = {
OneHour: 'OneHour' as const
}
export const enumRelationType = {
ONE_TO_MANY: 'ONE_TO_MANY' as const,
MANY_TO_ONE: 'MANY_TO_ONE' as const
}
export const enumBillingEntitlementKey = {
SSO: 'SSO' as const,
CUSTOM_DOMAIN: 'CUSTOM_DOMAIN' as const,
@@ -9596,6 +9585,11 @@ export const enumBillingEntitlementKey = {
AUDIT_LOGS: 'AUDIT_LOGS' as const
}
export const enumRelationType = {
ONE_TO_MANY: 'ONE_TO_MANY' as const,
MANY_TO_ONE: 'MANY_TO_ONE' as const
}
export const enumEmailingDomainDriver = {
AWS_SES: 'AWS_SES' as const
}
@@ -63,7 +63,7 @@ export default {
209,
221,
238,
253,
255,
292,
293,
303,
@@ -789,10 +789,10 @@ export default {
3
],
"relation": [
237
254
],
"morphRelations": [
237
254
],
"object": [
46
@@ -851,7 +851,7 @@ export default {
36
],
"objectMetadata": [
245,
247,
{
"paging": [
39,
@@ -864,7 +864,7 @@ export default {
}
],
"indexFieldMetadatas": [
243,
245,
{
"paging": [
39,
@@ -1109,7 +1109,7 @@ export default {
37
],
"fields": [
250,
252,
{
"paging": [
39,
@@ -1122,7 +1122,7 @@ export default {
}
],
"indexMetadatas": [
248,
250,
{
"paging": [
39,
@@ -1283,9 +1283,6 @@ export default {
"updatedAt": [
4
],
"isActive": [
6
],
"deletedAt": [
4
],
@@ -1459,9 +1456,6 @@ export default {
"updatedAt": [
4
],
"isActive": [
6
],
"deletedAt": [
4
],
@@ -1709,7 +1703,7 @@ export default {
131
],
"billingEntitlements": [
252
237
],
"hasValidEnterpriseKey": [
6
@@ -1909,9 +1903,6 @@ export default {
"updatedAt": [
4
],
"isActive": [
6
],
"deletedAt": [
4
],
@@ -2570,9 +2561,6 @@ export default {
"updatedAt": [
4
],
"isActive": [
6
],
"deletedAt": [
4
],
@@ -4431,179 +4419,9 @@ export default {
1
]
},
"Relation": {
"type": [
238
],
"sourceObjectMetadata": [
46
],
"targetObjectMetadata": [
46
],
"sourceFieldMetadata": [
34
],
"targetFieldMetadata": [
34
],
"__typename": [
1
]
},
"RelationType": {},
"IndexEdge": {
"node": [
37
],
"cursor": [
40
],
"__typename": [
1
]
},
"PageInfo": {
"hasNextPage": [
6
],
"hasPreviousPage": [
6
],
"startCursor": [
40
],
"endCursor": [
40
],
"__typename": [
1
]
},
"IndexConnection": {
"pageInfo": [
240
],
"edges": [
239
],
"__typename": [
1
]
},
"IndexFieldEdge": {
"node": [
36
],
"cursor": [
40
],
"__typename": [
1
]
},
"IndexIndexFieldMetadatasConnection": {
"pageInfo": [
240
],
"edges": [
242
],
"__typename": [
1
]
},
"ObjectEdge": {
"node": [
46
],
"cursor": [
40
],
"__typename": [
1
]
},
"IndexObjectMetadataConnection": {
"pageInfo": [
240
],
"edges": [
244
],
"__typename": [
1
]
},
"ObjectRecordCount": {
"objectNamePlural": [
1
],
"totalCount": [
21
],
"__typename": [
1
]
},
"ObjectConnection": {
"pageInfo": [
240
],
"edges": [
244
],
"__typename": [
1
]
},
"ObjectIndexMetadatasConnection": {
"pageInfo": [
240
],
"edges": [
239
],
"__typename": [
1
]
},
"FieldEdge": {
"node": [
34
],
"cursor": [
40
],
"__typename": [
1
]
},
"ObjectFieldsConnection": {
"pageInfo": [
240
],
"edges": [
249
],
"__typename": [
1
]
},
"FieldConnection": {
"pageInfo": [
240
],
"edges": [
249
],
"__typename": [
1
]
},
"BillingEntitlement": {
"key": [
253
238
],
"value": [
6
@@ -4641,7 +4459,145 @@ export default {
1
],
"records": [
254
239
],
"__typename": [
1
]
},
"IndexEdge": {
"node": [
37
],
"cursor": [
40
],
"__typename": [
1
]
},
"PageInfo": {
"hasNextPage": [
6
],
"hasPreviousPage": [
6
],
"startCursor": [
40
],
"endCursor": [
40
],
"__typename": [
1
]
},
"IndexConnection": {
"pageInfo": [
242
],
"edges": [
241
],
"__typename": [
1
]
},
"IndexFieldEdge": {
"node": [
36
],
"cursor": [
40
],
"__typename": [
1
]
},
"IndexIndexFieldMetadatasConnection": {
"pageInfo": [
242
],
"edges": [
244
],
"__typename": [
1
]
},
"ObjectEdge": {
"node": [
46
],
"cursor": [
40
],
"__typename": [
1
]
},
"IndexObjectMetadataConnection": {
"pageInfo": [
242
],
"edges": [
246
],
"__typename": [
1
]
},
"ObjectRecordCount": {
"objectNamePlural": [
1
],
"totalCount": [
21
],
"__typename": [
1
]
},
"ObjectConnection": {
"pageInfo": [
242
],
"edges": [
246
],
"__typename": [
1
]
},
"ObjectIndexMetadatasConnection": {
"pageInfo": [
242
],
"edges": [
241
],
"__typename": [
1
]
},
"FieldEdge": {
"node": [
34
],
"cursor": [
40
],
"__typename": [
1
]
},
"ObjectFieldsConnection": {
"pageInfo": [
242
],
"edges": [
251
],
"__typename": [
1
@@ -4658,6 +4614,38 @@ export default {
1
]
},
"Relation": {
"type": [
255
],
"sourceObjectMetadata": [
46
],
"targetObjectMetadata": [
46
],
"sourceFieldMetadata": [
34
],
"targetFieldMetadata": [
34
],
"__typename": [
1
]
},
"RelationType": {},
"FieldConnection": {
"pageInfo": [
242
],
"edges": [
251
],
"__typename": [
1
]
},
"AuthToken": {
"token": [
1
@@ -5170,6 +5158,9 @@ export default {
]
},
"PlaceDetailsResult": {
"street": [
1
],
"state": [
1
],
@@ -5987,7 +5978,7 @@ export default {
},
"AgentChatThreadConnection": {
"pageInfo": [
240
242
],
"edges": [
335
@@ -6594,7 +6585,7 @@ export default {
}
],
"objectRecordCounts": [
246
248
],
"object": [
46,
@@ -6606,7 +6597,7 @@ export default {
}
],
"objects": [
247,
249,
{
"paging": [
39,
@@ -6628,7 +6619,7 @@ export default {
}
],
"indexMetadatas": [
241,
243,
{
"paging": [
39,
@@ -6677,7 +6668,7 @@ export default {
}
],
"fields": [
251,
256,
{
"paging": [
39,
@@ -8352,7 +8343,7 @@ export default {
}
],
"upsertRowLevelPermissionPredicates": [
256,
253,
{
"input": [
456,
@@ -9094,7 +9085,7 @@ export default {
66
],
"checkCustomDomainValidRecords": [
255
240
],
"createOIDCIdentityProvider": [
232,
@@ -9427,7 +9418,7 @@ export default {
}
],
"checkPublicDomainValidRecords": [
255,
240,
{
"domain": [
1,
@@ -87,7 +87,7 @@ npmye yayımlamak, uygulamanızın Twenty pazaryerinde keşfedilebilir olmas
### Gereksinimler
* Bir [npm](https://www.npmjs.com) hesabı
* `package.json` içindeki `keywords` dizinizdeki `twenty-app` anahtar sözcüğü (elle ekleyin — varsayılan olarak `create-twenty-app` şablonunda yer almaz)
* The `twenty-app` keyword in your `package.json` `keywords` array (add it manually — it is not included by default in the `create-twenty-app` template)
```json filename="package.json"
{
File diff suppressed because one or more lines are too long
@@ -14,7 +14,6 @@ export const PAGE_LAYOUT_TAB_FRAGMENT = gql`
...PageLayoutWidgetFragment
}
pageLayoutId
isActive
createdAt
updatedAt
}
@@ -132,18 +132,20 @@ describe('geo-map GraphQL queries', () => {
queryDefinition.selectionSet.selections[0].selectionSet;
const fields = selectionSet.selections.map((s: any) => s.name.value);
expect(fields).toContain('street');
expect(fields).toContain('state');
expect(fields).toContain('postcode');
expect(fields).toContain('city');
expect(fields).toContain('country');
expect(fields).toContain('location');
expect(fields).toHaveLength(5);
expect(fields).toHaveLength(6);
});
it('should match the expected query string', () => {
const expectedQuery = gql`
query GetAddressDetails($placeId: String!, $token: String!) {
getAddressDetails(placeId: $placeId, token: $token) {
street
state
postcode
city
@@ -22,6 +22,7 @@ export const GET_AUTOCOMPLETE_QUERY = gql`
export const GET_PLACE_DETAILS_QUERY = gql`
query GetAddressDetails($placeId: String!, $token: String!) {
getAddressDetails(placeId: $placeId, token: $token) {
street
state
postcode
city
@@ -13,6 +13,7 @@ export type PlaceAutocompleteResult = {
};
export type PlaceDetailsResult = {
street?: string;
state?: string;
postcode?: string;
city?: string;
@@ -79,9 +79,7 @@ export const splitViewWithRelated = (
const { viewFields: _viewFields, ...viewFieldGroupProperties } =
viewFieldGroup;
flatViewFieldGroups.push({
...viewFieldGroupProperties,
});
flatViewFieldGroups.push(viewFieldGroupProperties);
}
}
@@ -7,6 +7,7 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useContext } from 'react';
import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null';
export const AddressFieldInput = () => {
const { draftValue, setDraftValue, fieldDefinition } = useAddressField();
@@ -25,8 +26,8 @@ export const AddressFieldInput = () => {
addressState: newAddress?.addressState ?? null,
addressCountry: newAddress?.addressCountry ?? null,
addressPostcode: newAddress?.addressPostcode ?? null,
addressLat: newAddress?.addressLat ?? null,
addressLng: newAddress?.addressLng ?? null,
addressLat: castAsNumberOrNull(newAddress?.addressLat),
addressLng: castAsNumberOrNull(newAddress?.addressLng),
};
};
const settings = fieldDefinition.metadata.settings;
@@ -58,7 +58,6 @@ const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
height: ${TAB_LIST_HEIGHT};
padding-left: ${themeCssVariables.spacing[2]};
position: relative;
user-select: none;
width: 100%;
@@ -28,6 +28,7 @@ import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { SidePanelPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useIsMobile } from 'twenty-ui/utilities';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
@@ -46,6 +47,10 @@ const StyledTabsAndDashboardContainer = styled.div`
overflow: hidden;
`;
const StyledPageLayoutTabListContainer = styled.div`
padding-left: ${themeCssVariables.spacing[2]};
`;
const StyledScrollWrapperContainer = styled.div`
flex: 1;
min-height: 0;
@@ -163,25 +168,27 @@ export const PageLayoutTabsRenderer = () => {
}
/>
{(sortedTabs.length > 1 || isPageLayoutInEditMode) && (
<PageLayoutTabList
tabs={sortedTabs}
behaveAsLinks={!isInSidePanel && !isPageLayoutInEditMode}
isInSidePanel={isInSidePanel}
componentInstanceId={tabListInstanceId}
onAddTab={handleAddTab}
isReorderEnabled={canEnableTabEditing}
onReorder={
canEnableTabEditing
? (result, provided) =>
reorderRecordPageTabs(
result,
provided,
isDefined(pinnedLeftTab),
)
: undefined
}
pageLayoutType={currentPageLayout.type}
/>
<StyledPageLayoutTabListContainer>
<PageLayoutTabList
tabs={sortedTabs}
behaveAsLinks={!isInSidePanel && !isPageLayoutInEditMode}
isInSidePanel={isInSidePanel}
componentInstanceId={tabListInstanceId}
onAddTab={handleAddTab}
isReorderEnabled={canEnableTabEditing}
onReorder={
canEnableTabEditing
? (result, provided) =>
reorderRecordPageTabs(
result,
provided,
isDefined(pinnedLeftTab),
)
: undefined
}
pageLayoutType={currentPageLayout.type}
/>
</StyledPageLayoutTabListContainer>
)}
<StyledScrollWrapperContainer>
@@ -87,7 +87,7 @@ export const PageLayoutVerticalListEditor = ({
<StyledVerticalListContainer
ref={provided.innerRef}
variant={variant}
shouldUseWhiteBackground={!isInPinnedTab || isMobile}
shouldUseWhiteBackground={isMobile || isInSidePanel}
// oxlint-disable-next-line react/jsx-props-no-spreading
{...provided.droppableProps}
>
@@ -45,7 +45,7 @@ export const PageLayoutVerticalListViewer = ({
return (
<StyledVerticalListContainer
variant={variant}
shouldUseWhiteBackground={!isInPinnedTab || isMobile}
shouldUseWhiteBackground={isMobile || isInSidePanel}
>
{widgets.map((widget) => (
<div key={widget.id}>
@@ -64,7 +64,6 @@ const mixedGraphsPageLayoutMocks = {
tabs: [
{
__typename: 'PageLayoutTab',
isActive: true,
id: 'mixed-tab',
title: 'Mixed Graphs',
position: 0,
@@ -75,7 +74,6 @@ const mixedGraphsPageLayoutMocks = {
widgets: [
{
__typename: 'PageLayoutWidget',
isActive: true,
id: 'number-widget',
pageLayoutTabId: 'mixed-tab',
type: WidgetType.GRAPH,
@@ -100,7 +98,6 @@ const mixedGraphsPageLayoutMocks = {
} satisfies PageLayoutWidget,
{
__typename: 'PageLayoutWidget',
isActive: true,
id: 'gauge-widget',
pageLayoutTabId: 'mixed-tab',
type: WidgetType.GRAPH,
@@ -126,7 +123,6 @@ const mixedGraphsPageLayoutMocks = {
} satisfies PageLayoutWidget,
{
__typename: 'PageLayoutWidget',
isActive: true,
id: 'pie-widget',
pageLayoutTabId: 'mixed-tab',
type: WidgetType.GRAPH,
@@ -153,7 +149,6 @@ const mixedGraphsPageLayoutMocks = {
} satisfies PageLayoutWidget,
{
__typename: 'PageLayoutWidget',
isActive: true,
id: 'bar-widget',
pageLayoutTabId: 'mixed-tab',
type: WidgetType.GRAPH,
@@ -23,7 +23,6 @@ const StyledContainer = styled.div`
const createInitialTabs = (): PageLayoutTab[] => [
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: 'overview',
title: 'Overview',
@@ -37,7 +36,6 @@ const createInitialTabs = (): PageLayoutTab[] => [
},
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: 'revenue',
title: 'Revenue',
@@ -50,7 +48,6 @@ const createInitialTabs = (): PageLayoutTab[] => [
},
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: 'forecasts',
title: 'Forecasts',
@@ -80,7 +77,6 @@ const PageLayoutTabListPlayground = ({
...prev,
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: `new-tab-${nextIndex}`,
title: `New Tab ${nextIndex}`,
@@ -21,7 +21,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-fields',
isActive: true,
pageLayoutTabId: 'company-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -61,7 +59,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-timeline',
isActive: true,
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
@@ -74,7 +71,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-timeline',
isActive: true,
pageLayoutTabId: 'company-tab-timeline',
title: 'Timeline',
type: WidgetType.TIMELINE,
@@ -101,7 +97,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-tasks',
isActive: true,
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
@@ -114,7 +109,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-tasks',
isActive: true,
pageLayoutTabId: 'company-tab-tasks',
title: 'Tasks',
type: WidgetType.TASKS,
@@ -141,7 +135,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-notes',
isActive: true,
title: 'Notes',
icon: 'IconNotes',
position: 400,
@@ -154,7 +147,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-notes',
isActive: true,
pageLayoutTabId: 'company-tab-notes',
title: 'Notes',
type: WidgetType.NOTES,
@@ -181,7 +173,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-files',
isActive: true,
title: 'Files',
icon: 'IconPaperclip',
position: 500,
@@ -194,7 +185,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-files',
isActive: true,
pageLayoutTabId: 'company-tab-files',
title: 'Files',
type: WidgetType.FILES,
@@ -221,7 +211,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-emails',
isActive: true,
title: 'Emails',
icon: 'IconMail',
position: 600,
@@ -234,7 +223,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-emails',
isActive: true,
pageLayoutTabId: 'company-tab-emails',
title: 'Emails',
type: WidgetType.EMAILS,
@@ -261,7 +249,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'company-tab-calendar',
isActive: true,
title: 'Calendar',
icon: 'IconCalendarEvent',
position: 700,
@@ -274,7 +261,6 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'company-widget-calendar',
isActive: true,
pageLayoutTabId: 'company-tab-calendar',
title: 'Calendar',
type: WidgetType.CALENDAR,
@@ -21,7 +21,6 @@ export const DEFAULT_MESSAGE_THREAD_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'message-thread-tab-home',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_MESSAGE_THREAD_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'message-thread-widget-email-thread',
isActive: true,
pageLayoutTabId: 'message-thread-tab-home',
title: 'Thread',
type: WidgetType.EMAIL_THREAD,
@@ -21,7 +21,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'note-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'note-widget-fields',
isActive: true,
pageLayoutTabId: 'note-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -61,7 +59,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'note-widget-note',
isActive: true,
pageLayoutTabId: 'note-tab-fields',
title: 'Note',
type: WidgetType.FIELD_RICH_TEXT,
@@ -95,7 +92,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'note-tab-note',
isActive: true,
title: 'Note',
icon: 'IconNotes',
position: 150,
@@ -108,7 +104,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'note-widget-note',
isActive: true,
pageLayoutTabId: 'note-tab-note',
title: 'Note',
type: WidgetType.FIELD_RICH_TEXT,
@@ -142,7 +137,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'note-tab-timeline',
isActive: true,
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
@@ -155,7 +149,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'note-widget-timeline',
isActive: true,
pageLayoutTabId: 'note-tab-timeline',
title: 'Timeline',
type: WidgetType.TIMELINE,
@@ -182,7 +175,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'note-tab-files',
isActive: true,
title: 'Files',
icon: 'IconPaperclip',
position: 300,
@@ -195,7 +187,6 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'note-widget-files',
isActive: true,
pageLayoutTabId: 'note-tab-files',
title: 'Files',
type: WidgetType.FILES,
@@ -21,7 +21,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-fields',
isActive: true,
pageLayoutTabId: 'opportunity-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -61,7 +59,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-timeline',
isActive: true,
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
@@ -74,7 +71,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-timeline',
isActive: true,
pageLayoutTabId: 'opportunity-tab-timeline',
title: 'Timeline',
type: WidgetType.TIMELINE,
@@ -101,7 +97,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-tasks',
isActive: true,
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
@@ -114,7 +109,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-tasks',
isActive: true,
pageLayoutTabId: 'opportunity-tab-tasks',
title: 'Tasks',
type: WidgetType.TASKS,
@@ -141,7 +135,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-notes',
isActive: true,
title: 'Notes',
icon: 'IconNotes',
position: 400,
@@ -154,7 +147,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-notes',
isActive: true,
pageLayoutTabId: 'opportunity-tab-notes',
title: 'Notes',
type: WidgetType.NOTES,
@@ -181,7 +173,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-files',
isActive: true,
title: 'Files',
icon: 'IconPaperclip',
position: 500,
@@ -194,7 +185,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-files',
isActive: true,
pageLayoutTabId: 'opportunity-tab-files',
title: 'Files',
type: WidgetType.FILES,
@@ -221,7 +211,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-emails',
isActive: true,
title: 'Emails',
icon: 'IconMail',
position: 600,
@@ -234,7 +223,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-emails',
isActive: true,
pageLayoutTabId: 'opportunity-tab-emails',
title: 'Emails',
type: WidgetType.EMAILS,
@@ -261,7 +249,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'opportunity-tab-calendar',
isActive: true,
title: 'Calendar',
icon: 'IconCalendarEvent',
position: 700,
@@ -274,7 +261,6 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'opportunity-widget-calendar',
isActive: true,
pageLayoutTabId: 'opportunity-tab-calendar',
title: 'Calendar',
type: WidgetType.CALENDAR,
@@ -21,7 +21,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-fields',
isActive: true,
pageLayoutTabId: 'person-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -61,7 +59,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-timeline',
isActive: true,
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
@@ -74,7 +71,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-timeline',
isActive: true,
pageLayoutTabId: 'person-tab-timeline',
title: 'Timeline',
type: WidgetType.TIMELINE,
@@ -101,7 +97,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-tasks',
isActive: true,
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
@@ -114,7 +109,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-tasks',
isActive: true,
pageLayoutTabId: 'person-tab-tasks',
title: 'Tasks',
type: WidgetType.TASKS,
@@ -141,7 +135,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-notes',
isActive: true,
title: 'Notes',
icon: 'IconNotes',
position: 400,
@@ -154,7 +147,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-notes',
isActive: true,
pageLayoutTabId: 'person-tab-notes',
title: 'Notes',
type: WidgetType.NOTES,
@@ -181,7 +173,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-files',
isActive: true,
title: 'Files',
icon: 'IconPaperclip',
position: 500,
@@ -194,7 +185,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-files',
isActive: true,
pageLayoutTabId: 'person-tab-files',
title: 'Files',
type: WidgetType.FILES,
@@ -221,7 +211,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-emails',
isActive: true,
title: 'Emails',
icon: 'IconMail',
position: 600,
@@ -234,7 +223,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-emails',
isActive: true,
pageLayoutTabId: 'person-tab-emails',
title: 'Emails',
type: WidgetType.EMAILS,
@@ -261,7 +249,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'person-tab-calendar',
isActive: true,
title: 'Calendar',
icon: 'IconCalendarEvent',
position: 700,
@@ -274,7 +261,6 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'person-widget-calendar',
isActive: true,
pageLayoutTabId: 'person-tab-calendar',
title: 'Calendar',
type: WidgetType.CALENDAR,
@@ -21,7 +21,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'default-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'default-widget-fields',
isActive: true,
pageLayoutTabId: 'default-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -61,7 +59,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'default-tab-timeline',
isActive: true,
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
@@ -74,7 +71,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'default-widget-timeline',
isActive: true,
pageLayoutTabId: 'default-tab-timeline',
title: 'Timeline',
type: WidgetType.TIMELINE,
@@ -101,7 +97,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'default-tab-tasks',
isActive: true,
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
@@ -114,7 +109,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'default-widget-tasks',
isActive: true,
pageLayoutTabId: 'default-tab-tasks',
title: 'Tasks',
type: WidgetType.TASKS,
@@ -141,7 +135,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'default-tab-notes',
isActive: true,
title: 'Notes',
icon: 'IconNotes',
position: 400,
@@ -154,7 +147,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'default-widget-notes',
isActive: true,
pageLayoutTabId: 'default-tab-notes',
title: 'Notes',
type: WidgetType.NOTES,
@@ -181,7 +173,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'default-tab-files',
isActive: true,
title: 'Files',
icon: 'IconPaperclip',
position: 500,
@@ -194,7 +185,6 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'default-widget-files',
isActive: true,
pageLayoutTabId: 'default-tab-files',
title: 'Files',
type: WidgetType.FILES,
@@ -21,7 +21,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'task-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -34,7 +33,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'task-widget-fields',
isActive: true,
pageLayoutTabId: 'task-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -58,7 +56,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'task-widget-note',
isActive: true,
pageLayoutTabId: 'task-tab-fields',
title: 'Note',
type: WidgetType.FIELD_RICH_TEXT,
@@ -92,7 +89,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'task-tab-note',
isActive: true,
title: 'Note',
icon: 'IconNotes',
position: 150,
@@ -105,7 +101,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'task-widget-note',
isActive: true,
pageLayoutTabId: 'task-tab-note',
title: 'Note',
type: WidgetType.FIELD_RICH_TEXT,
@@ -139,7 +134,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'task-tab-timeline',
isActive: true,
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
@@ -152,7 +146,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'task-widget-timeline',
isActive: true,
pageLayoutTabId: 'task-tab-timeline',
title: 'Timeline',
type: WidgetType.TIMELINE,
@@ -179,7 +172,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'task-tab-files',
isActive: true,
title: 'Files',
icon: 'IconPaperclip',
position: 300,
@@ -192,7 +184,6 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'task-widget-files',
isActive: true,
pageLayoutTabId: 'task-tab-files',
title: 'Files',
type: WidgetType.FILES,
@@ -26,7 +26,6 @@ export const DEFAULT_WORKFLOW_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'workflow-tab-flow',
isActive: true,
title: 'Flow',
position: 100,
layoutMode: PageLayoutTabLayoutMode.CANVAS,
@@ -39,7 +38,6 @@ export const DEFAULT_WORKFLOW_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'workflow-widget-flow',
isActive: true,
pageLayoutTabId: 'workflow-tab-flow',
title: 'Flow',
type: WidgetType.WORKFLOW,
@@ -27,7 +27,6 @@ export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'workflow-run-tab-fields',
isActive: true,
title: 'Home',
position: 100,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
@@ -40,7 +39,6 @@ export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'workflow-run-widget-fields',
isActive: true,
pageLayoutTabId: 'workflow-run-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -68,7 +66,6 @@ export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'workflow-run-tab-flow',
isActive: true,
title: 'Flow',
position: 200,
layoutMode: PageLayoutTabLayoutMode.CANVAS,
@@ -81,7 +78,6 @@ export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'workflow-run-widget-flow',
isActive: true,
pageLayoutTabId: 'workflow-run-tab-flow',
title: 'Flow',
type: WidgetType.WORKFLOW_RUN,
@@ -27,7 +27,6 @@ export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'workflow-version-tab-fields',
isActive: true,
title: 'Home',
icon: 'IconHome',
position: 100,
@@ -40,7 +39,6 @@ export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'workflow-version-widget-fields',
isActive: true,
pageLayoutTabId: 'workflow-version-tab-fields',
title: 'Fields',
type: WidgetType.FIELDS,
@@ -68,7 +66,6 @@ export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
applicationId: '',
id: 'workflow-version-tab-flow',
isActive: true,
title: 'Flow',
icon: 'IconSettings',
position: 200,
@@ -81,7 +78,6 @@ export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = {
{
__typename: 'PageLayoutWidget',
id: 'workflow-version-widget-flow',
isActive: true,
pageLayoutTabId: 'workflow-version-tab-flow',
title: 'Flow',
type: WidgetType.WORKFLOW_VERSION,
@@ -8,7 +8,6 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
objectMetadataId
createdAt
updatedAt
isActive
deletedAt
conditionalDisplay
conditionalAvailabilityExpression
@@ -24,7 +24,6 @@ const makeWidget = (
id,
pageLayoutTabId: tabId,
title: id,
isActive: true,
type: WidgetType.FIELDS,
gridPosition: { column: 0, columnSpan: 1, row: 0, rowSpan: 1 },
configuration: { __typename: 'FieldsConfiguration' as const },
@@ -47,7 +46,6 @@ const makeTab = (
id,
applicationId: '',
title: id,
isActive: true,
position,
layoutMode,
pageLayoutId: '',
@@ -24,7 +24,6 @@ const makeWidget = (
id,
pageLayoutTabId: tabId,
title: id,
isActive: true,
type: WidgetType.FIELDS,
gridPosition: { column: 0, columnSpan: 1, row: 0, rowSpan: 1 },
configuration: { __typename: 'FieldsConfiguration' as const },
@@ -47,7 +46,6 @@ const makeTab = (
id,
applicationId: '',
title: id,
isActive: true,
position,
layoutMode,
pageLayoutId: '',
@@ -79,7 +79,6 @@ describe('useCreatePageLayoutGraphWidget', () => {
id: 'tab-1',
applicationId: '',
title: 'Tab 1',
isActive: true,
position: 0,
pageLayoutId: '',
widgets: [],
@@ -163,7 +162,6 @@ describe('useCreatePageLayoutGraphWidget', () => {
id: 'tab-1',
applicationId: '',
title: 'Tab 1',
isActive: true,
position: 0,
pageLayoutId: '',
widgets: [],
@@ -308,7 +308,6 @@ describe('useCreatePageLayoutTab', () => {
id: 'existing-tab',
applicationId: '',
title: 'Existing Tab',
isActive: true,
position: 0,
pageLayoutId: 'test-layout',
widgets: [],
@@ -25,7 +25,6 @@ const makeWidget = (
id,
pageLayoutTabId: tabId,
title: id,
isActive: true,
type: WidgetType.FIELDS,
gridPosition: { column: 0, columnSpan: 1, row: 0, rowSpan: 1 },
configuration: { __typename: 'FieldsConfiguration' as const },
@@ -47,7 +46,6 @@ const makeTab = (
id,
applicationId: '',
title: id,
isActive: true,
position,
pageLayoutId: '',
widgets,
@@ -24,7 +24,6 @@ const makeWidget = (
id,
pageLayoutTabId: tabId,
title: id,
isActive: true,
type: WidgetType.FIELDS,
gridPosition: { column: 0, columnSpan: 1, row: 0, rowSpan: 1 },
configuration: { __typename: 'FieldsConfiguration' as const },
@@ -46,7 +45,6 @@ const makeTab = (
id,
applicationId: '',
title: id,
isActive: true,
position,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
pageLayoutId: '',
@@ -24,7 +24,6 @@ const makeWidget = (
id,
pageLayoutTabId: tabId,
title: id,
isActive: true,
type: WidgetType.FIELDS,
gridPosition: { column: 0, columnSpan: 1, row: 0, rowSpan: 1 },
configuration: { __typename: 'FieldsConfiguration' as const },
@@ -46,7 +45,6 @@ const makeTab = (
id,
applicationId: '',
title: id,
isActive: true,
position,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
pageLayoutId: '',
@@ -24,7 +24,6 @@ const makeWidget = (
id,
pageLayoutTabId: tabId,
title: id,
isActive: true,
type: WidgetType.FIELDS,
gridPosition: { column: 0, columnSpan: 1, row: 0, rowSpan: 1 },
configuration: { __typename: 'FieldsConfiguration' as const },
@@ -46,7 +45,6 @@ const makeTab = (
id,
applicationId: '',
title: id,
isActive: true,
position,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
pageLayoutId: '',
@@ -90,7 +90,6 @@ describe('usePageLayoutDraftState', () => {
id: 'tab-1',
applicationId: '',
title: 'Tab 1',
isActive: true,
position: 0,
pageLayoutId: '',
createdAt: new Date().toISOString(),
@@ -101,7 +100,6 @@ describe('usePageLayoutDraftState', () => {
id: 'widget-1',
pageLayoutTabId: 'tab-1',
title: 'New Widget',
isActive: true,
type: WidgetType.GRAPH,
gridPosition: { row: 2, column: 2, rowSpan: 2, columnSpan: 2 },
configuration: {
@@ -35,7 +35,6 @@ describe('usePageLayoutWithRelationWidgets', () => {
applicationId: '',
id: 'tab-1',
title: 'Fields',
isActive: true,
icon: 'IconList',
position: 100,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
@@ -49,7 +48,6 @@ describe('usePageLayoutWithRelationWidgets', () => {
id: 'widget-fields',
pageLayoutTabId: 'tab-1',
title: 'Fields',
isActive: true,
type: WidgetType.FIELDS,
objectMetadataId: null,
gridPosition: {
@@ -73,7 +71,6 @@ describe('usePageLayoutWithRelationWidgets', () => {
id: 'widget-notes',
pageLayoutTabId: 'tab-1',
title: 'Notes',
isActive: true,
type: WidgetType.NOTES,
objectMetadataId: null,
gridPosition: {
@@ -96,7 +93,6 @@ describe('usePageLayoutWithRelationWidgets', () => {
id: 'widget-other',
pageLayoutTabId: 'tab-1',
title: 'Other',
isActive: true,
type: WidgetType.GRAPH,
objectMetadataId: null,
gridPosition: {
@@ -221,7 +217,6 @@ describe('usePageLayoutWithRelationWidgets', () => {
id: 'widget-other',
pageLayoutTabId: 'tab-1',
title: 'Other',
isActive: true,
type: WidgetType.GRAPH,
objectMetadataId: null,
gridPosition: {
@@ -54,7 +54,6 @@ export const useCreatePageLayoutTab = ({
const newTab: PageLayoutTab = {
id: newTabId,
applicationId: '',
isActive: true,
title: title || `Tab ${tabsLength + 1}`,
position: maxPosition + 1,
pageLayoutId: pageLayoutId,
@@ -39,6 +39,7 @@ export const useCreatePendingFieldsWidgetViews = () => {
if (widget.type !== WidgetType.FIELDS) {
return false;
}
const viewId = getWidgetConfigurationViewId(widget.configuration);
return isDefined(viewId) && !persistedWidgetIds.has(widget.id);
@@ -2,14 +2,18 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/context
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
import { removeWidgetFromTab } from '@/page-layout/utils/removeWidgetFromTab';
import { removeWidgetLayoutFromTab } from '@/page-layout/utils/removeWidgetLayoutFromTab';
import { useDeleteViewForFieldsWidget } from '@/page-layout/widgets/fields/hooks/useDeleteViewForFieldsWidget';
import { useDeleteViewForRecordTableWidget } from '@/page-layout/widgets/record-table/hooks/useDeleteViewForRecordTableWidget';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { WidgetType } from '~/generated-metadata/graphql';
export const useDeletePageLayoutWidget = (pageLayoutIdFromProps?: string) => {
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
@@ -34,6 +38,16 @@ export const useDeletePageLayoutWidget = (pageLayoutIdFromProps?: string) => {
const { closeSidePanelMenu } = useSidePanelMenu();
const { deleteViewForFieldsWidget } = useDeleteViewForFieldsWidget();
const { deleteViewForRecordTableWidget } =
useDeleteViewForRecordTableWidget();
const pageLayoutPersistedState = useAtomComponentStateCallbackState(
pageLayoutPersistedComponentState,
pageLayoutId,
);
const store = useStore();
const deletePageLayoutWidget = useCallback(
@@ -47,6 +61,43 @@ export const useDeletePageLayoutWidget = (pageLayoutIdFromProps?: string) => {
tab.widgets.some((widget) => widget.id === widgetId),
);
const widgetToDelete = tabWithWidget?.widgets.find(
(widget) => widget.id === widgetId,
);
const persisted = store.get(pageLayoutPersistedState);
const persistedWidgetIds = new Set(
persisted?.tabs.flatMap((tab) =>
tab.widgets.map((widget) => widget.id),
) ?? [],
);
const isWidgetPersisted = persistedWidgetIds.has(widgetId);
if (
isWidgetPersisted &&
isDefined(widgetToDelete) &&
widgetToDelete.type === WidgetType.RECORD_TABLE &&
'viewId' in widgetToDelete.configuration &&
isDefined(widgetToDelete.configuration.viewId)
) {
deleteViewForRecordTableWidget(
widgetToDelete.configuration.viewId as string,
);
}
if (
isWidgetPersisted &&
isDefined(widgetToDelete) &&
widgetToDelete.type === WidgetType.FIELDS &&
'viewId' in widgetToDelete.configuration &&
isDefined(widgetToDelete.configuration.viewId)
) {
deleteViewForFieldsWidget(
widgetToDelete.configuration.viewId as string,
);
}
const tabId = tabWithWidget?.id;
if (isDefined(tabId)) {
@@ -73,9 +124,12 @@ export const useDeletePageLayoutWidget = (pageLayoutIdFromProps?: string) => {
},
[
closeSidePanelMenu,
deleteViewForFieldsWidget,
deleteViewForRecordTableWidget,
pageLayoutCurrentLayoutsState,
pageLayoutDraftState,
pageLayoutEditingWidgetIdState,
pageLayoutPersistedState,
store,
],
);
@@ -27,22 +27,20 @@ export const usePageLayoutTabWithVisibleWidgetsOrThrow = (
throw new Error('Tab not found');
}
const activeWidgets = tab.widgets.filter((widget) => widget.isActive);
if (isPageLayoutInEditMode) {
return {
...tab,
widgets:
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
? sortWidgetsByVerticalListPosition(activeWidgets)
: activeWidgets,
? sortWidgetsByVerticalListPosition(tab.widgets)
: tab.widgets,
};
}
const context = buildWidgetVisibilityContext({ isMobile, isInSidePanel });
const visibleWidgets = filterVisibleWidgets({
widgets: activeWidgets,
widgets: tab.widgets,
context,
});
@@ -8,10 +8,6 @@ import { useExitLayoutCustomizationMode } from '@/layout-customization/hooks/use
import { useInvalidateMetadataStore } from '@/metadata-store/hooks/useInvalidateMetadataStore';
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { fieldsWidgetEditorModeDraftComponentState } from '@/page-layout/states/fieldsWidgetEditorModeDraftComponentState';
import { fieldsWidgetGroupsDraftComponentState } from '@/page-layout/states/fieldsWidgetGroupsDraftComponentState';
import { fieldsWidgetUngroupedFieldsDraftComponentState } from '@/page-layout/states/fieldsWidgetUngroupedFieldsDraftComponentState';
import { hasInitializedFieldsWidgetGroupsDraftComponentState } from '@/page-layout/states/hasInitializedFieldsWidgetGroupsDraftComponentState';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
@@ -54,28 +50,6 @@ export const useRefreshPageLayoutAfterReset = (
pageLayoutId,
);
const fieldsWidgetGroupsDraftState = useAtomComponentStateCallbackState(
fieldsWidgetGroupsDraftComponentState,
pageLayoutId,
);
const fieldsWidgetUngroupedFieldsDraftState =
useAtomComponentStateCallbackState(
fieldsWidgetUngroupedFieldsDraftComponentState,
pageLayoutId,
);
const fieldsWidgetEditorModeDraftState = useAtomComponentStateCallbackState(
fieldsWidgetEditorModeDraftComponentState,
pageLayoutId,
);
const hasInitializedFieldsWidgetGroupsDraftState =
useAtomComponentStateCallbackState(
hasInitializedFieldsWidgetGroupsDraftComponentState,
pageLayoutId,
);
const refreshPageLayoutAfterReset = useCallback(
async (collectAffectedViewIds: (layout: PageLayout) => Set<string>) => {
const { data } = await client.query({
@@ -99,11 +73,6 @@ export const useRefreshPageLayoutAfterReset = (
);
}
store.set(fieldsWidgetGroupsDraftState, {});
store.set(fieldsWidgetUngroupedFieldsDraftState, {});
store.set(fieldsWidgetEditorModeDraftState, {});
store.set(hasInitializedFieldsWidgetGroupsDraftState, {});
setIsPageLayoutInEditMode(false);
exitLayoutCustomizationMode();
evictViewMetadataForViewIds(store, affectedViewIds);
@@ -116,10 +85,6 @@ export const useRefreshPageLayoutAfterReset = (
pageLayoutPersistedState,
pageLayoutDraftState,
pageLayoutCurrentLayoutsState,
fieldsWidgetGroupsDraftState,
fieldsWidgetUngroupedFieldsDraftState,
fieldsWidgetEditorModeDraftState,
hasInitializedFieldsWidgetGroupsDraftState,
setIsPageLayoutInEditMode,
exitLayoutCustomizationMode,
invalidateMetadataStore,
@@ -3,13 +3,18 @@ import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pag
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
import { getWidgetConfigurationViewId } from '@/page-layout/utils/getWidgetConfigurationViewId';
import { removeWidgetFromTab } from '@/page-layout/utils/removeWidgetFromTab';
import { removeWidgetLayoutFromTab } from '@/page-layout/utils/removeWidgetLayoutFromTab';
import { useDeleteViewForFieldsWidget } from '@/page-layout/widgets/fields/hooks/useDeleteViewForFieldsWidget';
import { useDeleteViewForRecordTableWidget } from '@/page-layout/widgets/record-table/hooks/useDeleteViewForRecordTableWidget';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { WidgetType } from '~/generated-metadata/graphql';
export const useRemovePageLayoutWidgetAndPreservePosition = (
pageLayoutIdFromProps?: string,
@@ -39,6 +44,16 @@ export const useRemovePageLayoutWidgetAndPreservePosition = (
pageLayoutId,
);
const { deleteViewForFieldsWidget } = useDeleteViewForFieldsWidget();
const { deleteViewForRecordTableWidget } =
useDeleteViewForRecordTableWidget();
const pageLayoutPersistedState = useAtomComponentStateCallbackState(
pageLayoutPersistedComponentState,
pageLayoutId,
);
const store = useStore();
const removePageLayoutWidgetAndPreservePosition = useCallback(
@@ -50,6 +65,35 @@ export const useRemovePageLayoutWidgetAndPreservePosition = (
tab.widgets.some((widget) => widget.id === widgetId),
);
const widgetToRemove = tabWithWidget?.widgets.find(
(widget) => widget.id === widgetId,
);
const persisted = store.get(pageLayoutPersistedState);
const persistedWidgetIds = new Set(
persisted?.tabs.flatMap((tab) =>
tab.widgets.map((widget) => widget.id),
) ?? [],
);
const isWidgetPersisted = persistedWidgetIds.has(widgetId);
if (isWidgetPersisted && isDefined(widgetToRemove)) {
const viewId = getWidgetConfigurationViewId(
widgetToRemove.configuration,
);
if (isDefined(viewId)) {
if (widgetToRemove.type === WidgetType.RECORD_TABLE) {
deleteViewForRecordTableWidget(viewId);
}
if (widgetToRemove.type === WidgetType.FIELDS) {
deleteViewForFieldsWidget(viewId);
}
}
}
const tabId = tabWithWidget?.id;
if (!isDefined(tabId)) {
@@ -87,10 +131,13 @@ export const useRemovePageLayoutWidgetAndPreservePosition = (
store.set(pageLayoutEditingWidgetIdState, null);
},
[
deleteViewForFieldsWidget,
deleteViewForRecordTableWidget,
pageLayoutCurrentLayoutsState,
pageLayoutDraftState,
pageLayoutDraggedAreaState,
pageLayoutEditingWidgetIdState,
pageLayoutPersistedState,
store,
],
);
@@ -18,15 +18,10 @@ export const pageLayoutsWithRelationsSelector = createAtomSelector<
const allFlatWidgets = get(metadataStoreState, 'pageLayoutWidgets')
.current as FlatPageLayoutWidget[];
const activeFlatTabs = allFlatTabs.filter((tab) => tab.isActive);
const activeFlatWidgets = allFlatWidgets.filter(
(widget) => widget.isActive,
);
const tabsByPageLayoutId = new Map<string, FlatPageLayoutTab[]>();
const widgetsByTabId = new Map<string, FlatPageLayoutWidget[]>();
for (const tab of activeFlatTabs) {
for (const tab of allFlatTabs) {
const existing = tabsByPageLayoutId.get(tab.pageLayoutId);
if (isDefined(existing)) {
@@ -36,7 +31,7 @@ export const pageLayoutsWithRelationsSelector = createAtomSelector<
}
}
for (const widget of activeFlatWidgets) {
for (const widget of allFlatWidgets) {
const existing = widgetsByTabId.get(widget.pageLayoutTabId);
if (isDefined(existing)) {
@@ -11,7 +11,6 @@ describe('addWidgetToTab', () => {
const mockWidget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
id: 'widget-1',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Test Widget',
type: WidgetType.GRAPH,
@@ -32,7 +31,6 @@ describe('addWidgetToTab', () => {
{
id: 'tab-1',
applicationId: '',
isActive: true,
title: 'Tab 1',
position: 0,
pageLayoutId: 'layout-1',
@@ -44,7 +42,6 @@ describe('addWidgetToTab', () => {
{
id: 'tab-2',
applicationId: '',
isActive: true,
title: 'Tab 2',
position: 1,
pageLayoutId: 'layout-1',
@@ -12,7 +12,6 @@ describe('convertLayoutsToWidgets', () => {
const mockWidgets: PageLayoutWidget[] = [
{
id: 'widget-1',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Widget 1',
type: WidgetType.GRAPH,
@@ -35,7 +34,6 @@ describe('convertLayoutsToWidgets', () => {
},
{
id: 'widget-2',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Widget 2',
type: WidgetType.GRAPH,
@@ -20,7 +20,6 @@ describe('convertPageLayoutToTabLayouts', () => {
{
id: 'tab-1',
applicationId: '',
isActive: true,
title: 'Tab 1',
position: 0,
pageLayoutId: 'page-layout-1',
@@ -28,7 +27,6 @@ describe('convertPageLayoutToTabLayouts', () => {
{
__typename: 'PageLayoutWidget',
id: 'widget-1',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Widget 1',
type: WidgetType.GRAPH,
@@ -47,7 +45,6 @@ describe('convertPageLayoutToTabLayouts', () => {
{
__typename: 'PageLayoutWidget',
id: 'widget-2',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Widget 2',
type: WidgetType.GRAPH,
@@ -102,7 +99,6 @@ describe('convertPageLayoutToTabLayouts', () => {
{
id: 'tab-1',
applicationId: '',
isActive: true,
title: 'Tab 1',
position: 0,
pageLayoutId: 'page-layout-1',
@@ -110,7 +106,6 @@ describe('convertPageLayoutToTabLayouts', () => {
{
__typename: 'PageLayoutWidget',
id: 'rich-text-widget',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Rich Text',
type: WidgetType.STANDALONE_RICH_TEXT,
@@ -23,7 +23,6 @@ const createMockWidget = (
},
): PageLayoutWidget => ({
id: 'widget-1',
isActive: true,
type: WidgetType.GRAPH,
title: 'Test',
objectMetadataId: null,
@@ -12,7 +12,6 @@ describe('filterVisibleWidgets', () => {
): PageLayoutTab['widgets'][0] => ({
__typename: 'PageLayoutWidget',
id,
isActive: true,
pageLayoutTabId: 'tab-1',
title: `Widget ${id}`,
type: WidgetType.FIELDS,
@@ -5,7 +5,6 @@ describe('getPageLayoutTabListInitialActiveTabId', () => {
const createMockTab = (id: string): PageLayoutTab => ({
id,
applicationId: '',
isActive: true,
pageLayoutId: 'page-layout-1',
title: `Tab ${id}`,
position: 0,
@@ -10,7 +10,6 @@ describe('getTabsByDisplayMode', () => {
const createMockTab = (id: string): PageLayoutTab => ({
id,
applicationId: '',
isActive: true,
pageLayoutId: 'page-layout-1',
title: `Tab ${id}`,
position: 0,
@@ -12,7 +12,6 @@ describe('getTabsWithVisibleWidgets', () => {
): PageLayoutTab['widgets'][0] => ({
__typename: 'PageLayoutWidget',
id,
isActive: true,
pageLayoutTabId: 'tab-1',
title: `Widget ${id}`,
type: WidgetType.FIELDS,
@@ -42,7 +41,6 @@ describe('getTabsWithVisibleWidgets', () => {
__typename: 'PageLayoutTab',
applicationId: '',
id,
isActive: true,
pageLayoutId: 'page-layout-1',
title: `Tab ${id}`,
position: 0,
@@ -10,7 +10,6 @@ import {
describe('prepareGridLayoutItemsWithPlaceholders', () => {
const createMockWidget = (id: string): PageLayoutWidget => ({
id,
isActive: true,
pageLayoutTabId: 'tab-1',
title: `Test Widget ${id}`,
type: WidgetType.GRAPH,
@@ -216,7 +215,6 @@ describe('prepareGridLayoutItemsWithPlaceholders', () => {
expect(resultWidget.createdAt).toBe(widget.createdAt);
expect(resultWidget.updatedAt).toBe(widget.updatedAt);
expect(resultWidget.deletedAt).toBe(widget.deletedAt);
expect(resultWidget.isActive).toBe(widget.isActive);
}
});
});
@@ -12,7 +12,6 @@ describe('removeWidgetFromTab', () => {
{
id: 'tab-1',
applicationId: '',
isActive: true,
title: 'Tab 1',
position: 0,
pageLayoutId: 'layout-1',
@@ -20,7 +19,6 @@ describe('removeWidgetFromTab', () => {
{
__typename: 'PageLayoutWidget' as const,
id: 'widget-1',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Widget 1',
type: WidgetType.GRAPH,
@@ -39,7 +37,6 @@ describe('removeWidgetFromTab', () => {
{
__typename: 'PageLayoutWidget' as const,
id: 'widget-2',
isActive: true,
pageLayoutTabId: 'tab-1',
title: 'Widget 2',
type: WidgetType.GRAPH,
@@ -65,7 +62,6 @@ describe('removeWidgetFromTab', () => {
{
id: 'tab-2',
applicationId: '',
isActive: true,
title: 'Tab 2',
position: 1,
pageLayoutId: 'layout-1',
@@ -73,7 +69,6 @@ describe('removeWidgetFromTab', () => {
{
__typename: 'PageLayoutWidget' as const,
id: 'widget-3',
isActive: true,
pageLayoutTabId: 'tab-2',
title: 'Widget 3',
type: WidgetType.IFRAME,
@@ -28,7 +28,6 @@ export const createDefaultFieldWidget = ({
id,
pageLayoutTabId,
title,
isActive: true,
type: WidgetType.FIELD,
configuration: {
__typename: 'FieldConfiguration',
@@ -23,7 +23,6 @@ export const createDefaultFieldsWidget = ({
id,
pageLayoutTabId,
title: 'Fields',
isActive: true,
type: WidgetType.FIELDS,
configuration: {
__typename: 'FieldsConfiguration',
@@ -18,7 +18,6 @@ export const createDefaultFrontComponentWidget = (
id,
pageLayoutTabId,
title,
isActive: true,
type: WidgetType.FRONT_COMPONENT,
configuration: {
__typename: 'FrontComponentConfiguration',
@@ -72,7 +72,6 @@ export const createDefaultGraphWidget = ({
id,
pageLayoutTabId,
title,
isActive: true,
type: WidgetType.GRAPH,
configuration,
gridPosition,
@@ -19,7 +19,6 @@ export const createDefaultIframeWidget = (
id,
pageLayoutTabId,
title,
isActive: true,
type: WidgetType.IFRAME,
configuration: {
configurationType: WidgetConfigurationType.IFRAME,
@@ -24,7 +24,6 @@ export const createDefaultRecordTableWidget = ({
id,
pageLayoutTabId,
title,
isActive: true,
type: WidgetType.RECORD_TABLE,
configuration: {
configurationType: WidgetConfigurationType.RECORD_TABLE,
@@ -19,7 +19,6 @@ export const createDefaultStandaloneRichTextWidget = (
id,
pageLayoutTabId,
title: 'Untitled Rich Text',
isActive: true,
type: WidgetType.STANDALONE_RICH_TEXT,
configuration: {
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
@@ -15,15 +15,13 @@ export const getTabsWithVisibleWidgets = ({
isInSidePanel,
isEditMode,
}: GetTabsWithVisibleWidgetsParams): PageLayoutTab[] => {
const activeTabs = tabs.filter((tab) => tab.isActive);
if (isEditMode) {
return activeTabs;
return tabs;
}
const context = buildWidgetVisibilityContext({ isMobile, isInSidePanel });
const tabsWithFilteredWidgets = activeTabs.map((tab) => ({
const tabsWithFilteredWidgets = tabs.map((tab) => ({
...tab,
widgets: filterVisibleWidgets({ widgets: tab.widgets, context }),
}));
@@ -32,7 +30,7 @@ export const getTabsWithVisibleWidgets = ({
(tab) => tab.widgets.length > 0,
);
if (tabsWithVisibleWidgets.length === 0 && activeTabs.length > 0) {
if (tabsWithVisibleWidgets.length === 0 && tabs.length > 0) {
return tabsWithFilteredWidgets.slice(0, 1);
}
@@ -18,7 +18,6 @@ const getRelationFieldWidgetToInsert = (
id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}${field.id}-${field.label}`,
pageLayoutTabId: tabId,
title: field.label,
isActive: true,
type: WidgetType.FIELD,
objectMetadataId: null,
gridPosition: {
@@ -9,6 +9,7 @@ import { useResolveFieldMetadataIdFromNameOrId } from '@/page-layout/hooks/useRe
import { isFieldWidget } from '@/page-layout/widgets/field/utils/isFieldWidget';
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { indexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/indexViewIdFromObjectMetadataItemFamilySelector';
import { styled } from '@linaria/react';
@@ -23,6 +24,7 @@ import {
TooltipPosition,
} from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { RelationType } from '~/generated-metadata/graphql';
const StyledLinkContainer = styled.div`
@@ -33,14 +35,21 @@ const StyledLinkContainer = styled.div`
}
`;
const StyledSeeAllButtonWrapper = styled.div`
opacity: 1;
pointer-events: auto;
const StyledSeeAllButtonWrapper = styled.div<{ isMobile: boolean }>`
opacity: ${({ isMobile }) => (isMobile ? '1' : '0')};
pointer-events: none;
transition: opacity ${themeCssVariables.animation.duration.instant}s ease;
.widget:hover & {
opacity: 1;
pointer-events: auto;
}
`;
export const WidgetActionFieldSeeAll = () => {
const widget = useCurrentWidget();
const targetRecord = useTargetRecord();
const isMobile = useIsMobile();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: targetRecord.targetObjectNameSingular,
@@ -136,7 +145,7 @@ export const WidgetActionFieldSeeAll = () => {
<div id={tooltipId}>
<StyledLinkContainer>
<Link to={filterLinkHref} data-testid="widget-see-all-link">
<StyledSeeAllButtonWrapper>
<StyledSeeAllButtonWrapper isMobile={isMobile}>
<LightIconButton Icon={IconArrowUpRight} accent="secondary" />
</StyledSeeAllButtonWrapper>
</Link>
@@ -205,10 +205,6 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
variant={variant}
hasHeader={showHeader}
isEditable={isWidgetEditable}
isInVerticalListTab={
layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
}
isMobile={isMobile}
hasInteractiveContent={widget.type === WidgetType.RECORD_TABLE}
>
{hasAccess ? (
@@ -143,7 +143,6 @@ const createPageLayoutWithWidget = (
tabs: [
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: TAB_ID_OVERVIEW,
title: 'Overview',
@@ -253,7 +252,6 @@ export const WithNumberChart: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_NUMBER_CHART,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -340,7 +338,6 @@ export const WithGaugeChart: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_GAUGE_CHART,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -427,7 +424,6 @@ export const WithBarChart: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_BAR_CHART,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -525,7 +521,6 @@ export const SmallWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_SMALL,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -619,7 +614,6 @@ export const MediumWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_MEDIUM,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -717,7 +711,6 @@ export const LargeWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_LARGE,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -815,7 +808,6 @@ export const WideWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_WIDE,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -909,7 +901,6 @@ export const TallWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_TALL,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -1008,7 +999,6 @@ export const WithManyToOneRelationFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_MANY_TO_ONE_RELATION,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1127,7 +1117,6 @@ export const WithOneToManyRelationFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_ONE_TO_MANY_RELATION,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1238,7 +1227,6 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_ONE_TO_MANY_RELATION,
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1375,7 +1363,6 @@ export const OnMobile: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-mobile',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -1473,7 +1460,6 @@ export const InSidePanel: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-side-panel',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
@@ -1631,7 +1617,6 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: WIDGET_ID_CATALOG,
pageLayoutTabId:
variant === 'side-column' ? 'pinned-tab' : TAB_ID_OVERVIEW,
@@ -1738,7 +1723,6 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
? [
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: 'pinned-tab',
title: 'Pinned Tab',
@@ -1751,7 +1735,6 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
},
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: 'other-tab',
title: 'Other Tab',
@@ -1766,7 +1749,6 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
: [
{
__typename: 'PageLayoutTab',
isActive: true,
applicationId: '',
id: TAB_ID_OVERVIEW,
title: 'Overview',
@@ -1,4 +1,5 @@
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useRecordFieldsScopeContextOrThrow } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
import { RecordDetailMorphRelationSectionDropdown } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailMorphRelationSectionDropdown';
import { RecordDetailRelationSectionDropdown } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdown';
import {
@@ -16,11 +17,15 @@ import {
type FieldRelationMetadata,
} from '@/object-record/record-field/ui/types/FieldMetadata';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { getRecordFieldCardRelationPickerDropdownId } from '@/object-record/record-show/utils/getRecordFieldCardRelationPickerDropdownId';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { styled } from '@linaria/react';
import { CustomError } from 'twenty-shared/utils';
import { IconPencil, IconPlus } from 'twenty-ui/display';
import { IconPencil } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { RelationType } from '~/generated-metadata/graphql';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type FieldWidgetRelationEditActionProps = {
fieldDefinition:
@@ -29,15 +34,30 @@ type FieldWidgetRelationEditActionProps = {
recordId: string;
};
const StyledEditButtonWrapper = styled.div`
opacity: 1;
pointer-events: auto;
const StyledEditButtonWrapper = styled.div<{
isDropdownOpen: boolean;
isMobile: boolean;
}>`
opacity: ${({ isDropdownOpen, isMobile }) =>
isDropdownOpen ? '1' : isMobile ? '1' : '0'};
pointer-events: ${({ isDropdownOpen }) => (isDropdownOpen ? 'auto' : 'none')};
transition: ${({ isDropdownOpen }) =>
isDropdownOpen
? 'none'
: `opacity ${themeCssVariables.animation.duration.instant}s ease`};
.widget:hover & {
opacity: 1;
pointer-events: auto;
}
`;
export const FieldWidgetRelationEditAction = ({
fieldDefinition,
recordId,
}: FieldWidgetRelationEditActionProps) => {
const { scopeInstanceId } = useRecordFieldsScopeContextOrThrow();
const { objectMetadataItems } = useObjectMetadataItems();
const objectMetadataItem = objectMetadataItems.find(
(item) =>
@@ -72,14 +92,26 @@ export const FieldWidgetRelationEditAction = ({
const isMorphRelation = isFieldMorphRelation(fieldDefinition);
const triggerIcon =
fieldDefinition.metadata.relationType === RelationType.MANY_TO_ONE
? IconPencil
: IconPlus;
const relationSelectionDropdownId =
getRecordFieldCardRelationPickerDropdownId({
fieldDefinition,
recordId,
instanceId: scopeInstanceId,
});
const isDropdownOpen = useAtomComponentStateValue(
isDropdownOpenComponentState,
relationSelectionDropdownId,
);
const isMobile = useIsMobile();
const dropdownTriggerClickableComponent = (
<StyledEditButtonWrapper>
<LightIconButton Icon={triggerIcon} accent="secondary" />
<StyledEditButtonWrapper
isDropdownOpen={isDropdownOpen}
isMobile={isMobile}
>
<LightIconButton Icon={IconPencil} accent="secondary" />
</StyledEditButtonWrapper>
);
@@ -291,7 +291,6 @@ const createPageLayoutWithWidget = (
tabs: [
{
__typename: 'PageLayoutTab' as const,
isActive: true,
applicationId: '',
id: TAB_ID_OVERVIEW,
title: 'Overview',
@@ -333,7 +332,6 @@ export const TextFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-text-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -427,7 +425,6 @@ export const AddressFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-address-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -524,7 +521,6 @@ export const NumberFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-number-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -618,7 +614,6 @@ export const LinkFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-link-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -712,7 +707,6 @@ export const ManyToOneRelationFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-relation-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -816,7 +810,6 @@ export const OneToManyRelationFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-one-to-many-relation-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -912,7 +905,6 @@ export const BooleanFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-boolean-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1005,7 +997,6 @@ export const CurrencyFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-currency-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1098,7 +1089,6 @@ export const EmailsFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-emails-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1192,7 +1182,6 @@ export const PhonesFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-phones-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1286,7 +1275,6 @@ export const SelectFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-select-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1381,7 +1369,6 @@ export const MultiSelectFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-multi-select-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1479,7 +1466,6 @@ export const TimelineActivityRelationFieldWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-timeline-activity-relation-field',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1578,7 +1564,6 @@ export const ManyToOneRelationCardWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-relation-card',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1690,7 +1675,6 @@ export const OneToManyRelationCardWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-one-to-many-relation-card',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1785,7 +1769,6 @@ export const TimelineActivityRelationCardWidget: Story = {
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-timeline-activity-relation-card',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -1946,7 +1929,6 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-one-to-many-relation-card-progressive',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELD,
@@ -141,7 +141,6 @@ const createPageLayoutWithWidget = (
tabs: [
{
__typename: 'PageLayoutTab' as const,
isActive: true,
applicationId: '',
id: TAB_ID_OVERVIEW,
title: 'Overview',
@@ -160,7 +159,6 @@ const createPageLayoutWithWidget = (
const createFieldsWidget = (viewId: string | null): PageLayoutWidget => ({
__typename: 'PageLayoutWidget',
isActive: true,
id: 'widget-fields',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.FIELDS,
@@ -215,7 +213,6 @@ const createViewField = (
fieldMetadataId,
position,
isVisible: true,
isActive: true,
size: 200,
aggregateOperation: null,
viewId: FIELDS_VIEW_ID,
@@ -233,7 +230,6 @@ const createViewFieldGroup = (
name,
position,
isVisible,
isActive: true,
viewId: FIELDS_VIEW_ID,
viewFields,
});
@@ -29,12 +29,9 @@ export const useFieldsWidgetGroups = ({
objectNameSingular,
});
const { groups, displayMode } = useMemo<{
groups: FieldsWidgetGroup[];
displayMode: FieldsWidgetDisplayMode;
}>(() => {
const groups = useMemo<FieldsWidgetGroup[]>(() => {
if (!isDefined(objectMetadataItem)) {
return { groups: [], displayMode: 'grouped' };
return [];
}
if (isDefined(view) && isNonEmptyArray(view.viewFieldGroups)) {
@@ -44,7 +41,7 @@ export const useFieldsWidgetGroups = ({
let globalIndex = 0;
const resultGroups = sortedGroups
return sortedGroups
.filter((group) => group.isVisible)
.map((group) => {
const groupFields = [...(group.viewFields ?? [])].sort(
@@ -82,76 +79,28 @@ export const useFieldsWidgetGroups = ({
};
})
.filter((group) => group.fields.length > 0);
return { groups: resultGroups, displayMode: 'grouped' };
}
if (isDefined(view) && isNonEmptyArray(view.viewFields)) {
let globalIndex = 0;
const fields: FieldsWidgetGroupField[] = [...view.viewFields]
.sort((a, b) => a.position - b.position)
.filter((viewField) => viewField.isVisible)
.map((viewField) => {
const fieldMetadataItem = objectMetadataItem.fields.find(
(f) => f.id === viewField.fieldMetadataId,
);
if (!isDefined(fieldMetadataItem)) {
return null;
}
return {
fieldMetadataItem,
position: viewField.position,
isVisible: viewField.isVisible,
globalIndex: globalIndex++,
};
})
.filter(isDefined);
return {
groups:
fields.length > 0
? [
{
id: `${viewId}-ungrouped`,
name: '',
position: 0,
isVisible: true,
fields,
},
]
: [],
displayMode: 'inline',
};
}
return {
groups: filterDraftGroupsForDisplay(
buildDefaultFieldsWidgetGroups({
fields: objectMetadataItem.fields,
objectNameSingular,
labelIdentifierFieldMetadataItemId:
labelIdentifierFieldMetadataItem?.id,
}),
),
displayMode: 'grouped',
};
return filterDraftGroupsForDisplay(
buildDefaultFieldsWidgetGroups({
fields: objectMetadataItem.fields,
objectNameSingular,
labelIdentifierFieldMetadataItemId:
labelIdentifierFieldMetadataItem?.id,
}),
);
}, [
objectMetadataItem,
objectNameSingular,
labelIdentifierFieldMetadataItem,
view,
viewId,
]);
const displayMode: FieldsWidgetDisplayMode = 'grouped';
return {
groups,
displayMode,
isFromView:
isDefined(view) &&
(isNonEmptyArray(view.viewFieldGroups) ||
isNonEmptyArray(view.viewFields)),
isFromView: isDefined(view) && isNonEmptyArray(view.viewFieldGroups),
};
};
@@ -29,7 +29,7 @@ export const useSaveRecordTableWidgetFiltersToView = (
const {
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
performViewFilterGroupAPIDelete,
} = usePerformViewFilterGroupAPIPersist();
const currentRecordFiltersCallbackState = useAtomComponentStateCallbackState(
@@ -142,7 +142,7 @@ export const useSaveRecordTableWidgetFiltersToView = (
})),
);
await performViewFilterGroupAPIDestroy(
await performViewFilterGroupAPIDelete(
viewFilterGroupsToDelete.map((viewFilterGroup) => viewFilterGroup.id),
);
}, [
@@ -155,7 +155,7 @@ export const useSaveRecordTableWidgetFiltersToView = (
performViewFilterAPIDelete,
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
performViewFilterGroupAPIDelete,
]);
return { saveRecordTableWidgetFiltersToView };
@@ -39,7 +39,7 @@ export const useSaveRecordTableWidgetsViewDataOnDashboardSave = () => {
const {
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
performViewFilterGroupAPIDelete,
} = usePerformViewFilterGroupAPIPersist();
const {
@@ -251,7 +251,7 @@ export const useSaveRecordTableWidgetsViewDataOnDashboardSave = () => {
},
})),
);
await performViewFilterGroupAPIDestroy(
await performViewFilterGroupAPIDelete(
viewFilterGroupsToDelete.map((viewFilterGroup) => viewFilterGroup.id),
);
@@ -297,7 +297,7 @@ export const useSaveRecordTableWidgetsViewDataOnDashboardSave = () => {
performViewFilterAPIDelete,
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
performViewFilterGroupAPIDelete,
performViewSortAPICreate,
performViewSortAPIUpdate,
performViewSortAPIDelete,
@@ -10,7 +10,6 @@ const buildViewField = (
id: v4(),
fieldMetadataId: v4(),
position: 0,
isActive: true,
isVisible: true,
size: 100,
aggregateOperation: null,
@@ -21,7 +20,6 @@ const buildExistingViewField = (overrides: Partial<ViewField>): ViewField => ({
id: v4(),
fieldMetadataId: v4(),
position: 0,
isActive: true,
isVisible: true,
size: 100,
aggregateOperation: null,
@@ -1,20 +1,14 @@
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type WidgetCardVariant } from '~/modules/page-layout/widgets/types/WidgetCardVariant';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type WidgetCardContentStyledProps = {
variant: WidgetCardVariant;
hasHeader: boolean;
isEditable: boolean;
isInVerticalListTab: boolean;
isMobile: boolean;
};
const StyledWidgetCardContent = styled.div<WidgetCardContentStyledProps>`
background-color: ${({ variant, isInVerticalListTab, isMobile }) =>
variant === 'record-page' && isInVerticalListTab && !isMobile
? themeCssVariables.background.secondary
: 'transparent'};
border: ${({ variant, isEditable }) =>
variant === 'record-page' || (variant === 'side-column' && isEditable)
? `1px solid ${themeCssVariables.border.color.medium}`
@@ -46,10 +40,10 @@ const StyledWidgetCardContent = styled.div<WidgetCardContentStyledProps>`
}};
&:empty {
border: none;
border-radius: 0;
margin-top: 0;
padding: 0;
margin-top: ${({ hasHeader, variant, isEditable }) => {
if (hasHeader && variant === 'side-column' && !isEditable) return '0';
return hasHeader ? themeCssVariables.spacing[2] : '0';
}};
}
`;
@@ -57,8 +51,6 @@ type WidgetCardContentProps = {
variant: WidgetCardVariant;
hasHeader: boolean;
isEditable: boolean;
isInVerticalListTab: boolean;
isMobile: boolean;
hasInteractiveContent?: boolean;
className?: string;
children?: React.ReactNode;
@@ -68,8 +60,6 @@ export const WidgetCardContent = ({
variant,
hasHeader,
isEditable,
isInVerticalListTab,
isMobile,
hasInteractiveContent = false,
className,
children,
@@ -87,8 +77,6 @@ export const WidgetCardContent = ({
variant={variant}
hasHeader={hasHeader}
isEditable={isEditable}
isInVerticalListTab={isInVerticalListTab}
isMobile={isMobile}
className={className}
onClick={handleContentClick}
>
@@ -10,10 +10,13 @@ import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
import { createDefaultFieldWidget } from '@/page-layout/utils/createDefaultFieldWidget';
import { createDefaultFieldsWidget } from '@/page-layout/utils/createDefaultFieldsWidget';
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
import { getWidgetConfigurationViewId } from '@/page-layout/utils/getWidgetConfigurationViewId';
import { isVerticalListPosition } from '@/page-layout/utils/isVerticalListPosition';
import { removeWidgetFromTab } from '@/page-layout/utils/removeWidgetFromTab';
import { useFieldWidgetEligibleFields } from '@/page-layout/widgets/field/hooks/useFieldWidgetEligibleFields';
import { getFieldWidgetDefaultDisplayMode } from '@/page-layout/widgets/field/utils/getFieldWidgetDisplayModeConfig';
import { useDeleteViewForFieldsWidget } from '@/page-layout/widgets/fields/hooks/useDeleteViewForFieldsWidget';
import { useDeleteViewForRecordTableWidget } from '@/page-layout/widgets/record-table/hooks/useDeleteViewForRecordTableWidget';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
@@ -88,6 +91,11 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
const { insertCreatedWidgetAtContext } =
useInsertCreatedWidgetAtContext(pageLayoutId);
const { deleteViewForFieldsWidget } = useDeleteViewForFieldsWidget();
const { deleteViewForRecordTableWidget } =
useDeleteViewForRecordTableWidget();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: targetObjectNameSingular,
});
@@ -134,11 +142,28 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
return;
}
if (isDefined(existingWidget)) {
const viewId = getWidgetConfigurationViewId(existingWidget.configuration);
if (isDefined(viewId)) {
if (existingWidget.type === WidgetType.RECORD_TABLE) {
deleteViewForRecordTableWidget(viewId);
}
if (existingWidget.type === WidgetType.FIELDS) {
deleteViewForFieldsWidget(viewId);
}
}
}
store.set(pageLayoutDraftState, (prev) => ({
...prev,
tabs: removeWidgetFromTab(prev.tabs, tabId, pageLayoutEditingWidgetId),
}));
}, [
deleteViewForFieldsWidget,
deleteViewForRecordTableWidget,
existingWidget,
isReplaceMode,
pageLayoutDraftState,
pageLayoutEditingWidgetId,
@@ -303,7 +328,6 @@ export const SidePanelPageLayoutRecordPageWidgetTypeSelect = () => {
const newWidget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
id: widgetId,
isActive: true,
pageLayoutTabId: tabId,
title: frontComponent.name,
type: WidgetType.FRONT_COMPONENT,
@@ -140,6 +140,45 @@ describe('useAddressAutocomplete', () => {
});
});
it('should use place street over full autocomplete text', async () => {
const mockOnChange = jest.fn();
mockGetPlaceDetailsData.mockResolvedValue({
street: '123 Main St',
city: 'Springfield',
state: 'IL',
country: 'US',
postcode: '62704',
});
mockFindCountryNameByCountryCode.mockReturnValue('United States');
const { result } = renderHook(() => useAddressAutocomplete(mockOnChange));
await act(async () => {
await result.current.autoFillInputsFromPlaceDetails(
'place123',
'token123',
'123 Main St, Springfield, IL 62704, USA',
{
addressStreet1: '',
addressStreet2: null,
addressCity: null,
addressState: null,
addressCountry: null,
addressPostcode: null,
addressLat: null,
addressLng: null,
},
);
});
expect(mockOnChange).toHaveBeenCalledWith(
expect.objectContaining({
addressStreet1: '123 Main St',
}),
);
});
it('should preserve existing values when place data is missing', async () => {
const mockOnChange = jest.fn();
const mockPlaceData = {
@@ -80,7 +80,10 @@ export const useAddressAutocomplete = (
const countryName = findCountryNameByCountryCode(placeData?.country);
const updatedAddress = {
addressStreet1: addressStreet1 || (internalValue?.addressStreet1 ?? ''),
addressStreet1:
placeData?.street ||
addressStreet1 ||
(internalValue?.addressStreet1 ?? ''),
addressStreet2: internalValue?.addressStreet2 ?? null,
addressCity: placeData?.city || (internalValue?.addressCity ?? null),
addressState: placeData?.state || (internalValue?.addressState ?? null),
@@ -10,7 +10,6 @@ export const VIEW_FIELD_FRAGMENT = gql`
size
aggregateOperation
viewFieldGroupId
isActive
createdAt
updatedAt
deletedAt
@@ -9,7 +9,6 @@ export const VIEW_FIELD_GROUP_FRAGMENT = gql`
position
isVisible
viewId
isActive
createdAt
updatedAt
deletedAt
@@ -104,11 +104,11 @@ export const usePerformViewFilterGroupAPIPersist = () => {
[apolloClient],
);
const performViewFilterGroupAPIDestroy = useCallback(
(viewFilterGroupIdsToDestroy: string[]) => {
if (!viewFilterGroupIdsToDestroy.length) return;
const performViewFilterGroupAPIDelete = useCallback(
(viewFilterGroupIdsToDelete: string[]) => {
if (!viewFilterGroupIdsToDelete.length) return;
return Promise.all(
viewFilterGroupIdsToDestroy.map((viewFilterGroupId) =>
viewFilterGroupIdsToDelete.map((viewFilterGroupId) =>
apolloClient.mutate<{ destroyViewFilterGroup: ViewFilterGroup }>({
mutation: DESTROY_VIEW_FILTER_GROUP,
variables: {
@@ -124,6 +124,6 @@ export const usePerformViewFilterGroupAPIPersist = () => {
return {
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
performViewFilterGroupAPIDelete,
};
};
@@ -1,10 +1,14 @@
import { useSaveAnyFieldFilterToView } from '@/views/hooks/useSaveAnyFieldFilterToView';
import { useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters } from '@/views/hooks/useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters';
import { useSaveRecordFilterGroupsToViewFilterGroups } from '@/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups';
import { useSaveRecordFiltersToViewFilters } from '@/views/hooks/useSaveRecordFiltersToViewFilters';
import { useSaveRecordSortsToViewSorts } from '@/views/hooks/useSaveRecordSortsToViewSorts';
export const useSaveCurrentViewFiltersAndSorts = () => {
const { saveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters } =
useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters();
const { saveRecordFilterGroupsToViewFilterGroups } =
useSaveRecordFilterGroupsToViewFilterGroups();
const { saveRecordFiltersToViewFilters } =
useSaveRecordFiltersToViewFilters();
const { saveRecordSortsToViewSorts } = useSaveRecordSortsToViewSorts();
@@ -12,7 +16,8 @@ export const useSaveCurrentViewFiltersAndSorts = () => {
const saveCurrentViewFilterAndSorts = async () => {
await saveRecordSortsToViewSorts();
await saveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters();
await saveRecordFilterGroupsToViewFilterGroups();
await saveRecordFiltersToViewFilters();
await saveAnyFieldFilterToView();
};
@@ -0,0 +1,86 @@
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { usePerformViewFilterGroupAPIPersist } from '@/views/hooks/internal/usePerformViewFilterGroupAPIPersist';
import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { getViewFilterGroupsToCreate } from '@/views/utils/getViewFilterGroupsToCreate';
import { getViewFilterGroupsToDelete } from '@/views/utils/getViewFilterGroupsToDelete';
import { getViewFilterGroupsToUpdate } from '@/views/utils/getViewFilterGroupsToUpdate';
import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useSaveRecordFilterGroupsToViewFilterGroups = () => {
const { canPersistChanges } = useCanPersistViewChanges();
const {
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDelete,
} = usePerformViewFilterGroupAPIPersist();
const { currentView } = useGetCurrentViewOnly();
const currentRecordFilterGroupsCallbackState =
useAtomComponentStateCallbackState(currentRecordFilterGroupsComponentState);
const store = useStore();
const saveRecordFilterGroupsToViewFilterGroups = useCallback(async () => {
if (!canPersistChanges || !isDefined(currentView)) {
return;
}
const currentViewFilterGroups = currentView?.viewFilterGroups ?? [];
const currentRecordFilterGroups = store.get(
currentRecordFilterGroupsCallbackState,
);
const newViewFilterGroups = currentRecordFilterGroups.map(
(recordFilterGroup) =>
mapRecordFilterGroupToViewFilterGroup({
recordFilterGroup,
view: currentView,
}),
);
const viewFilterGroupsToCreate = getViewFilterGroupsToCreate(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupsToDelete = getViewFilterGroupsToDelete(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupsToUpdate = getViewFilterGroupsToUpdate(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupIdsToDelete = viewFilterGroupsToDelete.map(
(viewFilterGroup) => viewFilterGroup.id,
);
await performViewFilterGroupAPICreate(
viewFilterGroupsToCreate,
currentView,
);
await performViewFilterGroupAPIUpdate(viewFilterGroupsToUpdate);
await performViewFilterGroupAPIDelete(viewFilterGroupIdsToDelete);
}, [
canPersistChanges,
currentView,
store,
currentRecordFilterGroupsCallbackState,
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDelete,
]);
return {
saveRecordFilterGroupsToViewFilterGroups,
};
};
@@ -1,211 +0,0 @@
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { type FlatViewFilter } from '@/metadata-store/types/FlatViewFilter';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { usePerformViewFilterAPIPersist } from '@/views/hooks/internal/usePerformViewFilterAPIPersist';
import { usePerformViewFilterGroupAPIPersist } from '@/views/hooks/internal/usePerformViewFilterGroupAPIPersist';
import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { getViewFilterGroupsToCreate } from '@/views/utils/getViewFilterGroupsToCreate';
import { getViewFilterGroupsToDelete } from '@/views/utils/getViewFilterGroupsToDelete';
import { getViewFilterGroupsToUpdate } from '@/views/utils/getViewFilterGroupsToUpdate';
import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate';
import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete';
import { getViewFiltersToUpdate } from '@/views/utils/getViewFiltersToUpdate';
import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup';
import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
() => {
const { canPersistChanges } = useCanPersistViewChanges();
const { currentView } = useGetCurrentViewOnly();
const store = useStore();
const {
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
} = usePerformViewFilterGroupAPIPersist();
const {
performViewFilterAPICreate,
performViewFilterAPIUpdate,
performViewFilterAPIDestroy,
} = usePerformViewFilterAPIPersist();
const currentRecordFilterGroupsCallbackState =
useAtomComponentStateCallbackState(
currentRecordFilterGroupsComponentState,
);
const currentRecordFiltersCallbackState =
useAtomComponentStateCallbackState(currentRecordFiltersComponentState);
const saveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
useCallback(async () => {
if (!canPersistChanges || !isDefined(currentView)) {
return;
}
const currentViewFilterGroups = currentView.viewFilterGroups ?? [];
const currentRecordFilterGroups = store.get(
currentRecordFilterGroupsCallbackState,
);
const newViewFilterGroups = currentRecordFilterGroups.map(
(recordFilterGroup) =>
mapRecordFilterGroupToViewFilterGroup({
recordFilterGroup,
view: currentView,
}),
);
const viewFilterGroupsToCreate = getViewFilterGroupsToCreate(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupsToDelete = getViewFilterGroupsToDelete(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupsToUpdate = getViewFilterGroupsToUpdate(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupIdsToDestroy = viewFilterGroupsToDelete.map(
(viewFilterGroup) => viewFilterGroup.id,
);
await performViewFilterGroupAPICreate(
viewFilterGroupsToCreate,
currentView,
);
await performViewFilterGroupAPIUpdate(viewFilterGroupsToUpdate);
await performViewFilterGroupAPIDestroy(viewFilterGroupIdsToDestroy);
// Mirror the DB cascade: remove cascade-deleted viewFilters from the store
if (viewFilterGroupIdsToDestroy.length > 0) {
const destroyedIdsSet = new Set(viewFilterGroupIdsToDestroy);
store.set(metadataStoreState.atomFamily('viewFilters'), (prev) => ({
...prev,
current: (prev.current as FlatViewFilter[]).filter(
(viewFilter) =>
!isDefined(viewFilter.viewFilterGroupId) ||
!destroyedIdsSet.has(viewFilter.viewFilterGroupId),
),
}));
}
const currentViewFilters = currentView.viewFilters ?? [];
const currentRecordFilters = store.get(
currentRecordFiltersCallbackState,
);
const newViewFilters = currentRecordFilters.map(
mapRecordFilterToViewFilter,
);
const viewFiltersToCreate = getViewFiltersToCreate(
currentViewFilters,
newViewFilters,
);
const viewFiltersToDelete = getViewFiltersToDelete(
currentViewFilters,
newViewFilters,
).filter(
(viewFilter) =>
!isDefined(viewFilter.viewFilterGroupId) ||
!viewFilterGroupIdsToDestroy.includes(viewFilter.viewFilterGroupId),
);
const viewFiltersToUpdate = getViewFiltersToUpdate(
currentViewFilters,
newViewFilters,
);
const createViewFilterInputs = viewFiltersToCreate.map(
(viewFilter) => ({
input: {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
viewId: currentView.id,
value: viewFilter.value,
operand: viewFilter.operand,
viewFilterGroupId: viewFilter.viewFilterGroupId,
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
subFieldName: viewFilter.subFieldName ?? null,
},
}),
);
const updateViewFilterInputs = viewFiltersToUpdate.map(
(viewFilter) => ({
input: {
id: viewFilter.id,
update: {
value: viewFilter.value,
operand: viewFilter.operand,
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
viewFilterGroupId: viewFilter.viewFilterGroupId,
subFieldName: viewFilter.subFieldName ?? null,
},
},
}),
);
const destroyViewFilterInputs = viewFiltersToDelete.map(
(viewFilter) => ({
input: {
id: viewFilter.id,
},
}),
);
const createResult = await performViewFilterAPICreate(
createViewFilterInputs,
);
if (createResult.status === 'failed') {
return;
}
const updateResult = await performViewFilterAPIUpdate(
updateViewFilterInputs,
);
if (updateResult.status === 'failed') {
return;
}
const deleteResult = await performViewFilterAPIDestroy(
destroyViewFilterInputs,
);
if (deleteResult.status === 'failed') {
return;
}
}, [
canPersistChanges,
currentView,
store,
currentRecordFilterGroupsCallbackState,
currentRecordFiltersCallbackState,
performViewFilterGroupAPICreate,
performViewFilterGroupAPIUpdate,
performViewFilterGroupAPIDestroy,
performViewFilterAPICreate,
performViewFilterAPIUpdate,
performViewFilterAPIDestroy,
]);
return {
saveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters,
};
};
@@ -0,0 +1,137 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { usePerformViewFilterAPIPersist } from '@/views/hooks/internal/usePerformViewFilterAPIPersist';
import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges';
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate';
import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete';
import { getViewFiltersToUpdate } from '@/views/utils/getViewFiltersToUpdate';
import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useSaveRecordFiltersToViewFilters = () => {
const { canPersistChanges } = useCanPersistViewChanges();
const {
performViewFilterAPICreate,
performViewFilterAPIUpdate,
performViewFilterAPIDelete,
} = usePerformViewFilterAPIPersist();
const contextStoreCurrentViewId = useAtomComponentStateValue(
contextStoreCurrentViewIdComponentState,
);
const currentRecordFiltersCallbackState = useAtomComponentStateCallbackState(
currentRecordFiltersComponentState,
);
const store = useStore();
const saveRecordFiltersToViewFilters = useCallback(async () => {
const views = store.get(viewsSelector.atom);
const currentView = views.find(
(view) => view.id === contextStoreCurrentViewId,
);
if (!isDefined(currentView)) {
return;
}
if (!canPersistChanges || !isDefined(currentView)) {
return;
}
const currentViewFilters = currentView?.viewFilters ?? [];
const currentRecordFilters = store.get(currentRecordFiltersCallbackState);
const newViewFilters = currentRecordFilters.map(
mapRecordFilterToViewFilter,
);
const viewFiltersToCreate = getViewFiltersToCreate(
currentViewFilters,
newViewFilters,
);
const viewFiltersToDelete = getViewFiltersToDelete(
currentViewFilters,
newViewFilters,
);
const viewFiltersToUpdate = getViewFiltersToUpdate(
currentViewFilters,
newViewFilters,
);
const createViewFilterInputs = viewFiltersToCreate.map((viewFilter) => ({
input: {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
viewId: currentView.id,
value: viewFilter.value,
operand: viewFilter.operand,
viewFilterGroupId: viewFilter.viewFilterGroupId,
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
subFieldName: viewFilter.subFieldName ?? null,
},
}));
const updateViewFilterInputs = viewFiltersToUpdate.map((viewFilter) => ({
input: {
id: viewFilter.id,
update: {
value: viewFilter.value,
operand: viewFilter.operand,
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
viewFilterGroupId: viewFilter.viewFilterGroupId,
subFieldName: viewFilter.subFieldName ?? null,
},
},
}));
const deleteViewFilterInputs = viewFiltersToDelete.map((viewFilter) => ({
input: {
id: viewFilter.id,
},
}));
const createResult = await performViewFilterAPICreate(
createViewFilterInputs,
);
if (createResult.status === 'failed') {
return;
}
const updateResult = await performViewFilterAPIUpdate(
updateViewFilterInputs,
);
if (updateResult.status === 'failed') {
return;
}
const deleteResult = await performViewFilterAPIDelete(
deleteViewFilterInputs,
);
if (deleteResult.status === 'failed') {
return;
}
}, [
store,
canPersistChanges,
currentRecordFiltersCallbackState,
performViewFilterAPICreate,
performViewFilterAPIUpdate,
performViewFilterAPIDelete,
contextStoreCurrentViewId,
]);
return {
saveRecordFiltersToViewFilters,
};
};
@@ -16,7 +16,7 @@ export const useSaveRecordSortsToViewSorts = () => {
const {
performViewSortAPICreate,
performViewSortAPIUpdate,
performViewSortAPIDestroy,
performViewSortAPIDelete,
} = usePerformViewSortAPIPersist();
const { currentView } = useGetCurrentViewOnly();
@@ -71,7 +71,7 @@ export const useSaveRecordSortsToViewSorts = () => {
},
}));
const destroyViewSortInputs = viewSortsToDelete.map((viewSort) => ({
const deleteViewSortInputs = viewSortsToDelete.map((viewSort) => ({
input: {
id: viewSort.id,
},
@@ -87,7 +87,7 @@ export const useSaveRecordSortsToViewSorts = () => {
return;
}
const deleteResult = await performViewSortAPIDestroy(destroyViewSortInputs);
const deleteResult = await performViewSortAPIDelete(deleteViewSortInputs);
if (deleteResult.status === 'failed') {
return;
}
@@ -98,7 +98,7 @@ export const useSaveRecordSortsToViewSorts = () => {
currentRecordSortsCallbackState,
performViewSortAPICreate,
performViewSortAPIUpdate,
performViewSortAPIDestroy,
performViewSortAPIDelete,
]);
return {
@@ -24,7 +24,7 @@ export const viewsSelector = createAtomSelector<ViewWithRelations[]>({
flatObjectMetadataItems.map((item) => [item.id, item]),
);
const allFlatViewFields = get(metadataStoreState, 'viewFields')
const flatViewFields = get(metadataStoreState, 'viewFields')
.current as FlatViewField[];
const flatViewFilters = get(metadataStoreState, 'viewFilters')
.current as FlatViewFilter[];
@@ -34,14 +34,9 @@ export const viewsSelector = createAtomSelector<ViewWithRelations[]>({
.current as FlatViewGroup[];
const flatViewFilterGroups = get(metadataStoreState, 'viewFilterGroups')
.current as FlatViewFilterGroup[];
const allFlatViewFieldGroups = get(metadataStoreState, 'viewFieldGroups')
const flatViewFieldGroups = get(metadataStoreState, 'viewFieldGroups')
.current as FlatViewFieldGroup[];
const flatViewFields = allFlatViewFields.filter((field) => field.isActive);
const flatViewFieldGroups = allFlatViewFieldGroups.filter(
(group) => group.isActive,
);
const viewFieldsByViewId = new Map<string, FlatViewField[]>();
const viewFiltersByViewId = new Map<string, FlatViewFilter[]>();
const viewSortsByViewId = new Map<string, FlatViewSort[]>();
@@ -8,7 +8,6 @@ export type ViewField = {
viewId?: string;
fieldMetadataId: string;
position: number;
isActive: boolean;
isVisible: boolean;
size: number;
aggregateOperation?: AggregateOperations | null;
@@ -4,7 +4,6 @@ export type ViewFieldGroup = {
id: string;
name: string;
position: number;
isActive: boolean;
isVisible: boolean;
viewId: string;
viewFields: ViewField[];
@@ -60,7 +60,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
position: 1,
size: 1,
isVisible: false,
isActive: true,
definition: {
fieldMetadataId: '1',
label: 'label 1',
@@ -79,7 +78,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
position: 2,
size: 2,
isVisible: false,
isActive: true,
definition: {
fieldMetadataId: '2',
label: 'label 2',
@@ -98,7 +96,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
position: 3,
size: 3,
isVisible: true,
isActive: true,
definition: {
fieldMetadataId: '3',
label: 'label 3',
@@ -191,7 +188,6 @@ describe('mapColumnDefinitionsToViewFields', () => {
fieldMetadataId: 1,
position: 1,
isVisible: true,
isActive: true,
definition: columnDefinitions[0],
size: undefined,
},
@@ -201,7 +197,6 @@ describe('mapColumnDefinitionsToViewFields', () => {
position: 2,
size: 200,
isVisible: false,
isActive: true,
definition: columnDefinitions[1],
},
];
@@ -12,7 +12,6 @@ export const mapBoardFieldDefinitionsToViewFields = (
size: 0,
position: fieldDefinition.position,
isVisible: fieldDefinition.isVisible ?? true,
isActive: true,
definition: fieldDefinition,
}),
);
@@ -12,7 +12,6 @@ export const mapColumnDefinitionsToViewFields = (
position: columnDefinition.position,
size: columnDefinition.size,
isVisible: columnDefinition.isVisible ?? true,
isActive: true,
definition: columnDefinition,
}));
};
@@ -6,7 +6,6 @@ export const mapRecordFieldToViewField = (recordField: RecordField) => {
id: recordField.id,
fieldMetadataId: recordField.fieldMetadataItemId,
isVisible: recordField.isVisible,
isActive: true,
position: recordField.position,
size: recordField.size,
aggregateOperation: recordField.aggregateOperation,
@@ -133,5 +133,4 @@ export const createTestWidget = (
updatedAt: '2024-01-01',
deletedAt: null,
...overrides,
isActive: overrides.isActive ?? true,
});
+2
View File
@@ -3,6 +3,7 @@ import { type INestApplication } from '@nestjs/common';
import 'jest';
import { type DataSource } from 'typeorm';
import { type DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
declare module '@jest/types' {
@@ -35,6 +36,7 @@ declare global {
var testDataSource: DataSource;
var app: INestApplication;
var dataSourceService: DataSourceService;
var dataSeedWorkspaceCommand: DataSeedWorkspaceCommand;
}
export {};
@@ -4,10 +4,7 @@ import { Command, CommandRunner, Option } from 'nest-commander';
import {
SEED_APPLE_WORKSPACE_ID,
SEED_EMPTY_WORKSPACE_3_ID,
SEED_EMPTY_WORKSPACE_4_ID,
SEED_YCOMBINATOR_WORKSPACE_ID,
SeededEmptyWorkspacesIds,
SeededWorkspacesIds,
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/services/dev-seeder.service';
@@ -45,21 +42,12 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
? [SEED_APPLE_WORKSPACE_ID]
: [SEED_APPLE_WORKSPACE_ID, SEED_YCOMBINATOR_WORKSPACE_ID];
const emptyWorkspaceIds: SeededEmptyWorkspacesIds[] = [
SEED_EMPTY_WORKSPACE_3_ID,
SEED_EMPTY_WORKSPACE_4_ID,
];
try {
for (const workspaceId of workspaceIds) {
await this.devSeederService.seedDev(workspaceId, {
light: options.light,
});
}
for (const workspaceId of emptyWorkspaceIds) {
await this.devSeederService.seedEmptyWorkspace(workspaceId);
}
} catch (error) {
this.logger.error(error);
this.logger.error(error.stack);
@@ -8,6 +8,7 @@ import { InstanceCommandGenerationService } from 'src/database/commands/instance
import { ListOrphanedWorkspaceEntitiesCommand } from 'src/database/commands/list-and-delete-orphaned-workspace-entities.command';
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
import { RunInstanceCommandsCommand } from 'src/database/commands/run-instance-commands.command';
import { InstanceCommandProviderModule } from 'src/database/commands/upgrade-version-command/instance-command-provider.module';
import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module';
import { WorkspaceExportModule } from 'src/database/commands/workspace-export/workspace-export.module';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
@@ -44,6 +45,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
@Module({
imports: [
UpgradeVersionCommandModule,
InstanceCommandProviderModule,
TypeOrmModule.forFeature([WorkspaceEntity]),
WorkspaceExportModule,
// Cron command dependencies
@@ -62,9 +62,6 @@ export class RunInstanceCommandsCommand extends CommandRunner {
await this.checkWorkspaceVersionSafety(options);
await this.runLegacyPendingTypeOrmMigrations();
const activeOrSuspendedWorkspaceIds =
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
for (const {
command,
name,
@@ -82,6 +79,9 @@ export class RunInstanceCommandsCommand extends CommandRunner {
}
if (options.includeSlow) {
const hasWorkspaces =
await this.workspaceVersionService.hasActiveOrSuspendedWorkspaces();
for (const {
command,
name,
@@ -90,7 +90,7 @@ export class RunInstanceCommandsCommand extends CommandRunner {
await this.instanceUpgradeService.runSlowInstanceCommand({
command,
name,
skipDataMigration: activeOrSuspendedWorkspaceIds.length === 0,
skipDataMigration: !hasWorkspaces,
});
if (result.status === 'failed') {
@@ -119,10 +119,10 @@ export class RunInstanceCommandsCommand extends CommandRunner {
return;
}
const activeOrSuspendedWorkspaceIds =
const activeWorkspaceIds =
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
if (activeOrSuspendedWorkspaceIds.length === 0) {
if (activeWorkspaceIds.length === 0) {
return;
}
@@ -141,7 +141,7 @@ export class RunInstanceCommandsCommand extends CommandRunner {
const allAtPreviousVersion =
await this.upgradeMigrationService.areAllWorkspacesAtCommand({
commandName: lastWorkspaceCommand.name,
workspaceIds: activeOrSuspendedWorkspaceIds,
workspaceIds: activeWorkspaceIds,
});
if (!allAtPreviousVersion) {
@@ -1,12 +1,22 @@
import { Module } from '@nestjs/common';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { V1_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-21/1-21-upgrade-version-command.module';
import { V1_22_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-22/1-22-upgrade-version-command.module';
import { V1_23_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module';
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
import { UpgradeModule } from 'src/engine/core-modules/upgrade/upgrade.module';
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
@Module({
imports: [UpgradeModule, WorkspaceVersionModule, WorkspaceIteratorModule],
imports: [
V1_21_UpgradeVersionCommandModule,
V1_22_UpgradeVersionCommandModule,
V1_23_UpgradeVersionCommandModule,
UpgradeModule,
WorkspaceVersionModule,
WorkspaceIteratorModule,
],
providers: [UpgradeCommand],
})
export class UpgradeVersionCommandModule {}
@@ -269,11 +269,10 @@ export class UpgradeCommand extends CommandRunner {
);
for (const workspaceId of workspacesWithoutCursor) {
await this.upgradeMigrationService.markAsWorkspaceInitial({
await this.upgradeMigrationService.markAsInitial({
name: lastWorkspaceCommand.name,
workspaceId,
executedByVersion: '1.21.0',
status: 'completed',
});
}
}

Some files were not shown because too many files have changed in this diff Show More