Merge branch 'main' into feat/add-sub-field-filtering

This commit is contained in:
Lucas Bordeau 2024-10-31 16:26:09 +01:00
commit 547cec1d5c
56 changed files with 21161 additions and 21772 deletions

View File

@ -6,16 +6,29 @@ on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
package.json
packages/twenty-tinybird/**
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed == 'false'
run: echo "No relevant changes. Skipping CI."
- name: Check twenty-tinybird package
uses: tinybirdco/ci/.github/workflows/ci.yml@main
with:
data_project_dir: packages/twenty-tinybird
secrets:
tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }}
tb_host: https://api.eu-central-1.aws.tinybird.co

View File

@ -33,3 +33,7 @@ Your turn 👇
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-crm)
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/is-twenty-crm-the-right-tool-for-your-business-heres-my-honest-review-0d41e9d8a7eb)
» 30-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) blog Link: [blog](https://dev.to/adityadeshlahre/transform-your-customer-relationships-with-the-leading-open-source-crm-twenty-161d)
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) blog Link: [blog](https://zedblock.com/blog/twenty-crm)

View File

@ -31,3 +31,7 @@ Your turn 👇
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-self-host)
» 28-October-2024 by [AshishViradiya153](https://oss.gg/AshishViradiya153) blog Link: [blog](https://medium.com/@ashishviradiya153/comprehensive-guide-to-self-hosting-twenty-crm-26e7fa36c846)
» 30-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) blog Link: [blog](https://dev.to/adityadeshlahre/complete-guide-to-self-hosting-twenty-crm-2h08)
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) blog Link: [blog](https://dev.to/raju_gangitla_91920e1427f/self-hosting-twenty-crm-a-complete-guide-559n)

View File

@ -18,11 +18,13 @@ Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) Figma Link: [Figma](https://www.figma.com/design/XE21QdkFuy0IJHtmW7TURa/Twenty-(rajeevDewangan)?node-id=0-1&node-type=canvas&t=BYBulCT6hpJu6E8G-0)
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1)
» 24-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Figma Link: [Figma](https://www.figma.com/design/2qlAPS3llwf8jrWKGHEf6O/Twenty-(sateshcharan)?node-id=1633-94880&t=GIceWxqyY0ajWXnZ-1)
» 28-October-2024 by [Vanshdeep Singh](https://oss.gg/Vanshdeepsingh-2232) Figma Link:[Figma](https://www.figma.com/design/akgDOb37YLUW9iWLB155EV/Twenty-(Copy)?node-id=478-19796&t=8Gz1yqls2Q3dsN9h-1)
» 30-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) Figma Link:[Figma](https://www.figma.com/design/4hlpS6LOIaJqDbd6T6SlXc/CustomTwentyByAdityDeshlahre?node-id=478-19796&t=Dp8EBpl0FxVjiGHT-1) Figma Preview Link:[Prototype](https://www.figma.com/proto/4hlpS6LOIaJqDbd6T6SlXc/CustomTwentyByAdityDeshlahre?node-id=478-19796&t=Dp8EBpl0FxVjiGHT-1)

View File

@ -20,4 +20,9 @@ Your turn 👇
» 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view)
» 24-October-2024 by [chrisdadev13](https://oss.gg/chrisdadev13) video Link: [video](https://www.loom.com/share/f98f34f8d9b34728998847d3b97a16b7)
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://drive.google.com/file/d/1-wgzofJaWmnMcFgZZV5uYNNgtbJKJ_1G/view?usp=sharing/)
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) video Link: [video](https://www.loom.com/share/f072bf31fb46449d98d6826a3a824fe9?sid=21f2c3f4-f286-43a2-98aa-d1fb92c3a86e)

View File

@ -20,4 +20,5 @@ Your turn 👇
» 25-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) template Link: [template]()
» 30-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) video Link: [video](https://www.loom.com/share/89f86ef895e946fbbbbae3cc90559bb7?sid=dbebe60b-3ece-4ac8-acb2-184043c6f87b)
---

View File

@ -20,4 +20,6 @@ Your turn 👇
» 26-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) guide Link: [guide](https://dev.to/sateshcharan/supercharge-your-marketing-with-twentycrm-n8n-1hfd)
» 31-October-2024 by [RajuGangitla](https://oss.gg/RajuGangitla) guide Link: [guide](https://dev.to/raju_gangitla_91920e1427f/automating-people-data-sync-n8n-workflow-for-twenty-crm-and-google-sheets-5789
)
---

View File

@ -10,7 +10,7 @@ const modulesCoverage = {
branches: 25,
statements: 49,
lines: 50,
functions: 40,
functions: 38,
include: ['src/modules/**/*'],
exclude: ['src/**/*.ts'],
};

View File

@ -102,6 +102,7 @@ export const DeleteRecordsActionEffect = ({
position,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
onClick: () => {
setIsDeleteRecordsModalOpen(true);
},

View File

@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton';
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
@ -30,7 +31,9 @@ export const RecordIndexActionMenuBar = () => {
actionMenuEntriesComponentSelector,
);
if (actionMenuEntries.length === 0) {
const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned);
if (pinnedEntries.length === 0) {
return null;
}
@ -42,9 +45,10 @@ export const RecordIndexActionMenuBar = () => {
}}
>
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
{actionMenuEntries.map((entry, index) => (
{pinnedEntries.map((entry, index) => (
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
))}
<RecordIndexActionMenuBarAllActionsButton />
</BottomBar>
);
};

View File

@ -0,0 +1,53 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconLayoutSidebarRightExpand } from 'twenty-ui';
const StyledButton = styled.div`
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
justify-content: center;
padding: ${({ theme }) => theme.spacing(2)};
transition: background ${({ theme }) => theme.animation.duration.fast} ease;
user-select: none;
&:hover {
background: ${({ theme }) => theme.background.tertiary};
}
`;
const StyledButtonLabel = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-left: ${({ theme }) => theme.spacing(1)};
`;
const StyledShortcutLabel = styled.div`
color: ${({ theme }) => theme.font.color.light};
font-weight: ${({ theme }) => theme.font.weight.medium};
`;
const StyledSeparator = styled.div<{ size: 'sm' | 'md' }>`
background: ${({ theme }) => theme.border.color.light};
height: ${({ theme, size }) => theme.spacing(size === 'sm' ? 4 : 8)};
margin: 0 ${({ theme }) => theme.spacing(1)};
width: 1px;
`;
export const RecordIndexActionMenuBarAllActionsButton = () => {
const theme = useTheme();
const { openCommandMenu } = useCommandMenu();
return (
<>
<StyledSeparator size="md" />
<StyledButton onClick={() => openCommandMenu()}>
<IconLayoutSidebarRightExpand size={theme.icon.size.md} />
<StyledButtonLabel>All Actions</StyledButtonLabel>
<StyledSeparator size="sm" />
<StyledShortcutLabel>K</StyledShortcutLabel>
</StyledButton>
</>
);
};

View File

@ -2,31 +2,24 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
type RecordIndexActionMenuBarEntryProps = {
entry: ActionMenuEntry;
};
const StyledButton = styled.div<{ accent: MenuItemAccent }>`
const StyledButton = styled.div`
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${(props) =>
props.accent === 'danger'
? props.theme.color.red
: props.theme.font.color.secondary};
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
justify-content: center;
padding: ${({ theme }) => theme.spacing(2)};
transition: background 0.1s ease;
transition: background ${({ theme }) => theme.animation.duration.fast} ease;
user-select: none;
&:hover {
background: ${({ theme, accent }) =>
accent === 'danger'
? theme.background.danger
: theme.background.tertiary};
background: ${({ theme }) => theme.background.tertiary};
}
`;
@ -40,10 +33,7 @@ export const RecordIndexActionMenuBarEntry = ({
}: RecordIndexActionMenuBarEntryProps) => {
const theme = useTheme();
return (
<StyledButton
accent={entry.accent ?? 'default'}
onClick={() => entry.onClick?.()}
>
<StyledButton onClick={() => entry.onClick?.()}>
{entry.Icon && <entry.Icon size={theme.icon.size.md} />}
<StyledButtonLabel>{entry.label}</StyledButtonLabel>
</StyledButton>

View File

@ -10,15 +10,15 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
import { userEvent, waitFor, within } from '@storybook/test';
import { IconCheckbox, IconTrash } from 'twenty-ui';
import { IconTrash, RouterDecorator } from 'twenty-ui';
const deleteMock = jest.fn();
const markAsDoneMock = jest.fn();
const meta: Meta<typeof RecordIndexActionMenuBar> = {
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
component: RecordIndexActionMenuBar,
decorators: [
RouterDecorator,
(Story) => (
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
@ -48,6 +48,7 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
[
'delete',
{
isPinned: true,
key: 'delete',
label: 'Delete',
position: 0,
@ -55,16 +56,6 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
onClick: deleteMock,
},
],
[
'markAsDone',
{
key: 'markAsDone',
label: 'Mark as done',
position: 1,
Icon: IconCheckbox,
onClick: markAsDoneMock,
},
],
]),
);
set(
@ -120,12 +111,8 @@ export const WithButtonClicks: Story = {
const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton);
const markAsDoneButton = await canvas.findByText('Mark as done');
await userEvent.click(markAsDoneButton);
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
expect(markAsDoneMock).toHaveBeenCalled();
});
},
};

View File

@ -8,6 +8,7 @@ export type ActionMenuEntry = {
label: string;
position: number;
Icon: IconComponent;
isPinned?: boolean;
accent?: MenuItemAccent;
onClick?: (event?: MouseEvent<HTMLElement>) => void;
ConfirmationModal?: ReactNode;

View File

@ -19,8 +19,6 @@ export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSig
__typename: true,
createdAt: true,
updatedAt: true,
activity: true,
activityId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
});

View File

@ -94,6 +94,8 @@ const mocks: MockedResponse[] = [
updatedAt
viewId
workflowId
workflowRunId
workflowVersionId
workspaceMemberId
}
}

View File

@ -295,184 +295,6 @@ export const mocks = [
updatedAt
}
workflowId
workspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
timeFormat
timeZone
updatedAt
userEmail
userId
}
workspaceMemberId
}
}
companyId
createdAt
deletedAt
id
note {
__typename
body
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
position
title
updatedAt
}
noteId
opportunity {
__typename
amount {
amountMicros
currencyCode
}
closeDate
companyId
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
name
pointOfContactId
position
stage
updatedAt
}
opportunityId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
personId
position
rocket {
__typename
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
id
name
position
updatedAt
}
rocketId
task {
__typename
assigneeId
body
createdAt
createdBy {
source
workspaceMemberId
name
}
deletedAt
dueAt
id
position
status
title
updatedAt
}
taskId
updatedAt
view {
__typename
createdAt
deletedAt
icon
id
isCompact
kanbanFieldMetadataId
key
name
objectMetadataId
position
type
updatedAt
}
viewId
workflow {
__typename
createdAt
deletedAt
id
lastPublishedVersionId
name
position
statuses
updatedAt
}
workflowId
workflowRun {
__typename
createdAt
@ -554,8 +376,8 @@ export const mocks = [
mutation DeleteOneFavorite($idToDelete: ID!) {
deleteFavorite(id: $idToDelete) {
__typename
id
deletedAt
id
}
}
`,

View File

@ -1,5 +1,5 @@
import { DropResult, ResponderProvided } from '@hello-pangea/dnd';
import { act, renderHook, waitFor } from '@testing-library/react';
import { renderHook, waitFor } from '@testing-library/react';
import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -7,6 +7,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { act } from 'react';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import {

View File

@ -5,8 +5,11 @@ export const mapSoftDeleteFieldsToGraphQLQuery = (
): string => {
const softDeleteFields = ['deletedAt', 'id'];
const fieldsThatShouldBeQueried = objectMetadataItem.fields.filter(
(field) => field.isActive && softDeleteFields.includes(field.name),
const fieldsThatShouldBeQueried = objectMetadataItem.fields
.filter((field) => field.isActive && softDeleteFields.includes(field.name))
.sort(
(a, b) =>
softDeleteFields.indexOf(a.name) - softDeleteFields.indexOf(b.name),
);
return `{

View File

@ -8,8 +8,8 @@ const expectedQueryTemplate = `
mutation DeleteOnePerson($idToDelete: ID!) {
deletePerson(id: $idToDelete) {
__typename
id
deletedAt
id
}
}
`.replace(/\s/g, '');

View File

@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum';
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect';
import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
@ -16,7 +17,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
@ -69,9 +70,15 @@ export const RecordBoard = () => {
const { resetRecordSelection, setRecordAsSelected } =
useRecordBoardSelection(recordBoardId);
useListenClickOutsideByClassName({
classNames: ['record-board-card'],
excludeClassNames: ['bottom-bar', 'action-menu-dropdown', 'command-menu'],
useListenClickOutsideV2({
excludeClassNames: [
'bottom-bar',
'action-menu-dropdown',
'command-menu',
'modal-backdrop',
],
listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID,
refs: [boardRef],
callback: resetRecordSelection,
});

View File

@ -0,0 +1 @@
export const RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID = 'record-board';

View File

@ -84,9 +84,9 @@ export const MultiItemFieldInput = <T,>({
setInputValue(value);
if (!validateInput) return;
if (errorData.isValid) {
setErrorData(errorData);
}
setErrorData(
errorData.isValid ? errorData : { isValid: true, errorMessage: '' },
);
};
const handleAddButtonClick = () => {

View File

@ -7,6 +7,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
@ -81,12 +82,14 @@ export const RecordIndexTableContainerEffect = () => {
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
const unselectedRowIds = useRecoilValue(unselectedRowIdsSelector());
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
useEffect(() => {
if (hasUserSelectedAllRows) {
setContextStoreTargetedRecords({
mode: 'exclusion',
excludedRecordIds: unselectedRowIds,
filters: [],
filters: recordIndexFilters,
});
} else {
setContextStoreTargetedRecords({
@ -103,6 +106,7 @@ export const RecordIndexTableContainerEffect = () => {
};
}, [
hasUserSelectedAllRows,
recordIndexFilters,
selectedRowIds,
setContextStoreTargetedRecords,
unselectedRowIds,

View File

@ -1,19 +1,26 @@
import { Key } from 'ts-key-enum';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
type RecordTableInternalEffectProps = {
recordTableId: string;
tableBodyRef: React.RefObject<HTMLDivElement>;
};
export const RecordTableInternalEffect = ({
recordTableId,
tableBodyRef,
}: RecordTableInternalEffectProps) => {
const { leaveTableFocus, resetTableRowSelection, useMapKeyboardToSoftFocus } =
useRecordTable({ recordTableId });
const leaveTableFocus = useLeaveTableFocus(recordTableId);
const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({
recordTableId,
});
useMapKeyboardToSoftFocus();
@ -25,9 +32,15 @@ export const RecordTableInternalEffect = ({
TableHotkeyScope.Table,
);
useListenClickOutsideByClassName({
classNames: ['entity-table-cell'],
excludeClassNames: ['bottom-bar', 'action-menu-dropdown', 'command-menu'],
useListenClickOutsideV2({
excludeClassNames: [
'bottom-bar',
'action-menu-dropdown',
'command-menu',
'modal-backdrop',
],
listenerId: RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
refs: [tableBodyRef],
callback: () => {
leaveTableFocus();
},

View File

@ -87,7 +87,10 @@ export const RecordTableWithWrappers = ({
onDragSelectionChange={setRowSelected}
/>
</StyledTableInternalContainer>
<RecordTableInternalEffect recordTableId={recordTableId} />
<RecordTableInternalEffect
tableBodyRef={tableBodyRef}
recordTableId={recordTableId}
/>
</StyledTableContainer>
</StyledTableWithHeader>
</RecordUpdateContext.Provider>

View File

@ -0,0 +1 @@
export const RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID = 'record-table';

View File

@ -1,19 +1,13 @@
import { useRecoilCallback } from 'recoil';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useCloseCurrentTableCellInEditMode } from './useCloseCurrentTableCellInEditMode';
import { useDisableSoftFocus } from './useDisableSoftFocus';
export const useLeaveTableFocus = (recordTableId?: string) => {
const disableSoftFocus = useDisableSoftFocus(recordTableId);
const closeCurrentCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId);
const { isSoftFocusActiveState } = useRecordTableStates(recordTableId);
@ -27,28 +21,14 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
isSoftFocusActiveState,
);
const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
.getValue();
resetTableRowSelection();
if (!isSoftFocusActive) {
return;
}
if (currentHotkeyScope?.scope === TableHotkeyScope.Table) {
return;
}
closeCurrentCellInEditMode();
disableSoftFocus();
},
[
closeCurrentCellInEditMode,
disableSoftFocus,
isSoftFocusActiveState,
resetTableRowSelection,
],
[disableSoftFocus, isSoftFocusActiveState, resetTableRowSelection],
);
};

View File

@ -4,10 +4,19 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
import { useSetRecoilState } from 'recoil';
export const RecordTableCellFieldInput = () => {
const { getClickOutsideListenerIsActivatedState } =
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
const setClickOutsideListenerIsActivated = useSetRecoilState(
getClickOutsideListenerIsActivatedState,
);
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
useContext(RecordTableContext);
@ -40,6 +49,8 @@ export const RecordTableCellFieldInput = () => {
};
const handleClickOutside: FieldInputEvent = (persistField) => {
setClickOutsideListenerIsActivated(false);
onUpsertRecord({
persistField,
recordId,

View File

@ -21,6 +21,8 @@ import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDefined } from '~/utils/isDefined';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
@ -42,6 +44,9 @@ export type OpenTableCellArgs = {
};
export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const { getClickOutsideListenerIsActivatedState } =
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
const moveEditModeToTableCellPosition =
useMoveEditModeToTableCellPosition(tableScopeId);
@ -65,7 +70,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const navigate = useNavigate();
const openTableCell = useRecoilCallback(
({ snapshot }) =>
({ snapshot, set }) =>
({
initialValue,
cellPosition,
@ -80,6 +85,8 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
return;
}
set(getClickOutsideListenerIsActivatedState, false);
const isFirstColumnCell = cellPosition.column === 0;
const fieldValue = getSnapshotValue(
@ -137,17 +144,18 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
}
},
[
getClickOutsideListenerIsActivatedState,
setDragSelectionStartEnabled,
moveEditModeToTableCellPosition,
initDraftValue,
toggleClickOutsideListener,
leaveTableFocus,
setHotkeyScope,
initDraftValue,
moveEditModeToTableCellPosition,
openRightDrawer,
navigate,
indexIdentifierUrl,
setViewableRecordId,
setViewableRecordNameSingular,
indexIdentifierUrl,
navigate,
openRightDrawer,
setHotkeyScope,
],
);

View File

@ -23,7 +23,6 @@ export const SingleEntitySelect = ({
onEntitySelected,
relationObjectNameSingular,
relationPickerScopeId,
selectedEntity,
selectedRelationRecordIds,
width = 200,
}: SingleEntitySelectProps) => {
@ -61,7 +60,6 @@ export const SingleEntitySelect = ({
onEntitySelected,
relationObjectNameSingular,
relationPickerScopeId,
selectedEntity,
selectedRelationRecordIds,
}}
/>

View File

@ -37,6 +37,7 @@ export type SingleEntitySelectMenuItemsProps = {
onAllEntitySelected?: () => void;
hotkeyScope?: string;
isFiltered: boolean;
shouldSelectEmptyOption?: boolean;
};
export const SingleEntitySelectMenuItems = ({
@ -56,6 +57,7 @@ export const SingleEntitySelectMenuItems = ({
onAllEntitySelected,
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
isFiltered,
shouldSelectEmptyOption,
}: SingleEntitySelectMenuItemsProps) => {
const containerRef = useRef<HTMLDivElement>(null);
@ -181,7 +183,7 @@ export const SingleEntitySelectMenuItems = ({
onClick={() => onEntitySelected()}
LeftIcon={EmptyIcon}
text={emptyLabel}
selected={!selectedEntity}
selected={shouldSelectEmptyOption === true}
hovered={isSelectedSelectNoneButton}
/>
)

View File

@ -35,7 +35,6 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
onEntitySelected,
relationObjectNameSingular,
relationPickerScopeId = 'relation-picker',
selectedEntity,
selectedRelationRecordIds,
dropdownPlacement,
}: SingleEntitySelectMenuItemsWithSearchProps) => {
@ -71,11 +70,11 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
selectedEntity={
selectedEntity ??
(entities.selectedEntities.length === 1
entities.selectedEntities.length === 1
? entities.selectedEntities[0]
: undefined)
: undefined
}
shouldSelectEmptyOption={selectedRelationRecordIds?.length === 0}
hotkeyScope={relationPickerScopeId}
onCreate={onCreateWithInput}
isFiltered={!!relationPickerSearchFilter}

View File

@ -111,6 +111,8 @@ const companyMocks = [
updatedAt
viewId
workflowId
workflowRunId
workflowVersionId
workspaceMemberId
}
}
@ -260,6 +262,9 @@ const companyMocks = [
rocketId
taskId
updatedAt
workflowId
workflowRunId
workflowVersionId
workspaceMemberId
}
}

View File

@ -37,7 +37,7 @@ export const sanitizeRecordInput = ({
(field) => field.name === relationIdFieldName,
);
return relationIdFieldMetadataItem && fieldValue?.id
return relationIdFieldMetadataItem
? [relationIdFieldName, fieldValue?.id ?? null]
: undefined;
}

View File

@ -100,7 +100,6 @@ type SettingsDataModelFieldSettingsFormCardProps = {
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
display: grid;
flex: 1 1 100%;
`;

View File

@ -2,6 +2,8 @@ import styled from '@emotion/styled';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { ViewBar } from '@/views/components/ViewBar';
@ -20,9 +22,26 @@ export const SignInBackgroundMockContainer = () => {
const recordIndexId = 'sign-up-mock-record-table-id';
const viewBarId = 'companies-mock';
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
return (
<StyledContainer>
<ViewComponentInstanceContext.Provider value={{ instanceId: viewBarId }}>
<RecordIndexRootPropsContext.Provider
value={{
recordIndexId,
objectNamePlural,
objectNameSingular,
objectMetadataItem,
onIndexRecordsLoaded: () => {},
indexIdentifierUrl: () => '',
onCreateRecord: () => {},
}}
>
<ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
<ContextStoreComponentInstanceContext.Provider
value={{
instanceId: recordIndexId,
@ -33,7 +52,7 @@ export const SignInBackgroundMockContainer = () => {
>
<ViewBar
viewBarId={viewBarId}
onCurrentViewChange={async () => {}}
onCurrentViewChange={() => {}}
optionsDropdownButton={<></>}
/>
<SignInBackgroundMockContainerEffect
@ -50,6 +69,7 @@ export const SignInBackgroundMockContainer = () => {
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider>
</StyledContainer>
);
};

View File

@ -1,4 +1,4 @@
import { act, renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilState } from 'recoil';
import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog';
@ -8,6 +8,7 @@ import {
ImportedRow,
SpreadsheetImportDialogOptions,
} from '@/spreadsheet-import/types';
import { act } from 'react';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>

View File

@ -26,6 +26,7 @@ const StyledControlContainer = styled.div<{
color: ${({ disabled, theme }) =>
disabled ? theme.font.color.tertiary : theme.font.color.primary};
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
text-align: left;
`;
const StyledIconChevronDown = styled(IconChevronDown)<{

View File

@ -126,7 +126,6 @@ const StyledContainer = styled.div<{ calendarDisabled?: boolean }>`
}
& .react-datepicker__month-dropdown {
left: ${({ theme }) => theme.spacing(2)};
width: 160px;
height: 260px;
}

View File

@ -22,9 +22,9 @@ export type NavigationDrawerProps = {
title?: string;
};
const StyledAnimatedContainer = styled(motion.div)`
const StyledAnimatedContainer = styled(motion.div)<{ isSettings?: boolean }>`
max-height: 100vh;
overflow: hidden;
overflow: ${({ isSettings }) => (isSettings ? 'visible' : 'hidden')};
`;
const StyledContainer = styled.div<{
@ -50,11 +50,12 @@ const StyledContainer = styled.div<{
padding-right: 20px;
}
`;
const StyledItemsContainer = styled.div`
const StyledItemsContainer = styled.div<{ isSettings?: boolean }>`
display: flex;
flex-direction: column;
margin-bottom: auto;
overflow: hidden;
overflow: ${({ isSettings }) => (isSettings ? 'visible' : 'hidden')};
flex: 1;
`;
@ -102,6 +103,7 @@ export const NavigationDrawer = ({
transition={{
duration: theme.animation.duration.normal,
}}
isSettings={isSettingsDrawer}
>
<StyledContainer
isSettings={isSettingsDrawer}
@ -118,7 +120,9 @@ export const NavigationDrawer = ({
showCollapseButton={isHovered}
/>
)}
<StyledItemsContainer>{children}</StyledItemsContainer>
<StyledItemsContainer isSettings={isSettingsDrawer}>
{children}
</StyledItemsContainer>
{footer}
</StyledContainer>
</StyledAnimatedContainer>

View File

@ -1,13 +1,12 @@
import { fireEvent, renderHook } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { fireEvent, render, renderHook } from '@testing-library/react';
import { isDefined } from '~/utils/isDefined';
import {
ClickOutsideMode,
useListenClickOutside,
useListenClickOutsideByClassName,
} from '../useListenClickOutside';
const containerRef = React.createRef<HTMLDivElement>();
@ -77,59 +76,3 @@ describe('useListenClickOutside', () => {
expect(callback).toHaveBeenCalled();
});
});
describe('useListenClickOutsideByClassName', () => {
it('should trigger the callback when clicking outside the specified class names', () => {
const callback = jest.fn();
const { container } = render(
<div>
<div className="wont-trigger other-class">Inside</div>
<div className="will-trigger">Outside</div>
</div>,
);
renderHook(() =>
useListenClickOutsideByClassName({
classNames: ['wont-trigger'],
callback,
}),
);
act(() => {
const notClickableElement = container.querySelector('.will-trigger');
if (isDefined(notClickableElement)) {
fireEvent.mouseDown(notClickableElement);
fireEvent.click(notClickableElement);
}
});
expect(callback).toHaveBeenCalled();
});
it('should not trigger the callback when clicking inside the specified class names', () => {
const callback = jest.fn();
const { container } = render(
<div>
<div className="wont-trigger other-class">Inside</div>
<div className="will-trigger">Outside</div>
</div>,
);
renderHook(() =>
useListenClickOutsideByClassName({
classNames: ['wont-trigger'],
callback,
}),
);
act(() => {
const notClickableElement = container.querySelector('.wont-trigger');
if (isDefined(notClickableElement)) {
fireEvent.mouseDown(notClickableElement);
fireEvent.click(notClickableElement);
}
});
expect(callback).not.toHaveBeenCalled();
});
});

View File

@ -138,58 +138,3 @@ export const useListenClickOutside = <T extends Element>({
}
}, [refs, callback, mode, enabled, isMouseDownInside]);
};
export const useListenClickOutsideByClassName = ({
classNames,
excludeClassNames,
callback,
}: {
classNames: string[];
excludeClassNames?: string[];
callback: () => void;
}) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (!(event.target instanceof Node)) return;
const clickedElement = event.target as HTMLElement;
let isClickedInside = false;
let isClickedOnExcluded = false;
let currentElement: HTMLElement | null = clickedElement;
while (currentElement) {
const currentClassList = currentElement.classList;
isClickedInside = classNames.some((className) =>
currentClassList.contains(className),
);
isClickedOnExcluded =
excludeClassNames?.some((className) =>
currentClassList.contains(className),
) ?? false;
if (isClickedInside || isClickedOnExcluded) {
break;
}
currentElement = currentElement.parentElement;
}
if (!isClickedInside && !isClickedOnExcluded) {
callback();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchend', handleClickOutside, {
capture: true,
});
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchend', handleClickOutside, {
capture: true,
});
};
}, [callback, classNames, excludeClassNames]);
};

View File

@ -10,6 +10,7 @@ export enum ClickOutsideMode {
export type ClickOutsideListenerProps<T extends Element> = {
refs: Array<React.RefObject<T>>;
excludeClassNames?: string[];
callback: (event: MouseEvent | TouchEvent) => void;
mode?: ClickOutsideMode;
listenerId: string;
@ -18,6 +19,7 @@ export type ClickOutsideListenerProps<T extends Element> = {
export const useListenClickOutsideV2 = <T extends Element>({
refs,
excludeClassNames,
callback,
mode = ClickOutsideMode.compareHTMLRef,
listenerId,
@ -106,11 +108,34 @@ export const useListenClickOutsideV2 = <T extends Element>({
.getValue();
if (mode === ClickOutsideMode.compareHTMLRef) {
const clickedElement = event.target as HTMLElement;
let isClickedOnExcluded = false;
let currentElement: HTMLElement | null = clickedElement;
while (currentElement) {
const currentClassList = currentElement.classList;
isClickedOnExcluded =
excludeClassNames?.some((className) =>
currentClassList.contains(className),
) ?? false;
if (isClickedOnExcluded) {
break;
}
currentElement = currentElement.parentElement;
}
const clickedOnAtLeastOneRef = refs
.filter((ref) => !!ref.current)
.some((ref) => ref.current?.contains(event.target as Node));
if (!clickedOnAtLeastOneRef && !isMouseDownInside) {
if (
!clickedOnAtLeastOneRef &&
!isMouseDownInside &&
!isClickedOnExcluded
) {
callback(event);
}
}
@ -151,7 +176,13 @@ export const useListenClickOutsideV2 = <T extends Element>({
}
}
},
[mode, refs, callback, getClickOutsideListenerIsMouseDownInsideState],
[
getClickOutsideListenerIsMouseDownInsideState,
mode,
refs,
excludeClassNames,
callback,
],
);
useEffect(() => {

View File

@ -152,7 +152,9 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const sourceViewCombinedFilterGroups = getViewFilterGroupsCombined(
sourceView.id,
);
const sourceViewCombinedFilters = getViewFiltersCombined(sourceView.id);
const sourceViewCombinedFilters = getViewFiltersCombined(
sourceView.id,
);
const sourceViewCombinedSorts = getViewSortsCombined(sourceView.id);
await createViewSortRecords(sourceViewCombinedSorts, newView);

View File

@ -113,6 +113,33 @@ export const graphqlMocks = {
},
});
}),
graphql.query('FindManyViews', ({ variables }) => {
const objectMetadataId = variables.filter?.objectMetadataId?.eq;
const viewType = variables.filter?.type?.eq;
return HttpResponse.json({
data: {
views: {
edges: mockedViewsData
.filter(
(view) =>
view?.objectMetadataId === objectMetadataId &&
view?.type === viewType,
)
.map((view) => ({
node: view,
cursor: null,
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
},
},
});
}),
graphql.query('SearchWorkspaceMembers', () => {
return HttpResponse.json({
data: {

View File

@ -25,7 +25,8 @@ export const mockedViewsData = [
createdAt: '2021-09-01T00:00:00.000Z',
updatedAt: '2021-09-01T00:00:00.000Z',
isCompact: false,
viewFilterGroups: [],
viewGroups: [],
__typename: 'View',
},
{
@ -40,6 +41,8 @@ export const mockedViewsData = [
createdAt: '2021-09-01T00:00:00.000Z',
updatedAt: '2021-09-01T00:00:00.000Z',
isCompact: false,
viewFilterGroups: [],
viewGroups: [],
__typename: 'View',
},
{
@ -54,6 +57,8 @@ export const mockedViewsData = [
createdAt: '2021-09-01T00:00:00.000Z',
updatedAt: '2021-09-01T00:00:00.000Z',
isCompact: false,
viewFilterGroups: [],
viewGroups: [],
__typename: 'View',
},
{
@ -68,6 +73,8 @@ export const mockedViewsData = [
createdAt: '2021-09-01T00:00:00.000Z',
updatedAt: '2021-09-01T00:00:00.000Z',
isCompact: false,
viewFilterGroups: [],
viewGroups: [],
__typename: 'View',
},
];

View File

@ -1,14 +1,13 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { ObjectLiteral, Repository } from 'typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
@Command({
name: 'upgrade-0.32:copy-webhook-operation-into-operations',

View File

@ -79,11 +79,10 @@ export class SimplifySearchVectorExpressionCommand extends ActiveWorkspacesComma
fieldsUsedForSearch = SEARCH_FIELDS_FOR_OPPORTUNITY;
break;
}
default: {
throw new Error(
`search vector has unexpected standardId: ${searchVectorField.standardId}`,
);
}
if (fieldsUsedForSearch.length === 0) {
continue;
}
await this.searchService.updateSearchVector(

View File

@ -4,6 +4,7 @@ import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { CopyWebhookOperationIntoOperationsCommand } from 'src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command';
import { SimplifySearchVectorExpressionCommand } from 'src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
@ -25,6 +26,7 @@ export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner {
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand,
private readonly simplifySearchVectorExpressionCommand: SimplifySearchVectorExpressionCommand,
private readonly copyWebhookOperationIntoOperationsCommand: CopyWebhookOperationIntoOperationsCommand,
) {
super(workspaceRepository);
}
@ -54,5 +56,11 @@ export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner {
options,
workspaceIds,
);
await this.copyWebhookOperationIntoOperationsCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
}
}

View File

@ -88,12 +88,12 @@ export class GraphqlQuerySearchResolverService
qb.where(
searchTerms === ''
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`,
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery('simple', :searchTerms)`,
searchTerms === '' ? {} : { searchTerms },
).orWhere(
searchTermsOr === ''
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTermsOr)`,
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery('simple', :searchTermsOr)`,
searchTermsOr === '' ? {} : { searchTermsOr },
);
}),

View File

@ -25,7 +25,6 @@ const ATTACHMENT_GQL_FIELDS = `
updatedAt
deletedAt
authorId
activityId
taskId
noteId
personId
@ -70,7 +69,6 @@ describe('attachments resolvers (integration)', () => {
expect(attachment).toHaveProperty('updatedAt');
expect(attachment).toHaveProperty('deletedAt');
expect(attachment).toHaveProperty('authorId');
expect(attachment).toHaveProperty('activityId');
expect(attachment).toHaveProperty('taskId');
expect(attachment).toHaveProperty('noteId');
expect(attachment).toHaveProperty('personId');
@ -104,7 +102,6 @@ describe('attachments resolvers (integration)', () => {
expect(createdAttachment).toHaveProperty('updatedAt');
expect(createdAttachment).toHaveProperty('deletedAt');
expect(createdAttachment).toHaveProperty('authorId');
expect(createdAttachment).toHaveProperty('activityId');
expect(createdAttachment).toHaveProperty('taskId');
expect(createdAttachment).toHaveProperty('noteId');
expect(createdAttachment).toHaveProperty('personId');
@ -137,7 +134,6 @@ describe('attachments resolvers (integration)', () => {
expect(attachments).toHaveProperty('updatedAt');
expect(attachments).toHaveProperty('deletedAt');
expect(attachments).toHaveProperty('authorId');
expect(attachments).toHaveProperty('activityId');
expect(attachments).toHaveProperty('taskId');
expect(attachments).toHaveProperty('noteId');
expect(attachments).toHaveProperty('personId');
@ -169,7 +165,6 @@ describe('attachments resolvers (integration)', () => {
expect(attachment).toHaveProperty('updatedAt');
expect(attachment).toHaveProperty('deletedAt');
expect(attachment).toHaveProperty('authorId');
expect(attachment).toHaveProperty('activityId');
expect(attachment).toHaveProperty('taskId');
expect(attachment).toHaveProperty('noteId');
expect(attachment).toHaveProperty('personId');

View File

@ -14,8 +14,8 @@ import { generateRecordName } from 'test/integration/utils/generate-record-name'
const NOTE_TARGET_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
const NOTE_TARGET_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
const NOTE_TARGET_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
const PERSON_1_ID = '777a8457-eb2d-40ac-a707-441b615b6989';
const PERSON_2_ID = '777a8457-eb2d-40ac-a707-331b615b6989';
const PERSON_1_ID = 'f875ff46-b881-41ba-973a-b9cd5345e8f0';
const PERSON_2_ID = '1fe0f78c-8c59-4ce6-ae02-56571331b252';
const NOTE_TARGET_GQL_FIELDS = `
id
createdAt

View File

@ -14,8 +14,8 @@ import { generateRecordName } from 'test/integration/utils/generate-record-name'
const TASK_TARGET_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
const TASK_TARGET_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
const TASK_TARGET_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
const PERSON_1_ID = '777a8457-eb2d-40ac-a707-441b615b6989';
const PERSON_2_ID = '777a8457-eb2d-40ac-a707-331b615b6989';
const PERSON_1_ID = 'f875ff46-b881-41ba-973a-b9cd5345e8f0';
const PERSON_2_ID = '1fe0f78c-8c59-4ce6-ae02-56571331b252';
const TASK_TARGET_GQL_FIELDS = `
id
createdAt

View File

@ -42,7 +42,7 @@ const StyledTag = styled.h3<{
padding: 0 ${spacing2};
border: ${({ variant, theme }) =>
variant === 'outline' || variant === 'border'
? `1px ${variant === 'border' ? 'solid' : 'dash'} ${theme.border.color.strong}`
? `1px ${variant === 'border' ? 'solid' : 'dashed'} ${theme.border.color.strong}`
: ''};
gap: ${spacing1};