diff --git a/packages/api-v4/src/delivery/types.ts b/packages/api-v4/src/delivery/types.ts
index cb8691f8211..84f2d82460a 100644
--- a/packages/api-v4/src/delivery/types.ts
+++ b/packages/api-v4/src/delivery/types.ts
@@ -1,6 +1,7 @@
export const streamStatus = {
Active: 'active',
Inactive: 'inactive',
+ Provisioning: 'provisioning',
} as const;
export type StreamStatus = (typeof streamStatus)[keyof typeof streamStatus];
diff --git a/packages/manager/.changeset/pr-13284-added-1768550251063.md b/packages/manager/.changeset/pr-13284-added-1768550251063.md
new file mode 100644
index 00000000000..221c78b9716
--- /dev/null
+++ b/packages/manager/.changeset/pr-13284-added-1768550251063.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Added
+---
+
+Logs Stream - Provisioning status ([#13284](https://github.com/linode/manager/pull/13284))
diff --git a/packages/manager/src/features/Delivery/Shared/FormSubmitBar/FormSubmitBar.tsx b/packages/manager/src/features/Delivery/Shared/FormSubmitBar/FormSubmitBar.tsx
index 640d6093ce5..80e8ecfcad4 100644
--- a/packages/manager/src/features/Delivery/Shared/FormSubmitBar/FormSubmitBar.tsx
+++ b/packages/manager/src/features/Delivery/Shared/FormSubmitBar/FormSubmitBar.tsx
@@ -21,6 +21,7 @@ interface StreamFormSubmitBarProps {
mode: FormMode;
onSubmit: () => void;
onTestConnection: () => void;
+ submitButtonTooltip?: string;
}
export const FormSubmitBar = (props: StreamFormSubmitBarProps) => {
@@ -35,6 +36,7 @@ export const FormSubmitBar = (props: StreamFormSubmitBarProps) => {
isSubmitting,
isTesting,
disableTestConnection = false,
+ submitButtonTooltip,
} = props;
const capitalizedFormType = capitalize(formType);
@@ -108,6 +110,7 @@ export const FormSubmitBar = (props: StreamFormSubmitBarProps) => {
alignSelf: 'flex-end',
},
})}
+ tooltipText={submitButtonTooltip}
>
{buttonLabel}
diff --git a/packages/manager/src/features/Delivery/Shared/types.ts b/packages/manager/src/features/Delivery/Shared/types.ts
index 205e44573bd..ee312d80896 100644
--- a/packages/manager/src/features/Delivery/Shared/types.ts
+++ b/packages/manager/src/features/Delivery/Shared/types.ts
@@ -48,6 +48,11 @@ export const streamStatusOptions: AutocompleteOption[] = [
label: 'Inactive',
pendoId: 'Logs Delivery Streams-Status Inactive',
},
+ {
+ value: streamStatus.Provisioning,
+ label: 'Provisioning',
+ pendoId: 'Logs Delivery Streams-Status Provisioning',
+ },
];
export type DestinationDetailsForm =
diff --git a/packages/manager/src/features/Delivery/Streams/StreamActionMenu.tsx b/packages/manager/src/features/Delivery/Streams/StreamActionMenu.tsx
index f976acd4096..97078a82672 100644
--- a/packages/manager/src/features/Delivery/Streams/StreamActionMenu.tsx
+++ b/packages/manager/src/features/Delivery/Streams/StreamActionMenu.tsx
@@ -32,6 +32,7 @@ export const StreamActionMenu = (props: StreamActionMenuProps) => {
},
title: stream.status === streamStatus.Active ? 'Deactivate' : 'Activate',
pendoId: `Logs Delivery Streams-${stream.status === streamStatus.Active ? 'Deactivate' : 'Activate'}`,
+ disabled: stream.status === streamStatus.Provisioning,
},
{
onClick: () => {
diff --git a/packages/manager/src/features/Delivery/Streams/StreamForm/StreamEdit.test.tsx b/packages/manager/src/features/Delivery/Streams/StreamForm/StreamEdit.test.tsx
index 2d1eaacd74b..7b0d5f6c18f 100644
--- a/packages/manager/src/features/Delivery/Streams/StreamForm/StreamEdit.test.tsx
+++ b/packages/manager/src/features/Delivery/Streams/StreamForm/StreamEdit.test.tsx
@@ -224,6 +224,48 @@ describe('StreamEdit', () => {
expect(editStreamSpy).toHaveBeenCalled();
});
});
+
+ describe('and stream has status: provisioning', () => {
+ it('should have disabled Edit Stream button and show info tooltip', async () => {
+ server.use(
+ http.get('*/monitor/streams/destinations', () => {
+ return HttpResponse.json(makeResourcePage(mockDestinations));
+ }),
+ http.get(`*/monitor/streams/${streamId}`, () => {
+ return HttpResponse.json({
+ ...mockStream,
+ status: 'provisioning',
+ });
+ })
+ );
+
+ renderWithThemeAndHookFormContext({
+ component: ,
+ });
+ const loadingElement = screen.queryByTestId(loadingTestId);
+ await waitForElementToBeRemoved(loadingElement);
+
+ const editStreamButton = screen.getByRole('button', {
+ name: saveStreamButtonText,
+ });
+
+ // Edit stream button should be disabled
+ expect(editStreamButton).toBeDisabled();
+
+ // Edit stream
+ await userEvent.hover(editStreamButton);
+
+ await waitFor(() => {
+ expect(screen.getByRole('tooltip')).toBeInTheDocument();
+ });
+
+ const disabledButtonTooltip = screen.getByText(
+ 'You cannot save changes while the stream is provisioning.'
+ );
+
+ expect(disabledButtonTooltip).toBeInTheDocument();
+ });
+ });
});
});
diff --git a/packages/manager/src/features/Delivery/Streams/StreamForm/StreamForm.tsx b/packages/manager/src/features/Delivery/Streams/StreamForm/StreamForm.tsx
index 6ac55bb6ff8..19b01c72a73 100644
--- a/packages/manager/src/features/Delivery/Streams/StreamForm/StreamForm.tsx
+++ b/packages/manager/src/features/Delivery/Streams/StreamForm/StreamForm.tsx
@@ -1,5 +1,6 @@
import {
type CreateDestinationPayload,
+ streamStatus,
type StreamStatus,
streamType,
} from '@linode/api-v4';
@@ -14,7 +15,7 @@ import Grid from '@mui/material/Grid';
import { useNavigate } from '@tanstack/react-router';
import { enqueueSnackbar } from 'notistack';
import * as React from 'react';
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { type SubmitHandler, useFormContext, useWatch } from 'react-hook-form';
import {
@@ -72,6 +73,18 @@ export const StreamForm = (props: StreamFormProps) => {
name: 'destination',
});
+ const selectedStreamStatus = useWatch({
+ control,
+ name: 'stream.status',
+ });
+ const submitButtonTooltip = useMemo(
+ () =>
+ selectedStreamStatus === streamStatus.Provisioning
+ ? 'You cannot save changes while the stream is provisioning.'
+ : undefined,
+ [selectedStreamStatus]
+ );
+
useEffect(() => {
setDestinationVerified(false);
}, [destination, setDestinationVerified]);
@@ -194,7 +207,10 @@ export const StreamForm = (props: StreamFormProps) => {
{
scrollErrorIntoViewV2(formRef)
)}
onTestConnection={handleTestConnection}
+ submitButtonTooltip={submitButtonTooltip}
/>
diff --git a/packages/manager/src/features/Delivery/Streams/StreamTableRow.tsx b/packages/manager/src/features/Delivery/Streams/StreamTableRow.tsx
index 8184c7f33f9..44b92c1fddc 100644
--- a/packages/manager/src/features/Delivery/Streams/StreamTableRow.tsx
+++ b/packages/manager/src/features/Delivery/Streams/StreamTableRow.tsx
@@ -13,6 +13,7 @@ import { LinkWithTooltipAndEllipsis } from 'src/features/Delivery/Shared/LinkWit
import { StreamActionMenu } from 'src/features/Delivery/Streams/StreamActionMenu';
import type { Stream, StreamStatus } from '@linode/api-v4';
+import type { Status } from 'src/components/StatusIcon/StatusIcon';
import type { StreamHandlers } from 'src/features/Delivery/Streams/StreamActionMenu';
interface StreamTableRowProps extends StreamHandlers {
@@ -22,6 +23,9 @@ interface StreamTableRowProps extends StreamHandlers {
export const StreamTableRow = React.memo((props: StreamTableRowProps) => {
const { stream, onDelete, onDisableOrEnable, onEdit } = props;
const { id, status } = stream;
+ const iconStatus = (
+ ['active', 'error', 'inactive'].includes(status) ? status : 'other'
+ ) as Status;
return (
@@ -35,7 +39,7 @@ export const StreamTableRow = React.memo((props: StreamTableRowProps) => {
{getStreamTypeOption(stream.type)?.label}
-
+
{humanizeStreamStatus(status)}
{id}
@@ -70,6 +74,8 @@ const humanizeStreamStatus = (status: StreamStatus) => {
return 'Active';
case 'inactive':
return 'Inactive';
+ case 'provisioning':
+ return 'Provisioning';
default:
return 'Unknown';
}
diff --git a/packages/validation/src/delivery.schema.ts b/packages/validation/src/delivery.schema.ts
index 28a183b16f5..465748b70e6 100644
--- a/packages/validation/src/delivery.schema.ts
+++ b/packages/validation/src/delivery.schema.ts
@@ -212,7 +212,11 @@ const streamSchemaBase = object({
.min(3, 'Stream name must have at least 3 characters')
.max(maxLength, maxLengthMessage)
.required('Stream name is required.'),
- status: mixed<'active' | 'inactive'>().oneOf(['active', 'inactive']),
+ status: mixed<'active' | 'inactive' | 'provisioning'>().oneOf([
+ 'active',
+ 'inactive',
+ 'provisioning',
+ ]),
type: string()
.oneOf(['audit_logs', 'lke_audit_logs'])
.required('Stream type is required.'),
@@ -229,8 +233,8 @@ export const createStreamSchema = streamSchemaBase;
export const updateStreamSchema = streamSchemaBase
.omit(['type'])
.shape({
- status: mixed<'active' | 'inactive'>()
- .oneOf(['active', 'inactive'])
+ status: mixed<'active' | 'inactive' | 'provisioning'>()
+ .oneOf(['active', 'inactive', 'provisioning'])
.required(),
details: lazy((value) => {
if (