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

Display alert when an old, trial-only binding is added or backfilled #1440

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6b1d88b
Create foundation for top-level alert
kiahna-tucker Jan 28, 2025
c54e848
Display an alert when backfilling a old collection
kiahna-tucker Jan 28, 2025
2b3d449
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Jan 29, 2025
6e3d79f
Move trial only storage-related logic out of tenant store
kiahna-tucker Jan 31, 2025
503e9f2
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Jan 31, 2025
36fc4ac
Replace trial only storage hydrator with hook
kiahna-tucker Feb 3, 2025
99d99a1
Display alert when an old, trial-only collection added
kiahna-tucker Feb 3, 2025
1e038e5
Reduce scope of useTrialCollection hook
kiahna-tucker Feb 3, 2025
98ec30e
Correct backfill alert triggers
kiahna-tucker Feb 5, 2025
ff3ef0f
Rename and simplify setTrialOnlyStorage action
kiahna-tucker Feb 5, 2025
28b01d7
Add soureBackfillRecommended flag to binding metadata
kiahna-tucker Feb 6, 2025
9484923
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Feb 6, 2025
1fcee1c
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Feb 7, 2025
880248b
Evaluate trial collections when binding store hydrated
kiahna-tucker Feb 7, 2025
846e824
Consolidate log rocket events
kiahna-tucker Feb 10, 2025
4ef4859
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Feb 10, 2025
02b8b46
Move and rename trial storage-related hooks
kiahna-tucker Feb 11, 2025
35865c6
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Feb 11, 2025
9ff4db8
Remove trial metadata store
kiahna-tucker Feb 11, 2025
e9e950b
Extend condition dictating whether the top-level backfill alert is hi…
kiahna-tucker Feb 11, 2025
45d0352
Narrow trial collection query and update add trigger
kiahna-tucker Feb 11, 2025
e51f8a6
Store binding metadata in useGenerateCatalog
kiahna-tucker Feb 12, 2025
0bb43ed
Move collection metadata properties into independent state
kiahna-tucker Feb 13, 2025
37cb72d
Store binding error indicator logic in a constant
kiahna-tucker Feb 13, 2025
e42e20f
Store trial duration in env variable
kiahna-tucker Feb 13, 2025
e9c6ccf
Rename collection metadata-related variables and overriding linting c…
kiahna-tucker Feb 14, 2025
7f3d769
Reverse order in which sourceBackfillRecommended is evaluated
kiahna-tucker Feb 14, 2025
ada6946
Attempt six to override linter
kiahna-tucker Feb 14, 2025
03e3f3f
Remove trial duration env util
kiahna-tucker Feb 14, 2025
014c815
Return added collections from prefillBindingDependentState
kiahna-tucker Feb 14, 2025
fede7a2
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Feb 18, 2025
183a6eb
Create independent component for CollectionConfig alert
kiahna-tucker Feb 18, 2025
846e14e
Rename SectionAlert component to SectionAlertIndicator
kiahna-tucker Feb 18, 2025
13eb87d
Increase sensitivity to backfill all button
kiahna-tucker Feb 20, 2025
8a2bc3c
Comment use of setCollectionMetadata action
kiahna-tucker Feb 21, 2025
45fa211
Merge branch 'main' into kiahna-tucker/ux-tweaks/backfill-without-sto…
kiahna-tucker Feb 21, 2025
0ed1839
Return a change summary from updateBackfillCounter
kiahna-tucker Feb 21, 2025
20f3a1f
Unset sourceBackfillRecommended when backfill counter decremented
kiahna-tucker Feb 21, 2025
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
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ VITE_DOCS_ORIGIN=https://docs.estuary.dev
VITE_DOCS_IFRAME_STRING_INCLUDE=estuary.dev

VITE_STRIPE_PUBLISHABLE_KEY=pk_live_51LC6y0L0ioq4FQU8sBPuwFgtfQSvixeKCWWRhWBbNf0q4NqXuBlkNJK9DronbnlY8x7wTXgu6KhYjw26XFpx0U2200Fqz8Dawb

VITE_TRIAL_DURATION=20
51 changes: 51 additions & 0 deletions src/api/liveSpecsExt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PostgrestResponse } from '@supabase/postgrest-js';
import { supabaseClient } from 'context/GlobalProviders';
import { ProtocolLabel } from 'data-plane-gateway/types/gen/consumer/protocol/consumer';
import { DateTime } from 'luxon';
import pLimit from 'p-limit';
import {
CONNECTOR_IMAGE,
Expand All @@ -25,6 +26,8 @@ import {
import { CHUNK_SIZE, DEMO_TENANT } from 'utils/misc-utils';
import { getCountSettings } from 'utils/table-utils';

const trialDuration = import.meta.env.VITE_TRIAL_DURATION;

const baseColumns = [
'catalog_name',
'id',
Expand Down Expand Up @@ -429,6 +432,53 @@ const getLiveSpecShards = (tenant: string, entityType: Entity) => {
.eq('spec_type', entityType);
};

export interface TrialCollectionQuery {
catalog_name: string;
updated_at: string;
}

const getTrialCollections = async (
trialPrefixes: string[],
catalogNames: string[]
) => {
const limiter = pLimit(3);
const promises: Promise<PostgrestResponse<TrialCollectionQuery>>[] = [];
let index = 0;

const trialCollections = catalogNames.filter((name) =>
trialPrefixes.some((prefix) => name.startsWith(prefix))
);

const promiseGenerator = (idx: number) => {
const trialThreshold = DateTime.utc().minus({
days: trialDuration,
});
const catalogNameFilter = trialCollections
.slice(idx, idx + CHUNK_SIZE)
.map((name) => `catalog_name.eq.${name}`)
.join(',');

return supabaseClient
.from(TABLES.LIVE_SPECS_EXT)
.select('catalog_name,updated_at')
.or(catalogNameFilter)
.eq('spec_type', 'collection')
.lt('updated_at', trialThreshold);
};

while (index < trialCollections.length) {
const prom = promiseGenerator(index);
promises.push(limiter(() => prom));

index = index + CHUNK_SIZE;
}

const response = await Promise.all(promises);
const errors = response.filter((r) => r.error);

return errors[0] ?? { data: response.flatMap((r) => r.data) };
};

const liveSpecsExtRelatedColumns = ['catalog_name', 'reads_from', 'id'];
export const liveSpecsExtRelatedQuery = liveSpecsExtRelatedColumns.join(',');
export interface LiveSpecsExt_Related {
Expand Down Expand Up @@ -482,4 +532,5 @@ export {
getLiveSpecs_entitySelector,
getLiveSpecs_existingTasks,
getLiveSpecs_materializations,
getTrialCollections,
};
23 changes: 22 additions & 1 deletion src/api/storageMappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { PostgrestSingleResponse } from '@supabase/postgrest-js';
import { supabaseClient } from 'context/GlobalProviders';
import {
defaultTableFilter,
handleFailure,
handleSuccess,
Pagination,
RPCS,
SortingProps,
Expand Down Expand Up @@ -51,6 +53,20 @@ const getStorageMapping = (catalog_prefix: string) => {
.returns<StorageMappings[]>();
};

const getStorageMappingStores = async (prefixes: string[]) => {
return supabaseRetry(
() =>
supabaseClient
.from(TABLES.STORAGE_MAPPINGS)
.select('catalog_prefix,spec')
.in('catalog_prefix', prefixes),
'getStorageMappingStores'
).then(
handleSuccess<Pick<StorageMappings, 'catalog_prefix' | 'spec'>[]>,
handleFailure
);
};

const republishPrefix = async (prefix: string) => {
return supabaseRetry<PostgrestSingleResponse<string>>(
() =>
Expand All @@ -61,4 +77,9 @@ const republishPrefix = async (prefix: string) => {
);
};

export { getStorageMapping, getStorageMappings, republishPrefix };
export {
getStorageMapping,
getStorageMappingStores,
getStorageMappings,
republishPrefix,
};
70 changes: 13 additions & 57 deletions src/components/collection/Config.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,39 @@
import { Typography, useTheme } from '@mui/material';
import { Typography } from '@mui/material';
import MessageWithLink from 'components/content/MessageWithLink';
import BindingsMultiEditor from 'components/editor/Bindings';
import AlertBox from 'components/shared/AlertBox';
import WrapperWithHeader from 'components/shared/Entity/WrapperWithHeader';
import ErrorBoundryWrapper from 'components/shared/ErrorBoundryWrapper';
import { DraftSpecQuery } from 'hooks/useDraftSpecs';
import { WarningCircle } from 'iconoir-react';
import { ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import {
useBinding_bindingErrorsExist,
useBinding_fullSourceErrorsExist,
useBinding_hydrationErrorsExist,
useBinding_resourceConfigErrorsExist,
} from 'stores/Binding/hooks';
import { useFormStateStore_messagePrefix } from 'stores/FormState/hooks';

interface Props {
draftSpecs: DraftSpecQuery[];
readOnly?: boolean;
hideBorder?: boolean;
RediscoverButton?: ReactNode;
}
import { useIntl } from 'react-intl';
import { useBinding_hydrationErrorsExist } from 'stores/Binding/hooks';
import SectionAlertIndicator from './SectionAlertIndicator';
import { CollectionConfigProps } from './types';

function CollectionConfig({
draftSpecs,
readOnly = false,
hideBorder,
RediscoverButton,
}: Props) {
const theme = useTheme();
}: CollectionConfigProps) {
const intl = useIntl();

// Binding Store
const bindingHydrationErrorsExist = useBinding_hydrationErrorsExist();
const resourceConfigErrorsExist = useBinding_resourceConfigErrorsExist();
const bindingErrorsExist = useBinding_bindingErrorsExist();
const fullSourceErrorsExist = useBinding_fullSourceErrorsExist();

// Form State Store
const messagePrefix = useFormStateStore_messagePrefix();

const hasErrors =
bindingHydrationErrorsExist ||
resourceConfigErrorsExist ||
fullSourceErrorsExist;

const hasWarnings = bindingErrorsExist;

return (
<WrapperWithHeader
hideBorder={hideBorder}
header={
<>
{hasErrors || hasWarnings ? (
<WarningCircle
style={{
marginRight: 4,
fontSize: 12,
color: hasErrors
? theme.palette.error.main
: theme.palette.warning.main,
}}
/>
) : null}

<Typography variant="subtitle1">
<FormattedMessage
id={`${messagePrefix}.collections.heading`}
/>
</Typography>
</>
}
header={<SectionAlertIndicator />}
>
<ErrorBoundryWrapper>
{bindingHydrationErrorsExist ? (
<AlertBox
severity="error"
title={
<FormattedMessage id="workflows.error.initFormSection" />
<Typography component="span">
{intl.formatMessage({
id: 'workflows.error.initFormSection',
})}
</Typography>
}
short
>
Expand Down
22 changes: 22 additions & 0 deletions src/components/collection/ResourceConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import AdvancedOptions from 'components/editor/Bindings/AdvancedOptions';
import Backfill from 'components/editor/Bindings/Backfill';
import FieldSelectionViewer from 'components/editor/Bindings/FieldSelection';
import { useEditorStore_queryResponse_draftedBindingIndex } from 'components/editor/Store/hooks';
import TrialOnlyPrefixAlert from 'components/materialization/TrialOnlyPrefixAlert';
import ErrorBoundryWrapper from 'components/shared/ErrorBoundryWrapper';
import { useEntityType } from 'context/EntityContext';
import { FormattedMessage } from 'react-intl';
import {
useBinding_collectionMetadataProperty,
useBinding_currentBindingIndex,
useBinding_hydrated,
useBinding_resourceConfigOfMetaBindingProperty,
Expand Down Expand Up @@ -46,8 +48,27 @@ function ResourceConfig({
'disable'
);

const trialCollection = useBinding_collectionMetadataProperty(
collectionName,
'trialStorage'
);

const collectionAdded = useBinding_collectionMetadataProperty(
collectionName,
'added'
);

return (
<>
{entityType === 'materialization' ? (
<Box style={{ marginBottom: 16 }}>
<TrialOnlyPrefixAlert
messageId="workflows.error.oldBoundCollection.added"
triggered={Boolean(trialCollection && collectionAdded)}
/>
</Box>
) : null}

<Typography
component="div"
sx={{ mb: 2 }}
Expand All @@ -72,6 +93,7 @@ function ResourceConfig({

<Backfill
bindingIndex={draftedBindingIndex}
collection={collectionName}
collectionEnabled={!collectionDisabled}
/>

Expand Down
59 changes: 59 additions & 0 deletions src/components/collection/SectionAlertIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Typography, useTheme } from '@mui/material';
import { WarningCircle } from 'iconoir-react';
import { useIntl } from 'react-intl';
import {
useBinding_bindingErrorsExist,
useBinding_fullSourceErrorsExist,
useBinding_hydrationErrorsExist,
useBinding_resourceConfigErrorsExist,
} from 'stores/Binding/hooks';
import { useBindingStore } from 'stores/Binding/Store';
import { useFormStateStore_messagePrefix } from 'stores/FormState/hooks';

export default function SectionAlertIndicator() {
const intl = useIntl();
const theme = useTheme();

// Binding Store
const bindingHydrationErrorsExist = useBinding_hydrationErrorsExist();
const resourceConfigErrorsExist = useBinding_resourceConfigErrorsExist();
const bindingErrorsExist = useBinding_bindingErrorsExist();
const fullSourceErrorsExist = useBinding_fullSourceErrorsExist();
const sourceBackfillRecommended = useBindingStore((state) =>
Object.values(state.collectionMetadata).some(
(meta) => meta.sourceBackfillRecommended
)
);

// Form State Store
const messagePrefix = useFormStateStore_messagePrefix();

const hasErrors =
bindingHydrationErrorsExist ||
resourceConfigErrorsExist ||
fullSourceErrorsExist;

const hasWarnings = bindingErrorsExist || sourceBackfillRecommended;

return (
<>
{hasErrors || hasWarnings ? (
<WarningCircle
style={{
marginRight: 4,
fontSize: 12,
color: hasErrors
? theme.palette.error.main
: theme.palette.warning.main,
}}
/>
) : null}

<Typography variant="subtitle1">
{intl.formatMessage({
id: `${messagePrefix}.collections.heading`,
})}
</Typography>
</>
);
}
9 changes: 9 additions & 0 deletions src/components/collection/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DraftSpecQuery } from 'hooks/useDraftSpecs';
import { ReactNode } from 'react';

export interface CollectionConfigProps {
draftSpecs: DraftSpecQuery[];
readOnly?: boolean;
hideBorder?: boolean;
RediscoverButton?: ReactNode;
}
Loading
Loading