AI tools to create a demo workspace (#18236)
This PR adds the necessary tool to create a demo workspace with : relevant custom objects and fields, mock data and a real dashboard with graph widgets. It is still a bit under-optimized and slow but it works. This PR also adds an AI tool that allows to see what happens in real time, it navigates the app and waits when necessary. --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
+10
-2
@@ -1,11 +1,19 @@
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { PageFocusId } from '@/types/PageFocusId';
|
||||
import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export const useResetFocusStackToRecordIndex = () => {
|
||||
const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem();
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const resetFocusStackToRecordIndex = () => {
|
||||
const isCommandMenuOpen = store.get(isCommandMenuOpenedState.atom);
|
||||
|
||||
const shouldEnableGlobalHotkeys = !isCommandMenuOpen;
|
||||
|
||||
resetFocusStackToFocusItem({
|
||||
focusStackItem: {
|
||||
focusId: PageFocusId.RecordIndex,
|
||||
@@ -14,8 +22,8 @@ export const useResetFocusStackToRecordIndex = () => {
|
||||
componentInstanceId: PageFocusId.RecordIndex,
|
||||
},
|
||||
globalHotkeysConfig: {
|
||||
enableGlobalHotkeysWithModifiers: true,
|
||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||
enableGlobalHotkeysWithModifiers: shouldEnableGlobalHotkeys,
|
||||
enableGlobalHotkeysConflictingWithKeyboard: shouldEnableGlobalHotkeys,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+3
-1
@@ -1,11 +1,12 @@
|
||||
import { RecordTableDeactivateRecordTableRowEffect } from '@/object-record/record-table/components/RecordTableDeactivateRecordTableRowEffect';
|
||||
import { RecordTableEmptyHasNewRecordEffect } from '@/object-record/record-table/components/RecordTableEmptyHasNewRecordEffect';
|
||||
import { RecordTableBodyEscapeHotkeyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect';
|
||||
import { RecordTableBodyFocusClickOutsideEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect';
|
||||
import { RecordTableBodyFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect';
|
||||
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
|
||||
import { RecordTableNoRecordGroupScrollToPreviousRecordEffect } from '@/object-record/record-table/virtualization/components/RecordTableNoRecordGroupScrollToPreviousRecordEffect';
|
||||
import { RecordTableVirtualizedInitialDataLoadEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedInitialDataLoadEffect';
|
||||
import { RecordTableVirtualizedFieldMetadataUpdateEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedFieldMetadataUpdateEffect';
|
||||
import { RecordTableVirtualizedInitialDataLoadEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedInitialDataLoadEffect';
|
||||
|
||||
export interface RecordTableBodyEffectsWrapperProps {
|
||||
hasRecordGroups: boolean;
|
||||
@@ -22,6 +23,7 @@ export const RecordTableBodyEffectsWrapper = ({
|
||||
<RecordTableRecordGroupBodyEffects />
|
||||
) : (
|
||||
<>
|
||||
<RecordTableEmptyHasNewRecordEffect />
|
||||
<RecordTableNoRecordGroupScrollToPreviousRecordEffect />
|
||||
<RecordTableVirtualizedInitialDataLoadEffect />
|
||||
<RecordTableVirtualizedFieldMetadataUpdateEffect />
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent';
|
||||
import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexHasRecordsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexHasRecordsComponentSelector';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { recordTableWentFromEmptyToNotEmptyComponentState } from '@/object-record/record-table/states/recordTableWentFromEmptyToNotEmptyComponentState';
|
||||
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
import { useStore } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
import { computeRecordGqlOperationFilter } from 'twenty-shared/utils';
|
||||
|
||||
export const RecordTableEmptyHasNewRecordEffect = () => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const isRecordTableInitialLoading = useAtomComponentStateValue(
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
);
|
||||
|
||||
const recordTableHasRecords = useAtomComponentSelectorValue(
|
||||
recordIndexHasRecordsComponentSelector,
|
||||
);
|
||||
|
||||
const recordTableWentFromEmptyToNotEmptyCallbackState =
|
||||
useAtomComponentStateCallbackState(
|
||||
recordTableWentFromEmptyToNotEmptyComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const currentRecordSorts = useAtomComponentStateValue(
|
||||
currentRecordSortsComponentState,
|
||||
);
|
||||
|
||||
const currentRecordFilterGroups = useAtomComponentStateValue(
|
||||
currentRecordFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const queryId = `record-table-empty-${objectMetadataItem.nameSingular}`;
|
||||
|
||||
const operationSignature = useMemo(
|
||||
() => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {
|
||||
filter: computeRecordGqlOperationFilter({
|
||||
fields: objectMetadataItem.fields,
|
||||
recordFilters: currentRecordFilters,
|
||||
recordFilterGroups: currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
}),
|
||||
orderBy: turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts),
|
||||
},
|
||||
}),
|
||||
[
|
||||
objectMetadataItem,
|
||||
currentRecordFilters,
|
||||
currentRecordFilterGroups,
|
||||
filterValueDependencies,
|
||||
currentRecordSorts,
|
||||
],
|
||||
);
|
||||
|
||||
useListenToEventsForQuery({
|
||||
queryId,
|
||||
operationSignature,
|
||||
});
|
||||
|
||||
const handleObjectRecordOperation = (
|
||||
objectRecordOperationEventDetail: ObjectRecordOperationBrowserEventDetail,
|
||||
) => {
|
||||
const objectRecordOperation = objectRecordOperationEventDetail.operation;
|
||||
|
||||
if (
|
||||
objectRecordOperation.type.includes('update') ||
|
||||
objectRecordOperation.type.includes('create')
|
||||
) {
|
||||
if (!isRecordTableInitialLoading && !recordTableHasRecords) {
|
||||
store.set(recordTableWentFromEmptyToNotEmptyCallbackState, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useListenToObjectRecordOperationBrowserEvent({
|
||||
onObjectRecordOperationBrowserEvent: handleObjectRecordOperation,
|
||||
objectMetadataItemId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const recordTableWentFromEmptyToNotEmptyComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'recordTableWentFromEmptyToNotEmptyComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+13
-1
@@ -2,15 +2,16 @@ import { useRecordIndexTableFetchMore } from '@/object-record/record-index/hooks
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
import { recordTableWentFromEmptyToNotEmptyComponentState } from '@/object-record/record-table/states/recordTableWentFromEmptyToNotEmptyComponentState';
|
||||
import { useTriggerInitialRecordTableDataLoad } from '@/object-record/record-table/virtualization/hooks/useTriggerInitialRecordTableDataLoad';
|
||||
import { isInitializingVirtualTableDataLoadingComponentState } from '@/object-record/record-table/virtualization/states/isInitializingVirtualTableDataLoadingComponentState';
|
||||
import { lastContextStoreVirtualizedViewIdComponentState } from '@/object-record/record-table/virtualization/states/lastContextStoreVirtualizedViewIdComponentState';
|
||||
import { lastContextStoreVirtualizedVisibleRecordFieldsComponentState } from '@/object-record/record-table/virtualization/states/lastContextStoreVirtualizedVisibleRecordFieldsComponentState';
|
||||
import { lastRecordTableQueryIdentifierComponentState } from '@/object-record/record-table/virtualization/states/lastRecordTableQueryIdentifierComponentState';
|
||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useEffect } from 'react';
|
||||
@@ -31,6 +32,11 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
isInitializingVirtualTableDataLoadingComponentState,
|
||||
);
|
||||
|
||||
const [
|
||||
recordTableWentFromEmptyToNotEmpty,
|
||||
setRecordTableWentFromEmptyToNotEmpty,
|
||||
] = useAtomComponentState(recordTableWentFromEmptyToNotEmptyComponentState);
|
||||
|
||||
const isFetchingMoreRecords = useAtomFamilyStateValue(
|
||||
isFetchingMoreRecordsFamilyState,
|
||||
recordTableId,
|
||||
@@ -78,6 +84,10 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
) {
|
||||
setLastRecordTableQueryIdentifier(queryIdentifier);
|
||||
|
||||
await triggerInitialRecordTableDataLoad();
|
||||
} else if (recordTableWentFromEmptyToNotEmpty) {
|
||||
setRecordTableWentFromEmptyToNotEmpty(false);
|
||||
|
||||
await triggerInitialRecordTableDataLoad();
|
||||
} else if (
|
||||
JSON.stringify(lastContextStoreVirtualizedVisibleRecordFields) !==
|
||||
@@ -98,6 +108,8 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
}
|
||||
})();
|
||||
}, [
|
||||
recordTableWentFromEmptyToNotEmpty,
|
||||
setRecordTableWentFromEmptyToNotEmpty,
|
||||
queryIdentifier,
|
||||
lastRecordTableQueryIdentifier,
|
||||
triggerInitialRecordTableDataLoad,
|
||||
|
||||
Reference in New Issue
Block a user