Refactored and improved seeds (#8695)
- Added a new Seeder service to help with custom object seeds - Added RichTextFieldInput to edit a rich text field directly on the table, but deactivated it for now.
This commit is contained in:
@ -17,7 +17,6 @@ import { isFieldRelationToOneObject } from '@/object-record/record-field/types/g
|
|||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
|
|
||||||
import { ArrayFieldInput } from '@/object-record/record-field/meta-types/input/components/ArrayFieldInput';
|
import { ArrayFieldInput } from '@/object-record/record-field/meta-types/input/components/ArrayFieldInput';
|
||||||
import { RichTextFieldInput } from '@/object-record/record-field/meta-types/input/components/RichTextFieldInput';
|
|
||||||
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||||
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
||||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||||
@ -31,7 +30,6 @@ import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/is
|
|||||||
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
||||||
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
||||||
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { BooleanFieldInput } from '../meta-types/input/components/BooleanFieldInput';
|
import { BooleanFieldInput } from '../meta-types/input/components/BooleanFieldInput';
|
||||||
@ -167,8 +165,6 @@ export const FieldInput = ({
|
|||||||
onTab={onTab}
|
onTab={onTab}
|
||||||
onShiftTab={onShiftTab}
|
onShiftTab={onShiftTab}
|
||||||
/>
|
/>
|
||||||
) : isFieldRichText(fieldDefinition) ? (
|
|
||||||
<RichTextFieldInput />
|
|
||||||
) : isFieldArray(fieldDefinition) ? (
|
) : isFieldArray(fieldDefinition) ? (
|
||||||
<ArrayFieldInput
|
<ArrayFieldInput
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
|||||||
@ -28,6 +28,8 @@ import { RecordForSelect } from '@/object-record/relation-picker/types/RecordFor
|
|||||||
|
|
||||||
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
||||||
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
|
import { isFieldArrayValue } from '@/object-record/record-field/types/guards/isFieldArrayValue';
|
||||||
|
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||||
|
import { isFieldRichTextValue } from '@/object-record/record-field/types/guards/isFieldRichTextValue';
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
|
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
|
||||||
import { isFieldBooleanValue } from '../types/guards/isFieldBooleanValue';
|
import { isFieldBooleanValue } from '../types/guards/isFieldBooleanValue';
|
||||||
@ -111,6 +113,10 @@ export const usePersistField = () => {
|
|||||||
isFieldRawJson(fieldDefinition) &&
|
isFieldRawJson(fieldDefinition) &&
|
||||||
isFieldRawJsonValue(valueToPersist);
|
isFieldRawJsonValue(valueToPersist);
|
||||||
|
|
||||||
|
const fieldIsRichText =
|
||||||
|
isFieldRichText(fieldDefinition) &&
|
||||||
|
isFieldRichTextValue(valueToPersist);
|
||||||
|
|
||||||
const fieldIsArray =
|
const fieldIsArray =
|
||||||
isFieldArray(fieldDefinition) && isFieldArrayValue(valueToPersist);
|
isFieldArray(fieldDefinition) && isFieldArrayValue(valueToPersist);
|
||||||
|
|
||||||
@ -131,7 +137,8 @@ export const usePersistField = () => {
|
|||||||
fieldIsMultiSelect ||
|
fieldIsMultiSelect ||
|
||||||
fieldIsAddress ||
|
fieldIsAddress ||
|
||||||
fieldIsRawJson ||
|
fieldIsRawJson ||
|
||||||
fieldIsArray;
|
fieldIsArray ||
|
||||||
|
fieldIsRichText;
|
||||||
|
|
||||||
if (isValuePersistable) {
|
if (isValuePersistable) {
|
||||||
const fieldName = fieldDefinition.metadata.fieldName;
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { useTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useTextFieldDisplay';
|
import { useRichTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRichTextFieldDisplay';
|
||||||
import { getFirstNonEmptyLineOfRichText } from '@/ui/input/editor/utils/getFirstNonEmptyLineOfRichText';
|
import { getFirstNonEmptyLineOfRichText } from '@/ui/input/editor/utils/getFirstNonEmptyLineOfRichText';
|
||||||
import { PartialBlock } from '@blocknote/core';
|
|
||||||
|
|
||||||
export const RichTextFieldDisplay = () => {
|
export const RichTextFieldDisplay = () => {
|
||||||
const { fieldValue } = useTextFieldDisplay();
|
const { fieldValue } = useRichTextFieldDisplay();
|
||||||
const parsedField =
|
|
||||||
fieldValue === '' ? null : (JSON.parse(fieldValue) as PartialBlock[]);
|
|
||||||
|
|
||||||
return <>{getFirstNonEmptyLineOfRichText(parsedField)}</>;
|
return (
|
||||||
|
<div>
|
||||||
|
<span>{getFirstNonEmptyLineOfRichText(fieldValue)}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
|
||||||
|
import { FieldRichTextValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
|
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||||
|
import { isFieldRichTextValue } from '@/object-record/record-field/types/guards/isFieldRichTextValue';
|
||||||
|
import { PartialBlock } from '@blocknote/core';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
|
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||||
|
|
||||||
|
export const useRichTextField = () => {
|
||||||
|
const { recordId, fieldDefinition, hotkeyScope, maxWidth } =
|
||||||
|
useContext(FieldContext);
|
||||||
|
|
||||||
|
assertFieldMetadata(
|
||||||
|
FieldMetadataType.RichText,
|
||||||
|
isFieldRichText,
|
||||||
|
fieldDefinition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
|
||||||
|
const [fieldValue, setFieldValue] = useRecoilState<FieldRichTextValue>(
|
||||||
|
recordStoreFamilySelector({
|
||||||
|
recordId,
|
||||||
|
fieldName: fieldName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const fieldRichTextValue = isFieldRichTextValue(fieldValue) ? fieldValue : '';
|
||||||
|
|
||||||
|
const { setDraftValue, getDraftValueSelector } =
|
||||||
|
useRecordFieldInput<FieldRichTextValue>(`${recordId}-${fieldName}`);
|
||||||
|
|
||||||
|
const draftValue = useRecoilValue(getDraftValueSelector());
|
||||||
|
|
||||||
|
const draftValueParsed: PartialBlock[] = isNonEmptyString(draftValue)
|
||||||
|
? JSON.parse(draftValue)
|
||||||
|
: draftValue;
|
||||||
|
|
||||||
|
const persistField = usePersistField();
|
||||||
|
|
||||||
|
const persistRichTextField = (nextValue: PartialBlock[]) => {
|
||||||
|
if (!nextValue) {
|
||||||
|
persistField(null);
|
||||||
|
} else {
|
||||||
|
const parsedValueToPersist = JSON.stringify(nextValue);
|
||||||
|
|
||||||
|
persistField(parsedValueToPersist);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
draftValue: draftValueParsed,
|
||||||
|
setDraftValue,
|
||||||
|
maxWidth,
|
||||||
|
fieldDefinition,
|
||||||
|
fieldValue: fieldRichTextValue,
|
||||||
|
setFieldValue,
|
||||||
|
hotkeyScope,
|
||||||
|
persistRichTextField,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
|
|
||||||
|
import { FieldRichTextValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
|
||||||
|
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||||
|
import { PartialBlock } from '@blocknote/core';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { parseJson } from '~/utils/parseJson';
|
||||||
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
|
|
||||||
|
export const useRichTextFieldDisplay = () => {
|
||||||
|
const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||||
|
|
||||||
|
assertFieldMetadata(
|
||||||
|
FieldMetadataType.RichText,
|
||||||
|
isFieldRichText,
|
||||||
|
fieldDefinition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
|
||||||
|
const fieldValue = useRecordFieldValue<FieldRichTextValue | undefined>(
|
||||||
|
recordId,
|
||||||
|
fieldName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValueParsed = parseJson<PartialBlock[]>(fieldValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fieldDefinition,
|
||||||
|
fieldValue: fieldValueParsed,
|
||||||
|
hotkeyScope,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,5 +1,59 @@
|
|||||||
import { RichTextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextFieldDisplay';
|
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useRichTextField } from '@/object-record/record-field/meta-types/hooks/useRichTextField';
|
||||||
|
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||||
|
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
||||||
|
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||||
|
import { BlockEditorComponentInstanceContext } from '@/ui/input/editor/contexts/BlockEditorCompoponeInstanceContext';
|
||||||
|
import { PartialBlock } from '@blocknote/core';
|
||||||
|
import { useCreateBlockNote } from '@blocknote/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const RichTextFieldInput = () => {
|
import { useContext, useRef } from 'react';
|
||||||
return <RichTextFieldDisplay />;
|
|
||||||
|
const StyledRichTextContainer = styled.div`
|
||||||
|
height: 400px;
|
||||||
|
width: 500px;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type RichTextFieldInputProps = {
|
||||||
|
onClickOutside?: FieldInputClickOutsideEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RichTextFieldInput = ({
|
||||||
|
onClickOutside,
|
||||||
|
}: RichTextFieldInputProps) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { recordId } = useContext(FieldContext);
|
||||||
|
const { draftValue, hotkeyScope, persistRichTextField, fieldDefinition } =
|
||||||
|
useRichTextField();
|
||||||
|
|
||||||
|
const editor = useCreateBlockNote({
|
||||||
|
initialContent: draftValue,
|
||||||
|
domAttributes: { editor: { class: 'editor' } },
|
||||||
|
schema: BLOCK_SCHEMA,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||||
|
onClickOutside?.(() => persistRichTextField(editor.document), event);
|
||||||
|
};
|
||||||
|
|
||||||
|
useRegisterInputEvents<PartialBlock[]>({
|
||||||
|
inputRef: containerRef,
|
||||||
|
inputValue: draftValue,
|
||||||
|
onClickOutside: handleClickOutside,
|
||||||
|
hotkeyScope,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledRichTextContainer ref={containerRef}>
|
||||||
|
<BlockEditorComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: `${recordId}-${fieldDefinition.fieldMetadataId}` }}
|
||||||
|
>
|
||||||
|
<BlockEditor editor={editor} />
|
||||||
|
</BlockEditorComponentInstanceContext.Provider>
|
||||||
|
</StyledRichTextContainer>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -46,6 +46,8 @@ export type FieldDateMetadata = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FieldNumberVariant = 'number' | 'percentage';
|
||||||
|
|
||||||
export type FieldNumberMetadata = {
|
export type FieldNumberMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -53,7 +55,7 @@ export type FieldNumberMetadata = {
|
|||||||
isPositive?: boolean;
|
isPositive?: boolean;
|
||||||
settings?: {
|
settings?: {
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
type?: 'percentage' | 'number';
|
type?: FieldNumberVariant;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -209,6 +211,7 @@ export type FieldMetadata =
|
|||||||
| FieldActorMetadata
|
| FieldActorMetadata
|
||||||
| FieldArrayMetadata
|
| FieldArrayMetadata
|
||||||
| FieldTsVectorMetadata;
|
| FieldTsVectorMetadata;
|
||||||
|
|
||||||
export type FieldTextValue = string;
|
export type FieldTextValue = string;
|
||||||
export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ?
|
export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ?
|
||||||
export type FieldDateTimeValue = string | null;
|
export type FieldDateTimeValue = string | null;
|
||||||
@ -255,7 +258,7 @@ export type FieldRelationValue<
|
|||||||
export type Json = ZodHelperLiteral | { [key: string]: Json } | Json[];
|
export type Json = ZodHelperLiteral | { [key: string]: Json } | Json[];
|
||||||
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
||||||
|
|
||||||
export type FieldRichTextValue = Record<string, Json> | Json[] | null;
|
export type FieldRichTextValue = null | string;
|
||||||
|
|
||||||
export type FieldActorValue = {
|
export type FieldActorValue = {
|
||||||
source: string;
|
source: string;
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
FieldRatingMetadata,
|
FieldRatingMetadata,
|
||||||
FieldRawJsonMetadata,
|
FieldRawJsonMetadata,
|
||||||
FieldRelationMetadata,
|
FieldRelationMetadata,
|
||||||
|
FieldRichTextMetadata,
|
||||||
FieldSelectMetadata,
|
FieldSelectMetadata,
|
||||||
FieldTextMetadata,
|
FieldTextMetadata,
|
||||||
FieldUuidMetadata,
|
FieldUuidMetadata,
|
||||||
@ -68,7 +69,7 @@ type AssertFieldMetadataFunction = <
|
|||||||
: E extends 'RAW_JSON'
|
: E extends 'RAW_JSON'
|
||||||
? FieldRawJsonMetadata
|
? FieldRawJsonMetadata
|
||||||
: E extends 'RICH_TEXT'
|
: E extends 'RICH_TEXT'
|
||||||
? FieldTextMetadata
|
? FieldRichTextMetadata
|
||||||
: E extends 'ACTOR'
|
: E extends 'ACTOR'
|
||||||
? FieldActorMetadata
|
? FieldActorMetadata
|
||||||
: E extends 'ARRAY'
|
: E extends 'ARRAY'
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { FieldRichTextValue } from '../FieldMetadata';
|
||||||
|
|
||||||
|
export const richTextSchema: z.ZodType<FieldRichTextValue> = z.union([
|
||||||
|
z.null(), // Exclude literal values other than null
|
||||||
|
z.string(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const isFieldRichTextValue = (
|
||||||
|
fieldValue: unknown,
|
||||||
|
): fieldValue is FieldRichTextValue =>
|
||||||
|
richTextSchema.safeParse(fieldValue).success;
|
||||||
@ -5,6 +5,7 @@ import { RecordTableCellWrapper } from '@/object-record/record-table/record-tabl
|
|||||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||||
|
|
||||||
export const RecordTableCellsVisible = () => {
|
export const RecordTableCellsVisible = () => {
|
||||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
const { isSelected } = useRecordTableRowContextOrThrow();
|
||||||
@ -15,6 +16,10 @@ export const RecordTableCellsVisible = () => {
|
|||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!isNonEmptyArray(visibleTableColumns)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const tableColumnsAfterFirst = visibleTableColumns.slice(1);
|
const tableColumnsAfterFirst = visibleTableColumns.slice(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export const SettingsOptionIconCustomizer = ({
|
|||||||
<StyledIconCustomizer zoom={zoom} rotate={rotate}>
|
<StyledIconCustomizer zoom={zoom} rotate={rotate}>
|
||||||
<Icon
|
<Icon
|
||||||
size={theme.icon.size.lg}
|
size={theme.icon.size.lg}
|
||||||
color={theme.IllustrationIcon.color.grey}
|
color={theme.IllustrationIcon.color.gray}
|
||||||
stroke={theme.icon.stroke.md}
|
stroke={theme.icon.stroke.md}
|
||||||
/>
|
/>
|
||||||
</StyledIconCustomizer>
|
</StyledIconCustomizer>
|
||||||
|
|||||||
@ -125,7 +125,7 @@ export const SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS: SettingsNonCompositeFiel
|
|||||||
[FieldMetadataType.RichText]: {
|
[FieldMetadataType.RichText]: {
|
||||||
label: 'Rich Text',
|
label: 'Rich Text',
|
||||||
Icon: IllustrationIconSetting,
|
Icon: IllustrationIconSetting,
|
||||||
exampleValue: { key: 'value' },
|
exampleValue: "{ key: 'value' }",
|
||||||
category: 'Basic',
|
category: 'Basic',
|
||||||
} as const satisfies SettingsFieldTypeConfig<FieldRichTextValue>,
|
} as const satisfies SettingsFieldTypeConfig<FieldRichTextValue>,
|
||||||
[FieldMetadataType.Array]: {
|
[FieldMetadataType.Array]: {
|
||||||
|
|||||||
@ -19,14 +19,17 @@ interface BlockEditorProps {
|
|||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
onPaste?: (event: ClipboardEvent) => void;
|
onPaste?: (event: ClipboardEvent) => void;
|
||||||
onChange?: () => void;
|
onChange?: () => void;
|
||||||
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledEditor = styled.div`
|
const StyledEditor = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
& .editor {
|
& .editor {
|
||||||
background: ${({ theme }) => theme.background.primary};
|
background: ${({ theme }) => theme.background.primary};
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
& .editor [class^='_inlineContent']:before {
|
& .editor [class^='_inlineContent']:before {
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
@ -124,6 +127,7 @@ export const BlockEditor = ({
|
|||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
onPaste,
|
onPaste,
|
||||||
|
readonly,
|
||||||
}: BlockEditorProps) => {
|
}: BlockEditorProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const blockNoteTheme = theme.name === 'light' ? 'light' : 'dark';
|
const blockNoteTheme = theme.name === 'light' ? 'light' : 'dark';
|
||||||
@ -155,6 +159,7 @@ export const BlockEditor = ({
|
|||||||
theme={blockNoteTheme}
|
theme={blockNoteTheme}
|
||||||
slashMenu={false}
|
slashMenu={false}
|
||||||
sideMenu={false}
|
sideMenu={false}
|
||||||
|
editable={!readonly}
|
||||||
>
|
>
|
||||||
<CustomSideMenu editor={editor} />
|
<CustomSideMenu editor={editor} />
|
||||||
<SuggestionMenuController
|
<SuggestionMenuController
|
||||||
|
|||||||
@ -0,0 +1,66 @@
|
|||||||
|
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
|
||||||
|
import { isSlashMenuOpenComponentState } from '@/ui/input/editor/states/isSlashMenuOpenComponentState';
|
||||||
|
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||||
|
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
export type BlockEditorDropdownFocusEffectProps = {
|
||||||
|
editor: typeof BLOCK_SCHEMA.BlockNoteEditor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlockEditorDropdownFocusEffect = ({
|
||||||
|
editor,
|
||||||
|
}: BlockEditorDropdownFocusEffectProps) => {
|
||||||
|
const isSlashMenuOpenState = useRecoilComponentCallbackStateV2(
|
||||||
|
isSlashMenuOpenComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||||
|
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||||
|
|
||||||
|
const { goBackToPreviousDropdownFocusId } =
|
||||||
|
useGoBackToPreviousDropdownFocusId();
|
||||||
|
|
||||||
|
const updateCallBack = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
(event: any) => {
|
||||||
|
// TODO: This triggers before the onClick event of the slash menu item, so the click outside of the editor dropdown is triggered and everything closes.
|
||||||
|
// This is due to useRecoilCallback being executed before the onClick event of the slash menu item.
|
||||||
|
const eventWantsToOpen = event.show === true;
|
||||||
|
|
||||||
|
const isAlreadyOpen = snapshot
|
||||||
|
.getLoadable(isSlashMenuOpenState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const shouldOpen = eventWantsToOpen && !isAlreadyOpen;
|
||||||
|
|
||||||
|
if (shouldOpen) {
|
||||||
|
setActiveDropdownFocusIdAndMemorizePrevious('custom-slash-menu');
|
||||||
|
set(isSlashMenuOpenState, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventWantsToClose = event.show === false;
|
||||||
|
|
||||||
|
const isAlreadyClosed = !isAlreadyOpen;
|
||||||
|
|
||||||
|
const shouldClose = eventWantsToClose && !isAlreadyClosed;
|
||||||
|
|
||||||
|
if (shouldClose) {
|
||||||
|
goBackToPreviousDropdownFocusId();
|
||||||
|
set(isSlashMenuOpenState, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
isSlashMenuOpenState,
|
||||||
|
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||||
|
goBackToPreviousDropdownFocusId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.suggestionMenus.on('update /', updateCallBack);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const BlockEditorComponentInstanceContext =
|
||||||
|
createComponentInstanceContext();
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { BlockEditorComponentInstanceContext } from '@/ui/input/editor/contexts/BlockEditorCompoponeInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const isSlashMenuOpenComponentState = createComponentStateV2<boolean>({
|
||||||
|
key: 'isSlashMenuOpenComponentState',
|
||||||
|
defaultValue: false,
|
||||||
|
componentInstanceContext: BlockEditorComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -11,6 +11,14 @@ export const useSetActiveDropdownFocusIdAndMemorizePrevious = () => {
|
|||||||
.getLoadable(activeDropdownFocusIdState)
|
.getLoadable(activeDropdownFocusIdState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
|
const activeDropdownFocusId = snapshot
|
||||||
|
.getLoadable(activeDropdownFocusIdState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (activeDropdownFocusId === dropdownId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
set(previousDropdownFocusIdState, focusedDropdownId);
|
set(previousDropdownFocusIdState, focusedDropdownId);
|
||||||
set(activeDropdownFocusIdState, dropdownId);
|
set(activeDropdownFocusIdState, dropdownId);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -50,7 +50,10 @@ export const mapViewFieldsToColumnDefinitions = ({
|
|||||||
isSortable: correspondingColumnDefinition.isSortable,
|
isSortable: correspondingColumnDefinition.isSortable,
|
||||||
isFilterable: correspondingColumnDefinition.isFilterable,
|
isFilterable: correspondingColumnDefinition.isFilterable,
|
||||||
defaultValue: correspondingColumnDefinition.defaultValue,
|
defaultValue: correspondingColumnDefinition.defaultValue,
|
||||||
settings: correspondingColumnDefinition.metadata.settings,
|
settings:
|
||||||
|
'settings' in correspondingColumnDefinition.metadata
|
||||||
|
? correspondingColumnDefinition.metadata.settings
|
||||||
|
: undefined,
|
||||||
} as ColumnDefinition<FieldMetadata>;
|
} as ColumnDefinition<FieldMetadata>;
|
||||||
})
|
})
|
||||||
.filter(isDefined);
|
.filter(isDefined);
|
||||||
|
|||||||
@ -4066,7 +4066,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "f59e3f72-2ac5-44cc-9a12-8530f9550cc3",
|
"id": "f59e3f72-2ac5-44cc-9a12-8530f9550cc3",
|
||||||
"color": "grey",
|
"color": "gray",
|
||||||
"label": "Archived",
|
"label": "Archived",
|
||||||
"value": "ARCHIVED",
|
"value": "ARCHIVED",
|
||||||
"position": 3
|
"position": 3
|
||||||
@ -10971,7 +10971,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"id": "c7ff1ce0-2e47-44de-9761-867c25f68a90",
|
"id": "c7ff1ce0-2e47-44de-9761-867c25f68a90",
|
||||||
"color": "grey",
|
"color": "gray",
|
||||||
"label": "Not started",
|
"label": "Not started",
|
||||||
"value": "NOT_STARTED",
|
"value": "NOT_STARTED",
|
||||||
"position": 0
|
"position": 0
|
||||||
@ -14966,7 +14966,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"position": 1
|
"position": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "grey",
|
"color": "gray",
|
||||||
"label": "Deactivated",
|
"label": "Deactivated",
|
||||||
"value": "DEACTIVATED",
|
"value": "DEACTIVATED",
|
||||||
"position": 2
|
"position": 2
|
||||||
|
|||||||
13
packages/twenty-front/src/utils/parseJson.ts
Normal file
13
packages/twenty-front/src/utils/parseJson.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const parseJson = <T>(json: string | undefined | null) => {
|
||||||
|
if (!isDefined(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(json) as T;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,7 +1,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: ['@stylistic'],
|
plugins: ['@stylistic'],
|
||||||
extends: ['../../.eslintrc.cjs'],
|
extends: ['../../.eslintrc.cjs'],
|
||||||
ignorePatterns: ['src/engine/workspace-manager/demo-objects-prefill-data/**'],
|
ignorePatterns: [
|
||||||
|
'src/engine/workspace-manager/demo-objects-prefill-data/**',
|
||||||
|
'src/engine/seeder/data-seeds/**',
|
||||||
|
'src/engine/seeder/metadata-seeds/**',
|
||||||
|
],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.ts'],
|
files: ['*.ts'],
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { AppModule } from 'src/app.module';
|
import { AppModule } from 'src/app.module';
|
||||||
import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
|
import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
|
||||||
|
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||||
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
|
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
|
||||||
import { WorkspaceHealthCommandModule } from 'src/engine/workspace-manager/workspace-health/commands/workspace-health-command.module';
|
import { WorkspaceHealthCommandModule } from 'src/engine/workspace-manager/workspace-health/commands/workspace-health-command.module';
|
||||||
import { WorkspaceMigrationRunnerCommandsModule } from 'src/engine/workspace-manager/workspace-migration-runner/commands/workspace-migration-runner-commands.module';
|
import { WorkspaceMigrationRunnerCommandsModule } from 'src/engine/workspace-manager/workspace-migration-runner/commands/workspace-migration-runner-commands.module';
|
||||||
@ -15,6 +17,8 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage
|
|||||||
WorkspaceCleanerModule,
|
WorkspaceCleanerModule,
|
||||||
WorkspaceHealthCommandModule,
|
WorkspaceHealthCommandModule,
|
||||||
WorkspaceMigrationRunnerCommandsModule,
|
WorkspaceMigrationRunnerCommandsModule,
|
||||||
|
ObjectMetadataModule,
|
||||||
|
FieldMetadataModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CommandModule {}
|
export class CommandModule {}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||||
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
import { viewPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/view';
|
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewDefinitionsWithId = await viewPrefillData(
|
const viewDefinitionsWithId = await seedViewWithDemoData(
|
||||||
entityManager,
|
entityManager,
|
||||||
dataSourceMetadata.schema,
|
dataSourceMetadata.schema,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
|
|||||||
@ -1,7 +1,19 @@
|
|||||||
import { IsString, IsNumber, IsOptional, IsNotEmpty } from 'class-validator';
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
import { IsValidGraphQLEnumName } from 'src/engine/metadata-modules/field-metadata/validators/is-valid-graphql-enum-name.validator';
|
import { IsValidGraphQLEnumName } from 'src/engine/metadata-modules/field-metadata/validators/is-valid-graphql-enum-name.validator';
|
||||||
|
|
||||||
|
export type TagColor =
|
||||||
|
| 'green'
|
||||||
|
| 'turquoise'
|
||||||
|
| 'sky'
|
||||||
|
| 'blue'
|
||||||
|
| 'purple'
|
||||||
|
| 'pink'
|
||||||
|
| 'red'
|
||||||
|
| 'orange'
|
||||||
|
| 'yellow'
|
||||||
|
| 'gray';
|
||||||
|
|
||||||
export class FieldMetadataDefaultOption {
|
export class FieldMetadataDefaultOption {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -22,5 +34,5 @@ export class FieldMetadataDefaultOption {
|
|||||||
export class FieldMetadataComplexOption extends FieldMetadataDefaultOption {
|
export class FieldMetadataComplexOption extends FieldMetadataDefaultOption {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
color: string;
|
color: TagColor;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -752,7 +752,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
);
|
);
|
||||||
} else if (error instanceof NameNotAvailableException) {
|
} else if (error instanceof NameNotAvailableException) {
|
||||||
throw new FieldMetadataException(
|
throw new FieldMetadataException(
|
||||||
`Name "${fieldMetadataInput.name}" is not available`,
|
`Name "${fieldMetadataInput.name}" is not available, check that it is not duplicating another field's name.`,
|
||||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -6,25 +6,27 @@ export enum NumberDataType {
|
|||||||
BIGINT = 'bigint',
|
BIGINT = 'bigint',
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldMetadataDefaultSettings = {
|
export type FieldMetadataDefaultSettings = {
|
||||||
isForeignKey?: boolean;
|
isForeignKey?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldMetadataNumberSettings = {
|
export type FieldNumberVariant = 'number' | 'percentage';
|
||||||
|
|
||||||
|
export type FieldMetadataNumberSettings = {
|
||||||
dataType: NumberDataType;
|
dataType: NumberDataType;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
type?: string;
|
type?: FieldNumberVariant;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldMetadataTextSettings = {
|
export type FieldMetadataTextSettings = {
|
||||||
displayedMaxRows?: number;
|
displayedMaxRows?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldMetadataDateSettings = {
|
export type FieldMetadataDateSettings = {
|
||||||
displayAsRelativeDate?: boolean;
|
displayAsRelativeDate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldMetadataDateTimeSettings = {
|
export type FieldMetadataDateTimeSettings = {
|
||||||
displayAsRelativeDate?: boolean;
|
displayAsRelativeDate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ValidationArguments,
|
ValidationArguments,
|
||||||
ValidatorConstraint,
|
ValidatorConstraint,
|
||||||
ValidatorConstraintInterface,
|
ValidatorConstraintInterface,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
|
|
||||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
|
||||||
import {
|
import {
|
||||||
FieldMetadataEntity,
|
FieldMetadataEntity,
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util';
|
import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util';
|
||||||
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ValidatorConstraint({ name: 'isFieldMetadataDefaultValue', async: true })
|
@ValidatorConstraint({ name: 'isFieldMetadataDefaultValue', async: true })
|
||||||
@ -22,7 +23,8 @@ export class IsFieldMetadataDefaultValue
|
|||||||
implements ValidatorConstraintInterface
|
implements ValidatorConstraintInterface
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private readonly fieldMetadataService: FieldMetadataService,
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
private readonly loggerService: LoggerService,
|
private readonly loggerService: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -44,7 +46,11 @@ export class IsFieldMetadataDefaultValue
|
|||||||
let fieldMetadata: FieldMetadataEntity;
|
let fieldMetadata: FieldMetadataEntity;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fieldMetadata = await this.fieldMetadataService.findOneOrFail(id);
|
fieldMetadata = await this.fieldMetadataRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { ValidationArguments, ValidatorConstraint } from 'class-validator';
|
import { ValidationArguments, ValidatorConstraint } from 'class-validator';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
|
|
||||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
|
||||||
import {
|
import {
|
||||||
FieldMetadataEntity,
|
FieldMetadataEntity,
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
@ -16,7 +17,10 @@ import { validateOptionsForType } from 'src/engine/metadata-modules/field-metada
|
|||||||
export class IsFieldMetadataOptions {
|
export class IsFieldMetadataOptions {
|
||||||
private validationErrors: string[] = [];
|
private validationErrors: string[] = [];
|
||||||
|
|
||||||
constructor(private readonly fieldMetadataService: FieldMetadataService) {}
|
constructor(
|
||||||
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
async validate(
|
async validate(
|
||||||
value: FieldMetadataOptions,
|
value: FieldMetadataOptions,
|
||||||
@ -36,7 +40,9 @@ export class IsFieldMetadataOptions {
|
|||||||
let fieldMetadata: FieldMetadataEntity;
|
let fieldMetadata: FieldMetadataEntity;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fieldMetadata = await this.fieldMetadataService.findOneOrFail(id);
|
fieldMetadata = await this.fieldMetadataRepository.findOneOrFail({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,22 @@ import {
|
|||||||
Injectable,
|
Injectable,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BeforeDeleteOneHook,
|
BeforeDeleteOneHook,
|
||||||
DeleteOneInputType,
|
DeleteOneInputType,
|
||||||
} from '@ptc-org/nestjs-query-graphql';
|
} from '@ptc-org/nestjs-query-graphql';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BeforeDeleteOneRelation implements BeforeDeleteOneHook {
|
export class BeforeDeleteOneRelation implements BeforeDeleteOneHook {
|
||||||
constructor(readonly relationMetadataService: RelationMetadataService) {}
|
constructor(
|
||||||
|
@InjectRepository(RelationMetadataEntity, 'metadata')
|
||||||
|
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
async run(
|
async run(
|
||||||
instance: DeleteOneInputType,
|
instance: DeleteOneInputType,
|
||||||
@ -25,12 +30,13 @@ export class BeforeDeleteOneRelation implements BeforeDeleteOneHook {
|
|||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationMetadata =
|
const relationMetadata = await this.relationMetadataRepository.findOne({
|
||||||
await this.relationMetadataService.findOneWithinWorkspace(workspaceId, {
|
where: {
|
||||||
where: {
|
workspaceId,
|
||||||
id: instance.id.toString(),
|
id: instance.id.toString(),
|
||||||
},
|
},
|
||||||
});
|
relations: ['fromFieldMetafata', 'toFieldMetadata'],
|
||||||
|
});
|
||||||
|
|
||||||
if (!relationMetadata) {
|
if (!relationMetadata) {
|
||||||
throw new BadRequestException('Relation does not exist');
|
throw new BadRequestException('Relation does not exist');
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
|||||||
|
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||||
|
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||||
|
|
||||||
|
export type ObjectMetadataSeed = Omit<
|
||||||
|
CreateObjectInput,
|
||||||
|
'workspaceId' | 'dataSourceId'
|
||||||
|
> & { fields: Omit<CreateFieldInput, 'objectMetadataId' | 'workspaceId'>[] };
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
|
export const PETS_METADATA_SEEDS: ObjectMetadataSeed = {
|
||||||
|
labelPlural: 'Pets',
|
||||||
|
labelSingular: 'Pet',
|
||||||
|
namePlural: 'pets',
|
||||||
|
nameSingular: 'pet',
|
||||||
|
icon: 'IconCat',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.SELECT,
|
||||||
|
label: 'Species',
|
||||||
|
name: 'species',
|
||||||
|
options: [
|
||||||
|
{ label: 'Dog', value: 'dog', position: 0, color: 'blue' },
|
||||||
|
{ label: 'Cat', value: 'cat', position: 1, color: 'red' },
|
||||||
|
{ label: 'Bird', value: 'bird', position: 2, color: 'green' },
|
||||||
|
{ label: 'Fish', value: 'fish', position: 3, color: 'yellow' },
|
||||||
|
{ label: 'Rabbit', value: 'rabbit', position: 4, color: 'purple' },
|
||||||
|
{ label: 'Hamster', value: 'hamster', position: 5, color: 'orange' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'Comments',
|
||||||
|
name: 'comments',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
label: 'Age',
|
||||||
|
name: 'age',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.ADDRESS,
|
||||||
|
label: 'Location',
|
||||||
|
name: 'location',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
FieldMetadataNumberSettings,
|
||||||
|
FieldMetadataTextSettings,
|
||||||
|
NumberDataType,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
|
export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
|
||||||
|
labelPlural: 'Survey results',
|
||||||
|
labelSingular: 'Survey result',
|
||||||
|
namePlural: 'surveyResults',
|
||||||
|
nameSingular: 'surveyResult',
|
||||||
|
icon: 'IconRulerMeasure',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
label: 'Score (Float 3 decimals)',
|
||||||
|
name: 'score',
|
||||||
|
settings: {
|
||||||
|
dataType: NumberDataType.FLOAT,
|
||||||
|
decimals: 3,
|
||||||
|
type: 'number',
|
||||||
|
} as FieldMetadataNumberSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
label: 'Percentage of completion (Float 3 decimals + percentage)',
|
||||||
|
name: 'percentageOfCompletion',
|
||||||
|
settings: {
|
||||||
|
dataType: NumberDataType.FLOAT,
|
||||||
|
decimals: 6,
|
||||||
|
type: 'percentage',
|
||||||
|
} as FieldMetadataNumberSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
label: 'Participants (Int)',
|
||||||
|
name: 'participants',
|
||||||
|
settings: {
|
||||||
|
dataType: NumberDataType.INT,
|
||||||
|
type: 'number',
|
||||||
|
} as FieldMetadataNumberSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
label: 'Average estimated number of atoms in the universe (BigInt)',
|
||||||
|
name: 'averageEstimatedNumberOfAtomsInTheUniverse',
|
||||||
|
settings: {
|
||||||
|
dataType: NumberDataType.BIGINT,
|
||||||
|
type: 'number',
|
||||||
|
} as FieldMetadataNumberSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'Comments (Max 5 rows)',
|
||||||
|
name: 'comments',
|
||||||
|
settings: {
|
||||||
|
displayedMaxRows: 5,
|
||||||
|
} as FieldMetadataTextSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'Short notes (Max 1 row)',
|
||||||
|
name: 'shortNotes',
|
||||||
|
settings: {
|
||||||
|
displayedMaxRows: 1,
|
||||||
|
} as FieldMetadataTextSettings,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
17
packages/twenty-server/src/engine/seeder/seeder.module.ts
Normal file
17
packages/twenty-server/src/engine/seeder/seeder.module.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||||
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
|
import { SeederService } from 'src/engine/seeder/seeder.service';
|
||||||
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ObjectMetadataModule,
|
||||||
|
FieldMetadataModule,
|
||||||
|
WorkspaceDataSourceModule,
|
||||||
|
],
|
||||||
|
exports: [SeederService],
|
||||||
|
providers: [SeederService],
|
||||||
|
})
|
||||||
|
export class SeederModule {}
|
||||||
161
packages/twenty-server/src/engine/seeder/seeder.service.ts
Normal file
161
packages/twenty-server/src/engine/seeder/seeder.service.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
|
||||||
|
|
||||||
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||||
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
|
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||||
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
|
import { isDefined } from 'src/utils/is-defined';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SeederService {
|
||||||
|
constructor(
|
||||||
|
private readonly objectMetadataService: ObjectMetadataService,
|
||||||
|
private readonly fieldMetadataService: FieldMetadataService,
|
||||||
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async seedCustomObjects(
|
||||||
|
dataSourceId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
metadataSeeds: ObjectMetadataSeed,
|
||||||
|
dataSeeds: Record<string, any>[],
|
||||||
|
): Promise<void> {
|
||||||
|
const createdObjectMetadata = await this.objectMetadataService.createOne({
|
||||||
|
...metadataSeeds,
|
||||||
|
dataSourceId,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createdObjectMetadata) {
|
||||||
|
throw new Error("Object metadata couldn't be created");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const customField of metadataSeeds.fields) {
|
||||||
|
await this.fieldMetadataService.createOne({
|
||||||
|
...customField,
|
||||||
|
objectMetadataId: createdObjectMetadata.id,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectMetadataAfterFieldCreation =
|
||||||
|
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
||||||
|
where: { nameSingular: metadataSeeds.nameSingular },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!objectMetadataAfterFieldCreation) {
|
||||||
|
throw new Error(
|
||||||
|
"Object metadata couldn't be found after field creation.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaName =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
|
const workspaceDataSource =
|
||||||
|
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const entityManager = workspaceDataSource.createEntityManager();
|
||||||
|
|
||||||
|
const filteredFields = metadataSeeds.fields.filter((field) =>
|
||||||
|
objectMetadataAfterFieldCreation.fields.some(
|
||||||
|
(f) => f.name === field.name || f.name === `name`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredFields.length === 0) {
|
||||||
|
throw new Error('No fields found for seeding, check metadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredFields.unshift({
|
||||||
|
name: 'name',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'Name',
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldMetadataMap = filteredFields
|
||||||
|
.map((field) => {
|
||||||
|
if (isCompositeFieldMetadataType(field.type)) {
|
||||||
|
const compositeFieldTypeDefinition = compositeTypeDefinitions.get(
|
||||||
|
field.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(compositeFieldTypeDefinition)) {
|
||||||
|
throw new Error(
|
||||||
|
`Composite field type definition not found for ${field.type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldNames = compositeFieldTypeDefinition.properties?.map(
|
||||||
|
(property) => property.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
fieldNames?.map(
|
||||||
|
(subFieldName: string) =>
|
||||||
|
`${field.name}${capitalize(subFieldName)}`,
|
||||||
|
) ?? []
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return field.name;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
const flattenedSeeds = dataSeeds.map((seed) => {
|
||||||
|
const flattenedSeed = {};
|
||||||
|
|
||||||
|
for (const field of filteredFields) {
|
||||||
|
if (isCompositeFieldMetadataType(field.type)) {
|
||||||
|
const compositeFieldTypeDefinition = compositeTypeDefinitions.get(
|
||||||
|
field.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(compositeFieldTypeDefinition)) {
|
||||||
|
throw new Error(
|
||||||
|
`Composite field type definition not found for ${field.type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldNames = compositeFieldTypeDefinition.properties
|
||||||
|
?.map((property) => property.name)
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
for (const subFieldName of fieldNames) {
|
||||||
|
flattenedSeed[`${field.name}${capitalize(subFieldName)}`] =
|
||||||
|
seed?.[field.name]?.[subFieldName];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flattenedSeed[field.name] = seed[field.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flattenedSeed;
|
||||||
|
});
|
||||||
|
|
||||||
|
await entityManager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(`${schemaName}._${objectMetadataAfterFieldCreation.nameSingular}`, [
|
||||||
|
...fieldMetadataMap,
|
||||||
|
'position',
|
||||||
|
])
|
||||||
|
.orIgnore()
|
||||||
|
.values(
|
||||||
|
flattenedSeeds.map((flattenedSeed, index) => ({
|
||||||
|
...flattenedSeed,
|
||||||
|
position: index,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.returning('*')
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from "src/engine/workspace-manager/demo-objects-prefill-data/workspace-member";
|
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from "src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-member-with-demo-data";
|
||||||
|
|
||||||
export const companiesDemo = [
|
export const COMPANIES_DEMO = [
|
||||||
{
|
{
|
||||||
name: 'Google',
|
name: 'Google',
|
||||||
domainName: 'goo.gle',
|
domainName: 'goo.gle',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from "src/engine/workspace-manager/demo-objects-prefill-data/workspace-member";
|
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from "src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-member-with-demo-data";
|
||||||
|
|
||||||
export const peopleDemo = [
|
export const peopleDemo = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { EntityManager } from 'typeorm';
|
import { EntityManager } from 'typeorm';
|
||||||
|
|
||||||
import { companiesDemo } from 'src/engine/workspace-manager/demo-objects-prefill-data/companies-demo.json';
|
import { COMPANIES_DEMO } from 'src/engine/workspace-manager/demo-objects-prefill-data/companies-demo.json';
|
||||||
|
|
||||||
export const companyPrefillDemoData = async (
|
export const seedCompanyWithDemoData = async (
|
||||||
entityManager: EntityManager,
|
entityManager: EntityManager,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
) => {
|
) => {
|
||||||
@ -22,7 +22,7 @@ export const companyPrefillDemoData = async (
|
|||||||
])
|
])
|
||||||
.orIgnore()
|
.orIgnore()
|
||||||
.values(
|
.values(
|
||||||
companiesDemo.map((company, index) => ({ ...company, position: index })),
|
COMPANIES_DEMO.map((company, index) => ({ ...company, position: index })),
|
||||||
)
|
)
|
||||||
.returning('*')
|
.returning('*')
|
||||||
.execute();
|
.execute();
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from 'src/engine/workspace-manager/demo-objects-prefill-data/workspace-member';
|
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-member-with-demo-data';
|
||||||
import { EntityManager } from 'typeorm';
|
import { EntityManager } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ const generateOpportunities = (companies) => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const opportunityPrefillDemoData = async (
|
export const seedOpportunityWithDemoData = async (
|
||||||
entityManager: EntityManager,
|
entityManager: EntityManager,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
) => {
|
) => {
|
||||||
@ -2,7 +2,7 @@ import { EntityManager } from 'typeorm';
|
|||||||
|
|
||||||
import { peopleDemo } from 'src/engine/workspace-manager/demo-objects-prefill-data/people-demo.json';
|
import { peopleDemo } from 'src/engine/workspace-manager/demo-objects-prefill-data/people-demo.json';
|
||||||
|
|
||||||
export const personPrefillDemoData = async (
|
export const seedPersonWithDemoData = async (
|
||||||
entityManager: EntityManager,
|
entityManager: EntityManager,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
) => {
|
) => {
|
||||||
@ -8,7 +8,7 @@ export const DEMO_SEED_WORKSPACE_MEMBER_IDS = {
|
|||||||
TIM: '20202020-1553-45c6-a028-5a9064cce07e',
|
TIM: '20202020-1553-45c6-a028-5a9064cce07e',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const workspaceMemberPrefillData = async (
|
export const seedWorkspaceMemberWithDemoData = async (
|
||||||
entityManager: EntityManager,
|
entityManager: EntityManager,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
) => {
|
) => {
|
||||||
@ -3,13 +3,13 @@ import { DataSource, EntityManager } from 'typeorm';
|
|||||||
import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites';
|
import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
||||||
import { companyPrefillDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/company';
|
import { seedCompanyWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-company-with-demo-data';
|
||||||
import { opportunityPrefillDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/opportunity';
|
import { seedOpportunityWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-opportunity-with-demo-data';
|
||||||
import { personPrefillDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/person';
|
import { seedPersonWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-person-with-demo-data';
|
||||||
import { workspaceMemberPrefillData } from 'src/engine/workspace-manager/demo-objects-prefill-data/workspace-member';
|
import { seedWorkspaceMemberWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-member-with-demo-data';
|
||||||
import { viewPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/view';
|
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
|
||||||
|
|
||||||
export const demoObjectsPrefillData = async (
|
export const seedWorkspaceWithDemoData = async (
|
||||||
workspaceDataSource: DataSource,
|
workspaceDataSource: DataSource,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
objectMetadata: ObjectMetadataEntity[],
|
objectMetadata: ObjectMetadataEntity[],
|
||||||
@ -30,11 +30,11 @@ export const demoObjectsPrefillData = async (
|
|||||||
|
|
||||||
await workspaceDataSource.transaction(
|
await workspaceDataSource.transaction(
|
||||||
async (entityManager: EntityManager) => {
|
async (entityManager: EntityManager) => {
|
||||||
await companyPrefillDemoData(entityManager, schemaName);
|
await seedCompanyWithDemoData(entityManager, schemaName);
|
||||||
await personPrefillDemoData(entityManager, schemaName);
|
await seedPersonWithDemoData(entityManager, schemaName);
|
||||||
await opportunityPrefillDemoData(entityManager, schemaName);
|
await seedOpportunityWithDemoData(entityManager, schemaName);
|
||||||
|
|
||||||
const viewDefinitionsWithId = await viewPrefillData(
|
const viewDefinitionsWithId = await seedViewWithDemoData(
|
||||||
entityManager,
|
entityManager,
|
||||||
schemaName,
|
schemaName,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
@ -48,7 +48,7 @@ export const demoObjectsPrefillData = async (
|
|||||||
entityManager,
|
entityManager,
|
||||||
schemaName,
|
schemaName,
|
||||||
);
|
);
|
||||||
await workspaceMemberPrefillData(entityManager, schemaName);
|
await seedWorkspaceMemberWithDemoData(entityManager, schemaName);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -13,7 +13,7 @@ import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objec
|
|||||||
import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view';
|
import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view';
|
||||||
import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view';
|
import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view';
|
||||||
|
|
||||||
export const viewPrefillData = async (
|
export const seedViewWithDemoData = async (
|
||||||
entityManager: EntityManager,
|
entityManager: EntityManager,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||||
@ -5,7 +5,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
||||||
import { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company';
|
import { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company';
|
||||||
import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person';
|
import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person';
|
||||||
import { viewPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/view';
|
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
|
||||||
|
|
||||||
export const standardObjectsPrefillData = async (
|
export const standardObjectsPrefillData = async (
|
||||||
workspaceDataSource: DataSource,
|
workspaceDataSource: DataSource,
|
||||||
@ -37,7 +37,7 @@ export const standardObjectsPrefillData = async (
|
|||||||
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
|
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
|
||||||
await companyPrefillData(entityManager, schemaName);
|
await companyPrefillData(entityManager, schemaName);
|
||||||
await personPrefillData(entityManager, schemaName);
|
await personPrefillData(entityManager, schemaName);
|
||||||
const viewDefinitionsWithId = await viewPrefillData(
|
const viewDefinitionsWithId = await seedViewWithDemoData(
|
||||||
entityManager,
|
entityManager,
|
||||||
schemaName,
|
schemaName,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
|
|||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||||
|
import { SeederModule } from 'src/engine/seeder/seeder.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
|
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
|
||||||
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
||||||
@ -15,6 +16,7 @@ import { WorkspaceManagerService } from './workspace-manager.service';
|
|||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
ObjectMetadataModule,
|
ObjectMetadataModule,
|
||||||
|
SeederModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceSyncMetadataModule,
|
WorkspaceSyncMetadataModule,
|
||||||
WorkspaceHealthModule,
|
WorkspaceHealthModule,
|
||||||
|
|||||||
@ -6,8 +6,13 @@ import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-s
|
|||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||||
|
import { PETS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/pets-data-seeds';
|
||||||
|
import { SURVEY_RESULTS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/survey-results-data-seeds';
|
||||||
|
import { PETS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/pets-metadata-seeds';
|
||||||
|
import { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/survey-results-metadata-seeds';
|
||||||
|
import { SeederService } from 'src/engine/seeder/seeder.service';
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
import { demoObjectsPrefillData } from 'src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data';
|
import { seedWorkspaceWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-with-demo-data';
|
||||||
import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data';
|
import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data';
|
||||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||||
|
|
||||||
@ -17,6 +22,7 @@ export class WorkspaceManagerService {
|
|||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||||
private readonly objectMetadataService: ObjectMetadataService,
|
private readonly objectMetadataService: ObjectMetadataService,
|
||||||
|
private readonly seederService: SeederService,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
||||||
private readonly featureFlagService: FeatureFlagService,
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
@ -139,12 +145,26 @@ export class WorkspaceManagerService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await demoObjectsPrefillData(
|
await seedWorkspaceWithDemoData(
|
||||||
workspaceDataSource,
|
workspaceDataSource,
|
||||||
dataSourceMetadata.schema,
|
dataSourceMetadata.schema,
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
isWorkflowEnabled,
|
isWorkflowEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.seederService.seedCustomObjects(
|
||||||
|
dataSourceMetadata.id,
|
||||||
|
workspaceId,
|
||||||
|
PETS_METADATA_SEEDS,
|
||||||
|
PETS_DATA_SEEDS,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.seederService.seedCustomObjects(
|
||||||
|
dataSourceMetadata.id,
|
||||||
|
workspaceId,
|
||||||
|
SURVEY_RESULTS_METADATA_SEEDS,
|
||||||
|
SURVEY_RESULTS_DATA_SEEDS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||||
|
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import {
|
import {
|
||||||
RelationMetadataType,
|
RelationMetadataType,
|
||||||
@ -31,7 +32,7 @@ export enum WorkflowVersionStatus {
|
|||||||
ARCHIVED = 'ARCHIVED',
|
ARCHIVED = 'ARCHIVED',
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkflowVersionStatusOptions = [
|
const WorkflowVersionStatusOptions: FieldMetadataComplexOption[] = [
|
||||||
{
|
{
|
||||||
value: WorkflowVersionStatus.DRAFT,
|
value: WorkflowVersionStatus.DRAFT,
|
||||||
label: 'Draft',
|
label: 'Draft',
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-o
|
|||||||
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
|
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
|
||||||
import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
||||||
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
|
import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||||
|
|
||||||
export enum WorkflowStatus {
|
export enum WorkflowStatus {
|
||||||
DRAFT = 'DRAFT',
|
DRAFT = 'DRAFT',
|
||||||
@ -32,7 +33,7 @@ export enum WorkflowStatus {
|
|||||||
DEACTIVATED = 'DEACTIVATED',
|
DEACTIVATED = 'DEACTIVATED',
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkflowStatusOptions = [
|
const WorkflowStatusOptions: FieldMetadataComplexOption[] = [
|
||||||
{
|
{
|
||||||
value: WorkflowStatus.DRAFT,
|
value: WorkflowStatus.DRAFT,
|
||||||
label: 'Draft',
|
label: 'Draft',
|
||||||
@ -49,7 +50,7 @@ const WorkflowStatusOptions = [
|
|||||||
value: WorkflowStatus.DEACTIVATED,
|
value: WorkflowStatus.DEACTIVATED,
|
||||||
label: 'Deactivated',
|
label: 'Deactivated',
|
||||||
position: 2,
|
position: 2,
|
||||||
color: 'grey',
|
color: 'gray',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
ThemeContext,
|
ThemeContext,
|
||||||
ThemeType,
|
ThemeType,
|
||||||
} from '@ui/theme';
|
} from '@ui/theme';
|
||||||
|
import { isDefined } from '@ui/utilities';
|
||||||
|
|
||||||
const spacing5 = THEME_COMMON.spacing(5);
|
const spacing5 = THEME_COMMON.spacing(5);
|
||||||
const spacing2 = THEME_COMMON.spacing(2);
|
const spacing2 = THEME_COMMON.spacing(2);
|
||||||
@ -22,8 +23,20 @@ const StyledTag = styled.h3<{
|
|||||||
preventShrink?: boolean;
|
preventShrink?: boolean;
|
||||||
}>`
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ color, theme }) =>
|
background: ${({ color, theme }) => {
|
||||||
color === 'transparent' ? color : theme.tag.background[color]};
|
if (color === 'transparent') {
|
||||||
|
return 'transparent';
|
||||||
|
} else {
|
||||||
|
const themeColor = theme.tag.background[color];
|
||||||
|
|
||||||
|
if (!isDefined(themeColor)) {
|
||||||
|
console.warn(`Tag color ${color} is not defined in the theme`);
|
||||||
|
return theme.tag.background.gray;
|
||||||
|
} else {
|
||||||
|
return themeColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
border-radius: ${BORDER_COMMON.radius.sm};
|
border-radius: ${BORDER_COMMON.radius.sm};
|
||||||
color: ${({ color, theme }) =>
|
color: ${({ color, theme }) =>
|
||||||
color === 'transparent'
|
color === 'transparent'
|
||||||
@ -103,10 +116,12 @@ export const Tag = ({
|
|||||||
variant={variant}
|
variant={variant}
|
||||||
preventShrink={preventShrink}
|
preventShrink={preventShrink}
|
||||||
>
|
>
|
||||||
{!!Icon && (
|
{isDefined(Icon) ? (
|
||||||
<StyledIconContainer>
|
<StyledIconContainer>
|
||||||
<Icon size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
|
<Icon size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
|
||||||
</StyledIconContainer>
|
</StyledIconContainer>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
)}
|
)}
|
||||||
{preventShrink ? (
|
{preventShrink ? (
|
||||||
<StyledNonShrinkableText>{text}</StyledNonShrinkableText>
|
<StyledNonShrinkableText>{text}</StyledNonShrinkableText>
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import { COLOR } from './Colors';
|
|||||||
export const ILLUSTRATION_ICON_DARK = {
|
export const ILLUSTRATION_ICON_DARK = {
|
||||||
color: {
|
color: {
|
||||||
blue: COLOR.blue50,
|
blue: COLOR.blue50,
|
||||||
grey: GRAY_SCALE.gray50,
|
gray: GRAY_SCALE.gray50,
|
||||||
},
|
},
|
||||||
fill: {
|
fill: {
|
||||||
blue: COLOR.blue70,
|
blue: COLOR.blue70,
|
||||||
grey: GRAY_SCALE.gray70,
|
gray: GRAY_SCALE.gray70,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import { COLOR } from './Colors';
|
|||||||
export const ILLUSTRATION_ICON_LIGHT = {
|
export const ILLUSTRATION_ICON_LIGHT = {
|
||||||
color: {
|
color: {
|
||||||
blue: COLOR.blue40,
|
blue: COLOR.blue40,
|
||||||
grey: GRAY_SCALE.gray40,
|
gray: GRAY_SCALE.gray40,
|
||||||
},
|
},
|
||||||
fill: {
|
fill: {
|
||||||
blue: COLOR.blue20,
|
blue: COLOR.blue20,
|
||||||
grey: GRAY_SCALE.gray20,
|
gray: GRAY_SCALE.gray20,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user