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 (
+ <>
+
+ }
+ onClick={(event) => {
+ event.stopPropagation();
+ setAnchorEl(event.currentTarget);
+ }}
+ size="small"
+ style={{ minWidth: 'fit-content' }}
+ variant="outlined"
+ >
+ {intl.formatMessage({
+ id: 'fieldSelection.cta.selectAlgorithm',
+ })}
+
+
+
+ >
+ );
+}
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) => {