diff --git a/src/components/collection/Selector/List/Header/Toggle/MenuContent.tsx b/src/components/collection/Selector/List/Header/Toggle/MenuContent.tsx index eccec5fb7..f0cdff98d 100644 --- a/src/components/collection/Selector/List/Header/Toggle/MenuContent.tsx +++ b/src/components/collection/Selector/List/Header/Toggle/MenuContent.tsx @@ -1,8 +1,8 @@ import { Box, Button, Divider, RadioGroup, Stack } from '@mui/material'; +import RadioMenuItem from 'components/shared/RadioMenuItem'; import { useEntityType } from 'context/EntityContext'; import { SyntheticEvent, useState } from 'react'; -import { FormattedMessage } from 'react-intl'; -import ScopeMenuItem from './MenuItem'; +import { FormattedMessage, useIntl } from 'react-intl'; import { Scopes } from './types'; interface MenuOption { @@ -30,6 +30,7 @@ function ScopeMenuContent({ menuOptions, updateScope, }: Props) { + const intl = useIntl(); const entityType = useEntityType(); const [scope, setScope] = useState(initialScope); @@ -41,26 +42,22 @@ function ScopeMenuContent({ value={scope} style={{ maxWidth: 320, textWrap: 'wrap' }} > - - } - scope="all" - title={} + - - } - scope="page" - title={} + diff --git a/src/components/editor/Bindings/FieldSelection/FieldActionButton.tsx b/src/components/editor/Bindings/FieldSelection/FieldActionButton.tsx deleted file mode 100644 index 0f24e333d..000000000 --- a/src/components/editor/Bindings/FieldSelection/FieldActionButton.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Button } from '@mui/material'; -import { FormattedMessage } from 'react-intl'; -import { - useBinding_recommendFields, - useBinding_setMultiSelection, -} from 'stores/Binding/hooks'; -import { FieldSelectionDictionary } from 'stores/Binding/slices/FieldSelection'; -import { isRequireOnlyField } from 'utils/workflow-utils'; -import { CompositeProjection, FieldSelectionType } from './types'; - -interface Props { - bindingUUID: string; - disabled: boolean; - labelId: string; - projections: CompositeProjection[] | null | undefined; - selectedValue: FieldSelectionType; -} - -const evaluateUpdatedFields = ( - projections: CompositeProjection[], - recommended: boolean, - selectedValue: FieldSelectionType -) => { - const updatedFields: FieldSelectionDictionary = {}; - - projections.forEach(({ field, constraint, selectionMetadata }) => { - const required = constraint - ? isRequireOnlyField(constraint.type) - : false; - - let selectionType = required ? 'require' : selectedValue; - - if (recommended) { - selectionType = - selectedValue === 'exclude' && required - ? 'default' - : selectedValue; - } - - updatedFields[field] = { meta: selectionMetadata, mode: selectionType }; - }); - - return updatedFields; -}; - -export default function FieldActionButton({ - bindingUUID, - disabled, - labelId, - projections, - selectedValue, -}: Props) { - const recommended = useBinding_recommendFields(); - const setMultiSelection = useBinding_setMultiSelection(); - - return ( - - ); -} diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions.tsx b/src/components/editor/Bindings/FieldSelection/FieldActions.tsx deleted file mode 100644 index 907b71401..000000000 --- a/src/components/editor/Bindings/FieldSelection/FieldActions.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Stack } from '@mui/material'; -import { useBinding_recommendFields } from 'stores/Binding/hooks'; -import { useFormStateStore_isActive } from 'stores/FormState/hooks'; -import { hasLength } from 'utils/misc-utils'; -import FieldActionButton from './FieldActionButton'; -import { CompositeProjection } from './types'; - -interface Props { - bindingUUID: string; - loading: boolean; - projections: CompositeProjection[] | null | undefined; -} - -export default function FieldActions({ - bindingUUID, - loading, - projections, -}: Props) { - const recommended = useBinding_recommendFields(); - - const formActive = useFormStateStore_isActive(); - - return ( - - - - - - ); -} diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions/MenuActions.tsx b/src/components/editor/Bindings/FieldSelection/FieldActions/MenuActions.tsx new file mode 100644 index 000000000..7bbfb6c45 --- /dev/null +++ b/src/components/editor/Bindings/FieldSelection/FieldActions/MenuActions.tsx @@ -0,0 +1,45 @@ +import { Box, Button, Stack } from '@mui/material'; +import { useIntl } from 'react-intl'; +import { useBindingStore } from 'stores/Binding/Store'; +import SaveButton from './SaveButton'; +import { MenuActionProps } from './types'; + +export default function MenuActions({ + bindingUUID, + closeMenu, + loading, + projections, +}: MenuActionProps) { + const intl = useIntl(); + + const selectionAlgorithm = useBindingStore( + (state) => state.selectionAlgorithm + ); + + return ( + + + + + + ); +} diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions/MenuOptions.tsx b/src/components/editor/Bindings/FieldSelection/FieldActions/MenuOptions.tsx new file mode 100644 index 000000000..eac217224 --- /dev/null +++ b/src/components/editor/Bindings/FieldSelection/FieldActions/MenuOptions.tsx @@ -0,0 +1,46 @@ +import { RadioGroup } from '@mui/material'; +import RadioMenuItem from 'components/shared/RadioMenuItem'; +import { useIntl } from 'react-intl'; +import { SelectionAlgorithm } from 'stores/Binding/slices/FieldSelection'; +import { useBindingStore } from 'stores/Binding/Store'; + +export default function MenuOptions() { + const intl = useIntl(); + + const selectionAlgorithm = useBindingStore( + (state) => state.selectionAlgorithm + ); + const setSelectionAlgorithm = useBindingStore( + (state) => state.setSelectionAlgorithm + ); + + return ( + + setSelectionAlgorithm(event.target.value as SelectionAlgorithm) + } + value={selectionAlgorithm} + style={{ maxWidth: 320, textWrap: 'wrap' }} + > + + + + + ); +} diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions/SaveButton.tsx b/src/components/editor/Bindings/FieldSelection/FieldActions/SaveButton.tsx new file mode 100644 index 000000000..c415da1b8 --- /dev/null +++ b/src/components/editor/Bindings/FieldSelection/FieldActions/SaveButton.tsx @@ -0,0 +1,57 @@ +import { Button } from '@mui/material'; +import { useIntl } from 'react-intl'; +import { + useBinding_recommendFields, + useBinding_setMultiSelection, +} from 'stores/Binding/hooks'; +import { useFormStateStore_isActive } from 'stores/FormState/hooks'; +import { hasLength } from 'utils/misc-utils'; +import { evaluateUpdatedFields } from './shared'; +import { SaveButtonProps } from './types'; + +export default function SaveButton({ + bindingUUID, + closeMenu, + loading, + projections, + selectedAlgorithm, +}: SaveButtonProps) { + const intl = useIntl(); + + const recommended = useBinding_recommendFields(); + const setMultiSelection = useBinding_setMultiSelection(); + + const formActive = useFormStateStore_isActive(); + + return ( + + ); +} diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions/index.tsx b/src/components/editor/Bindings/FieldSelection/FieldActions/index.tsx new file mode 100644 index 000000000..43b7c4c8c --- /dev/null +++ b/src/components/editor/Bindings/FieldSelection/FieldActions/index.tsx @@ -0,0 +1,61 @@ +import { Button, Divider, Menu } from '@mui/material'; +import { NavArrowDown } from 'iconoir-react'; +import { useState } from 'react'; +import { useIntl } from 'react-intl'; +import MenuActions from './MenuActions'; +import MenuOptions from './MenuOptions'; +import { BaseProps } from './types'; + +export default function FieldActions({ + bindingUUID, + loading, + projections, +}: BaseProps) { + const intl = useIntl(); + + const [anchorEl, setAnchorEl] = useState(null); + const openMenu = Boolean(anchorEl); + const closeMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + + + + ); +} diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions/shared.ts b/src/components/editor/Bindings/FieldSelection/FieldActions/shared.ts new file mode 100644 index 000000000..2d0c814f2 --- /dev/null +++ b/src/components/editor/Bindings/FieldSelection/FieldActions/shared.ts @@ -0,0 +1,30 @@ +import { FieldSelectionDictionary } from 'stores/Binding/slices/FieldSelection'; +import { isRequireOnlyField } from 'utils/workflow-utils'; +import { CompositeProjection, FieldSelectionType } from '../types'; + +export const evaluateUpdatedFields = ( + projections: CompositeProjection[], + recommended: boolean, + selectedValue: FieldSelectionType +) => { + const updatedFields: FieldSelectionDictionary = {}; + + projections.forEach(({ field, constraint, selectionMetadata }) => { + const required = constraint + ? isRequireOnlyField(constraint.type) + : false; + + let selectionType = required ? 'require' : selectedValue; + + if (recommended) { + selectionType = + selectedValue === 'exclude' && required + ? 'default' + : selectedValue; + } + + updatedFields[field] = { meta: selectionMetadata, mode: selectionType }; + }); + + return updatedFields; +}; diff --git a/src/components/editor/Bindings/FieldSelection/FieldActions/types.ts b/src/components/editor/Bindings/FieldSelection/FieldActions/types.ts new file mode 100644 index 000000000..b576e1e52 --- /dev/null +++ b/src/components/editor/Bindings/FieldSelection/FieldActions/types.ts @@ -0,0 +1,16 @@ +import { SelectionAlgorithm } from 'stores/Binding/slices/FieldSelection'; +import { CompositeProjection } from '../types'; + +export interface BaseProps { + bindingUUID: string; + loading: boolean; + projections: CompositeProjection[] | null | undefined; +} + +export interface MenuActionProps extends BaseProps { + closeMenu: () => void; +} + +export interface SaveButtonProps extends MenuActionProps { + selectedAlgorithm: SelectionAlgorithm | null; +} diff --git a/src/components/collection/Selector/List/Header/Toggle/MenuItem.tsx b/src/components/shared/RadioMenuItem.tsx similarity index 73% rename from src/components/collection/Selector/List/Header/Toggle/MenuItem.tsx rename to src/components/shared/RadioMenuItem.tsx index b0bfef4a0..5aa71c0f5 100644 --- a/src/components/collection/Selector/List/Header/Toggle/MenuItem.tsx +++ b/src/components/shared/RadioMenuItem.tsx @@ -1,17 +1,14 @@ import { Box, FormControlLabel, Radio, Typography } from '@mui/material'; -import { ReactNode } from 'react'; -import { Scopes } from './types'; +import { RadioMenuItemProps } from './types'; -interface Props { - desc: ReactNode; - scope: Scopes; - title: ReactNode; -} - -function ScopeMenuItem({ desc, scope, title }: Props) { +export default function RadioMenuItem({ + description, + label, + value, +}: RadioMenuItemProps) { return ( } label={ @@ -21,7 +18,7 @@ function ScopeMenuItem({ desc, scope, title }: Props) { fontWeight: 500, }} > - {title} + {label} - {desc} + {description} } @@ -40,5 +37,3 @@ function ScopeMenuItem({ desc, scope, title }: Props) { /> ); } - -export default ScopeMenuItem; diff --git a/src/components/shared/types.ts b/src/components/shared/types.ts index e2233d04c..936c1e0da 100644 --- a/src/components/shared/types.ts +++ b/src/components/shared/types.ts @@ -14,3 +14,9 @@ export interface AlertBoxProps extends BaseComponentProps { onClose?: () => void; title?: string | ReactNode; } + +export interface RadioMenuItemProps { + description: string; + label: string; + value: string; +} diff --git a/src/lang/en-US/Workflows.ts b/src/lang/en-US/Workflows.ts index 5d36d6663..300cae6b6 100644 --- a/src/lang/en-US/Workflows.ts +++ b/src/lang/en-US/Workflows.ts @@ -222,7 +222,7 @@ export const Workflows: Record = { 'fieldSelection.message.docLink': `see the docs`, 'fieldSelection.message.docPath': `https://docs.estuary.dev/guides/customize-materialization-fields/`, - 'fieldSelection.cta.defaultAllFields': `Include recommended fields`, + 'fieldSelection.cta.selectAlgorithm': `Mode`, 'fieldSelection.dialog.refreshFields.header': `Please wait while we gather information about your resource fields`, 'fieldSelection.dialog.updateProjection.header': `Update Projection`, 'fieldSelection.dialog.updateProjection.header.new': `Add Projection`, @@ -234,10 +234,7 @@ export const Workflows: Record = { 'fieldSelection.refresh.alert': `Refreshing the fields is recommended as editing the config can sometimes change the options below.`, 'fieldSelection.update.failed': `Field selection update failed`, 'fieldSelection.table.cta.addProjection': `Add Projection`, - 'fieldSelection.table.cta.defaultField': `Default`, - 'fieldSelection.table.cta.defaultAllFields': `Default All`, 'fieldSelection.table.cta.excludeField': `Exclude`, - 'fieldSelection.table.cta.excludeAllFields': `Exclude All`, 'fieldSelection.table.cta.includeField': `Include`, 'fieldSelection.table.cta.renameField': `Rename`, 'fieldSelection.table.cta.requireField': `Require`, @@ -260,6 +257,10 @@ export const Workflows: Record = { 'fieldSelection.table.label.translated.unsatisfiable': `field is unsatisfiable`, 'fieldSelection.table.label.filter': `Filter fields`, 'fieldSelection.table.tooltip.disabledRowAction': `Action disabled: {reason}.`, + 'fieldSelection.massActionMenu.recommended.label': `Select Scalars`, + 'fieldSelection.massActionMenu.recommended.description': `Select essential, scalar fields`, + 'fieldSelection.massActionMenu.excludeAll.label': `Exclude All`, + 'fieldSelection.massActionMenu.excludeAll.description': `Exclude non-essential fields`, // Messages from binding editing 'updateBinding.error.noBinding': `Unable to update the proper binding. Contact Support.`, diff --git a/src/stores/Binding/slices/FieldSelection.ts b/src/stores/Binding/slices/FieldSelection.ts index a25a864d2..5d32df918 100644 --- a/src/stores/Binding/slices/FieldSelection.ts +++ b/src/stores/Binding/slices/FieldSelection.ts @@ -4,6 +4,8 @@ import { Schema } from 'types'; import { NamedSet } from 'zustand/middleware'; import { BindingState } from '../types'; +export type SelectionAlgorithm = 'excludeAll' | 'recommended'; + export interface FieldSelection { mode: FieldSelectionType | null; meta?: Schema; @@ -46,16 +48,26 @@ export interface StoreWithFieldSelection { value: StoreWithFieldSelection['selectionSaving'] ) => void; + selectionAlgorithm: SelectionAlgorithm | null; + setSelectionAlgorithm: ( + value: StoreWithFieldSelection['selectionAlgorithm'] + ) => void; + searchQuery: string | null; setSearchQuery: (value: StoreWithFieldSelection['searchQuery']) => void; } export const getInitialFieldSelectionData = (): Pick< StoreWithFieldSelection, - 'recommendFields' | 'searchQuery' | 'selectionSaving' | 'selections' + | 'recommendFields' + | 'searchQuery' + | 'selectionAlgorithm' + | 'selectionSaving' + | 'selections' > => ({ recommendFields: {}, searchQuery: null, + selectionAlgorithm: null, selectionSaving: false, selections: {}, }); @@ -100,6 +112,16 @@ export const getStoreWithFieldSelectionSettings = ( ); }, + setSelectionAlgorithm: (value) => { + set( + produce((state: BindingState) => { + state.selectionAlgorithm = value; + }), + false, + 'Selection Algorithm Set' + ); + }, + setSelectionSaving: (value) => { set( produce((state: BindingState) => {