Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable setting the backfill counter of all task bindings with a single action #965

Merged
merged 23 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
abcfb59
Add backfill section above binding selector
kiahna-tucker Feb 5, 2024
4226e1e
Attempt to synchronize the backfill toggle button states
kiahna-tucker Feb 5, 2024
789bcd2
Use a callback function as the backfill button click handler
kiahna-tucker Feb 6, 2024
e5dc56d
Remove updateAll component property
kiahna-tucker Feb 6, 2024
edf31a5
Default the binding index property for global binding update
kiahna-tucker Feb 6, 2024
116b9d6
Set backfilled collections and flag state at the same time
kiahna-tucker Feb 6, 2024
4b5279c
Make bindingMetadata argument an array in hook callback
kiahna-tucker Feb 6, 2024
3891a80
Add error message content fallback to hook
kiahna-tucker Feb 6, 2024
6de4f95
Remove logging statements
kiahna-tucker Feb 6, 2024
9803c66
Merge branch 'main' into kiahna-tucker/backfill/add-mass-action
kiahna-tucker Feb 6, 2024
0b21c99
Disable backfill button when binding selector empty
kiahna-tucker Feb 6, 2024
f2ded06
Restrict backfill button to edit workflows
kiahna-tucker Feb 6, 2024
e7801c1
Distinguish task-level backfill section description
kiahna-tucker Feb 6, 2024
3ace80c
Disable the backfill button when all bindings are disabled
kiahna-tucker Feb 6, 2024
22cde9b
Merge branch 'main' into kiahna-tucker/backfill/add-mass-action
kiahna-tucker Feb 7, 2024
877f5ab
Change return value of helper function for logical semantics
kiahna-tucker Feb 7, 2024
21e34ab
Accept memory cost over a performance tax
kiahna-tucker Feb 7, 2024
144dd77
Remove margin
kiahna-tucker Feb 7, 2024
9677917
Remove stack
kiahna-tucker Feb 7, 2024
763c658
Reset form error
kiahna-tucker Feb 7, 2024
a93343d
Merge branch 'main' into kiahna-tucker/backfill/add-mass-action
kiahna-tucker Feb 7, 2024
71efaf8
Add disabled styling for primary colored outlined toggle button
kiahna-tucker Feb 8, 2024
a5d8b3d
Reset backfill counter related before modifying spec post-rediscovery
kiahna-tucker Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/capture/AutoDiscoverySettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ function AutoDiscoverySettings({ readOnly }: Props) {
]);

return (
<Stack spacing={1} sx={{ mt: 2, mb: 3 }}>
<Typography sx={{ fontWeight: 500 }}>
<Stack spacing={1}>
<Typography variant="formSectionHeader">
<FormattedMessage id="workflows.autoDiscovery.header" />
</Typography>

Expand Down
11 changes: 9 additions & 2 deletions src/components/collection/ResourceConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, Typography } from '@mui/material';
import ResourceConfigForm from 'components/collection/ResourceConfigForm';
import Backfill from 'components/editor/Bindings/Backfill';
import FieldSelectionViewer from 'components/editor/Bindings/FieldSelection';
import ManualBackfill from 'components/editor/Bindings/ManualBackfill';
import TimeTravel from 'components/editor/Bindings/TimeTravel';
import { useEditorStore_queryResponse_draftedBindingIndex } from 'components/editor/Store/hooks';
import { useEntityType } from 'context/EntityContext';
Expand Down Expand Up @@ -54,7 +54,14 @@ function ResourceConfig({ collectionName, readOnly = false }: Props) {
</Box>

{isEdit && draftedBindingIndex > -1 && !collectionDisabled ? (
<ManualBackfill bindingIndex={draftedBindingIndex} />
<Backfill
bindingIndex={draftedBindingIndex}
description={
<FormattedMessage
id={`workflows.collectionSelector.manualBackfill.message.${entityType}`}
/>
}
/>
) : null}

{entityType === 'materialization' && !collectionDisabled ? (
Expand Down
189 changes: 189 additions & 0 deletions src/components/editor/Bindings/Backfill/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Box, Stack, Typography } from '@mui/material';
import OutlinedToggleButton from 'components/shared/OutlinedToggleButton';
import { Check } from 'iconoir-react';
import { ReactNode, useCallback, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import {
useFormStateStore_isActive,
useFormStateStore_setFormState,
} from 'stores/FormState/hooks';
import { FormStatus } from 'stores/FormState/types';
import {
useResourceConfig_allBindingsDisabled,
useResourceConfig_backfillAllBindings,
useResourceConfig_backfilledCollections,
useResourceConfig_collections,
useResourceConfig_currentCollection,
useResourceConfig_setBackfilledCollections,
} from 'stores/ResourceConfig/hooks';
import { hasLength } from 'utils/misc-utils';
import { useEditorStore_queryResponse_draftSpecs } from '../../Store/hooks';
import useUpdateBackfillCounter, {
BindingMetadata,
} from './useUpdateBackfillCounter';

export type BooleanString = 'true' | 'false';

interface Props {
description: ReactNode;
bindingIndex?: number;
}

function Backfill({ description, bindingIndex = -1 }: Props) {
const { updateBackfillCounter } = useUpdateBackfillCounter();

// Draft Editor Store
const draftSpecs = useEditorStore_queryResponse_draftSpecs();

// Form State Store
const formActive = useFormStateStore_isActive();
const setFormState = useFormStateStore_setFormState();

// Resource Config Store
const currentCollection = useResourceConfig_currentCollection();
const collections = useResourceConfig_collections();

const backfilledCollections = useResourceConfig_backfilledCollections();
const setBackfilledCollections =
useResourceConfig_setBackfilledCollections();

const backfillAllBindings = useResourceConfig_backfillAllBindings();
const allBindingsDisabled = useResourceConfig_allBindingsDisabled();

const selected = useMemo(() => {
if (bindingIndex === -1) {
return backfillAllBindings;
}

return currentCollection
? backfilledCollections.includes(currentCollection)
: false;
}, [
backfillAllBindings,
backfilledCollections,
bindingIndex,
currentCollection,
]);

const value: BooleanString = useMemo(
() => (selected ? 'true' : 'false'),
[selected]
);

const draftSpec = useMemo(
() =>
draftSpecs.length > 0 && draftSpecs[0].spec ? draftSpecs[0] : null,
[draftSpecs]
);

const evaluateServerDifferences = useCallback(
(increment: BooleanString) => {
if (bindingIndex === -1) {
return (
(backfillAllBindings && increment === 'false') ||
(!backfillAllBindings && increment === 'true')
);
}

if (currentCollection) {
return increment === 'true'
? !backfilledCollections.includes(currentCollection)
: backfilledCollections.includes(currentCollection);
}

return false;
},
[
backfillAllBindings,
backfilledCollections,
bindingIndex,
currentCollection,
]
);

const handleClick = useCallback(
(increment: BooleanString) => {
const serverUpdateRequired = evaluateServerDifferences(increment);

if (draftSpec && serverUpdateRequired) {
setFormState({ status: FormStatus.UPDATING, error: null });

const singleBindingUpdate =
bindingIndex > -1 && currentCollection;

const bindingMetadata: BindingMetadata[] = singleBindingUpdate
? [{ collection: currentCollection, bindingIndex }]
: [];

updateBackfillCounter(
draftSpec,
increment,
bindingMetadata
).then(
() => {
const targetCollection = singleBindingUpdate
? currentCollection
: undefined;

setBackfilledCollections(increment, targetCollection);
setFormState({ status: FormStatus.UPDATED });
},
(error) => {
setFormState({
status: FormStatus.FAILED,
error: {
title: 'workflows.collectionSelector.manualBackfill.error.title',
error,
},
});
}
);
}
},
[
bindingIndex,
currentCollection,
draftSpec,
evaluateServerDifferences,
setBackfilledCollections,
setFormState,
updateBackfillCounter,
]
);

return (
<Box sx={{ mt: 3 }}>
<Stack spacing={1} sx={{ mb: 2 }}>
<Typography
variant={bindingIndex === -1 ? 'formSectionHeader' : 'h6'}
>
<FormattedMessage id="workflows.collectionSelector.manualBackfill.header" />
</Typography>

<Typography component="div">{description}</Typography>
</Stack>

<OutlinedToggleButton
value={value}
selected={selected}
disabled={
formActive || !hasLength(collections) || allBindingsDisabled
}
onClick={(event, checked: string) => {
event.preventDefault();
event.stopPropagation();

handleClick(checked === 'true' ? 'false' : 'true');
}}
>
<FormattedMessage id="workflows.collectionSelector.manualBackfill.cta.backfill" />

{selected ? (
<Check style={{ marginLeft: 8, fontSize: 13 }} />
) : null}
</OutlinedToggleButton>
</Box>
);
}

export default Backfill;
134 changes: 134 additions & 0 deletions src/components/editor/Bindings/Backfill/useUpdateBackfillCounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { modifyDraftSpec } from 'api/draftSpecs';
import { BooleanString } from 'components/editor/Bindings/Backfill';
import { useEntityType } from 'context/EntityContext';
import { DraftSpecQuery } from 'hooks/useDraftSpecs';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
import { BASE_ERROR } from 'services/supabase';
import { useResourceConfig_backfilledCollections } from 'stores/ResourceConfig/hooks';
import { Schema } from 'types';
import { hasLength } from 'utils/misc-utils';
import { getBackfillCounter, getCollectionName } from 'utils/workflow-utils';
import {
useEditorStore_persistedDraftId,
useEditorStore_queryResponse_mutate,
} from '../../Store/hooks';

export interface BindingMetadata {
bindingIndex: number;
collection: string;
}

const evaluateBackfillCounter = (
binding: Schema,
increment: BooleanString
): number => {
let counter = getBackfillCounter(binding);

if (increment === 'true') {
counter = counter + 1;
} else if (counter > 0) {
counter = counter - 1;
}

return counter;
};

function useUpdateBackfillCounter() {
const intl = useIntl();
const entityType = useEntityType();

// Draft Editor Store
const draftId = useEditorStore_persistedDraftId();
const mutateDraftSpecs = useEditorStore_queryResponse_mutate();

// Resource Config Store
const backfilledCollections = useResourceConfig_backfilledCollections();

const updateBackfillCounter = useCallback(
async (
draftSpec: DraftSpecQuery,
increment: BooleanString,
bindingMetadata: BindingMetadata[]
) => {
const bindingMetadataExists = hasLength(bindingMetadata);

const invalidBindingIndex = bindingMetadataExists
? bindingMetadata.findIndex(
({ bindingIndex }) => bindingIndex === -1
)
: -1;

if (!mutateDraftSpecs || invalidBindingIndex > -1) {
const errorMessageId = bindingMetadataExists
? 'workflows.collectionSelector.manualBackfill.error.message.singleCollection'
: 'workflows.collectionSelector.manualBackfill.error.message.allBindings';

const errorMessageValues = bindingMetadataExists
? {
collection:
bindingMetadata[invalidBindingIndex].collection,
}
: undefined;

return Promise.reject({
...BASE_ERROR,
message: intl.formatMessage(
{ id: errorMessageId },
errorMessageValues
),
});
}

const spec: Schema = draftSpec.spec;

if (bindingMetadataExists) {
bindingMetadata.forEach(({ bindingIndex }) => {
if (bindingIndex > -1) {
spec.bindings[bindingIndex].backfill =
evaluateBackfillCounter(
spec.bindings[bindingIndex],
increment
);
}
});
} else {
spec.bindings.forEach((binding: Schema, index: number) => {
const collection = getCollectionName(binding);
const collectionBackfilled =
backfilledCollections.includes(collection);

const shouldIncrement =
!collectionBackfilled && increment === 'true';

const shouldDecrement =
collectionBackfilled && increment === 'false';

if (shouldIncrement || shouldDecrement) {
spec.bindings[index].backfill = evaluateBackfillCounter(
binding,
increment
);
}
});
}

const updateResponse = await modifyDraftSpec(spec, {
draft_id: draftId,
catalog_name: draftSpec.catalog_name,
spec_type: entityType,
});

if (updateResponse.error) {
return Promise.reject(updateResponse.error);
}

return mutateDraftSpecs();
},
[backfilledCollections, draftId, entityType, intl, mutateDraftSpecs]
);

return { updateBackfillCounter };
}

export default useUpdateBackfillCounter;
Loading