Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-13492-added-1773404986416.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

Stream Metrics tab with embedded metrics dashboard ([#13492](https://github.com/linode/manager/pull/13492))
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function editDestinationViaActionMenu(
mockGetDestination(destination);
// Edit destination redirect
ui.actionMenuItem.findByTitle('Edit').click();
cy.url().should('endWith', `/destinations/${destination.id}/edit`);
cy.url().should('endWith', `/destinations/${destination.id}/summary`);
});
}

Expand Down Expand Up @@ -151,7 +151,10 @@ describe('destinations landing checks for non-empty state', () => {

// Redirect to destination edit page via name
cy.findByText(exampleDestination.label).click();
cy.url().should('endWith', `/destinations/${exampleDestination.id}/edit`);
cy.url().should(
'endWith',
`/destinations/${exampleDestination.id}/summary`
);
cy.wait('@getDestination');

cy.visit('/logs/delivery/destinations');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ describe('Edit Destination', () => {
bypassAccountCapabilities: true,
},
});
cy.visitWithLogin(`/logs/delivery/destinations/${mockDestination.id}/edit`);
cy.visitWithLogin(
`/logs/delivery/destinations/${mockDestination.id}/summary`
);
mockGetDestination(mockDestination);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ describe('Edit Stream', () => {
mockGetStream(mockAuditLogsStream);

// Visit the Edit Stream page
cy.visitWithLogin(
`/logs/delivery/streams/${mockAuditLogsStream.id}/edit/`
);
cy.visitWithLogin(`/logs/delivery/streams/${mockAuditLogsStream.id}`);

const updatedLabel = randomLabel();

Expand Down Expand Up @@ -206,9 +204,7 @@ describe('Edit Stream', () => {
mockGetClusters([cluster1, cluster2, cluster3, cluster4]);

// Visit the Edit Stream page
cy.visitWithLogin(
`/logs/delivery/streams/${mockLKEAuditLogsStream.id}/edit/`
);
cy.visitWithLogin(`/logs/delivery/streams/${mockLKEAuditLogsStream.id}`);

const updatedLabel = randomLabel();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function editStreamViaActionMenu(tableAlias: string, stream: Stream) {
mockGetStream(stream);
// Edit stream redirect
ui.actionMenuItem.findByTitle('Edit').click();
cy.url().should('endWith', `/streams/${stream.id}/edit`);
cy.url().should('endWith', `/streams/${stream.id}/summary`);
});
}

Expand Down Expand Up @@ -178,7 +178,7 @@ describe('Streams non-empty landing page', () => {

// Redirect to stream edit page via name
cy.findByText(exampleStream.label).click();
cy.url().should('endWith', `/streams/${exampleStream.id}/edit`);
cy.url().should('endWith', `/streams/${exampleStream.id}/summary`);
cy.wait(['@getStream', '@getDestinations']);

// Redirect to stream edit page via menu item
Expand Down
4 changes: 4 additions & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ interface AclpLogsFlag extends BetaFeatureFlag {
* This property indicates whether to show Custom HTTPS destination type
*/
customHttpsEnabled?: boolean;
/**
* This property indicates whether to show the "Metrics" tab on Logs Stream details page or not
*/
metricsEnabled?: boolean;
/**
* This property indicates whether the feature is new or not
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,14 @@ describe('DestinationEdit', () => {
});
});

describe('given Test Connection and Edit Destination buttons', () => {
describe('given Test Connection and Save Changes buttons', () => {
const testConnectionButtonText = 'Test Connection';
const saveDestinationButtonText = 'Save Changes';
const editDestinationSpy = vi.fn();
const verifyDestinationSpy = vi.fn();

describe('when Test Connection button clicked and connection verified positively', () => {
it("should enable Edit Destination button and perform proper call when it's clicked", async () => {
it("should enable Save Changes button and perform proper call when it's clicked", async () => {
server.use(
http.get(`*/monitor/streams/destinations/${destinationId}`, () => {
return HttpResponse.json(mockDestination);
Expand Down Expand Up @@ -267,7 +267,7 @@ describe('DestinationEdit', () => {
});

describe('when Test Connection button clicked and connection verified negatively', () => {
it('should not enable Edit Destination button', async () => {
it('should not enable Save Changes button', async () => {
server.use(
http.get(`*/monitor/streams/destinations/${destinationId}`, () => {
return HttpResponse.json(mockDestination);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type { DestinationFormType } from 'src/features/Delivery/Shared/types';
export const DestinationEdit = () => {
const navigate = useNavigate();
const { destinationId } = useParams({
from: '/logs/delivery/destinations/$destinationId/edit',
from: '/logs/delivery/destinations/$destinationId/summary',
});
const { mutateAsync: updateDestination, isPending: isUpdatingDestination } =
useUpdateDestinationMutation();
Expand All @@ -37,7 +37,7 @@ export const DestinationEdit = () => {

const landingHeaderProps: LandingHeaderProps = {
breadcrumbProps: {
pathname: '/logs/delivery/destinations/edit',
pathname: '/logs/delivery/destinations/summary',
crumbOverrides: [
{
label: 'Delivery',
Expand All @@ -48,7 +48,7 @@ export const DestinationEdit = () => {
},
docsLink: 'https://techdocs.akamai.com/cloud-computing/docs/log-delivery',
removeCrumbX: [1, 2],
title: `Edit Destination ${destinationId}`,
title: `Destination ${destinationId}`,
};

const form = useForm<DestinationFormType>({
Expand Down Expand Up @@ -114,7 +114,7 @@ export const DestinationEdit = () => {

return (
<>
<DocumentTitleSegment segment="Edit Destination" />
<DocumentTitleSegment segment="Destination" />
<LandingHeader {...landingHeaderProps} />
{isLoading && (
<Box display="flex" justifyContent="center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createLazyRoute } from '@tanstack/react-router';
import { DestinationEdit } from 'src/features/Delivery/Destinations/DestinationForm/DestinationEdit';

export const destinationEditLazyRoute = createLazyRoute(
'/logs/delivery/destinations/$destinationId/edit'
'/logs/delivery/destinations/$destinationId/summary'
)({
component: DestinationEdit,
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const DestinationTableRow = React.memo(
<TableCell>
<LinkWithTooltipAndEllipsis
pendoId="Logs Delivery Destinations-Name"
to={`/logs/delivery/destinations/${id}/edit`}
to={`/logs/delivery/destinations/${id}/summary`}
>
{destination.label}
</LinkWithTooltipAndEllipsis>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ describe('Destinations Landing Table', () => {
await clickOnActionMenuItem('Edit');

expect(mockNavigate).toHaveBeenCalledWith({
to: '/logs/delivery/destinations/1/edit',
to: '/logs/delivery/destinations/1/summary',
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const DestinationsLanding = () => {
}

const handleEdit = ({ id }: Destination) => {
navigate({ to: `/logs/delivery/destinations/${id}/edit` });
navigate({ to: `/logs/delivery/destinations/${id}/summary` });
};

const openDeleteDialog = (destination: Destination) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { screen } from '@testing-library/react';
import * as React from 'react';
import { beforeEach, describe, it } from 'vitest';

import {
akamaiObjectStorageDestinationFactory,
streamFactory,
} from 'src/factories';
import { StreamLanding } from 'src/features/Delivery/Streams/Stream/StreamLanding';
import { http, HttpResponse, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import type { Flags } from 'src/featureFlags';

const streamId = 123;
const mockDestinations = [
akamaiObjectStorageDestinationFactory.build({ id: 1 }),
];
const mockStream = streamFactory.build({
id: streamId,
label: `Stream ${streamId}`,
destinations: mockDestinations,
});

describe('StreamLanding', () => {
const renderComponent = (flags: Partial<Flags>) => {
renderWithTheme(<StreamLanding />, {
flags,
initialRoute: '/logs/delivery/streams/$streamId/summary',
});
};

beforeEach(async () => {
server.use(
http.get(`*/monitor/streams/${streamId}`, () => {
return HttpResponse.json(mockStream);
})
);
});

describe('and metrics are not enabled', () => {
const flags = {
aclpLogs: {
enabled: true,
beta: false,
metricsEnabled: false,
},
};

it('should render the summary and not the metrics tab', async () => {
renderComponent(flags);

screen.getByText('Summary');
expect(screen.queryByText('Metrics')).not.toBeInTheDocument();
});
});

describe('and metrics are enabled', () => {
const flags = {
aclpLogs: {
enabled: true,
beta: false,
metricsEnabled: true,
},
};

it('should render the summary tab and metrics tab', async () => {
renderComponent(flags);

screen.getByText('Summary');
expect(screen.queryByText('Metrics')).toBeInTheDocument();

Check warning on line 71 in packages/manager/src/features/Delivery/Streams/Stream/StreamLanding.test.tsx

View workflow job for this annotation

GitHub Actions / ESLint Review (manager)

[eslint] reported by reviewdog 🐢 Use `getBy*` queries rather than `queryBy*` for checking element is present Raw Output: {"ruleId":"testing-library/prefer-presence-queries","severity":1,"message":"Use `getBy*` queries rather than `queryBy*` for checking element is present","line":71,"column":21,"nodeType":"Identifier","messageId":"wrongPresenceQuery","endLine":71,"endColumn":32}
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useParams } from '@tanstack/react-router';
import * as React from 'react';
import { useMemo } from 'react';

import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import {
LandingHeader,
type LandingHeaderProps,
} from 'src/components/LandingHeader';
import { SuspenseLoader } from 'src/components/SuspenseLoader';
import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel';
import { TabPanels } from 'src/components/Tabs/TabPanels';
import { Tabs } from 'src/components/Tabs/Tabs';
import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList';
import { useIsACLPLogsEnabled } from 'src/features/Delivery/deliveryUtils';
import { StreamMetrics } from 'src/features/Delivery/Streams/Stream/StreamMetrics';
import { StreamEdit } from 'src/features/Delivery/Streams/StreamForm/StreamEdit';
import { useTabs } from 'src/hooks/useTabs';

import type { Tab } from 'src/hooks/useTabs';

export const StreamLanding = () => {
const { streamId } = useParams({
strict: false,
});
const { isACLPLogsMetricsEnabled } = useIsACLPLogsEnabled();

const activeTabs = useMemo(() => {
const result: Tab[] = [
{
title: 'Summary',
to: `/logs/delivery/streams/$streamId/summary`,
},
];

if (isACLPLogsMetricsEnabled) {
result.push({
title: 'Metrics',
to: `/logs/delivery/streams/$streamId/metrics`,
});
}

return result;
}, [isACLPLogsMetricsEnabled]);

const { handleTabChange, tabIndex, tabs } = useTabs(activeTabs);

const landingHeaderProps: LandingHeaderProps = {
breadcrumbProps: {
pathname: '/logs/delivery/streams/summary',
crumbOverrides: [
{
label: 'Delivery',
linkTo: '/logs/delivery/streams',
position: 1,
},
],
},
docsLink: 'https://techdocs.akamai.com/cloud-computing/docs/log-delivery',
removeCrumbX: [1, 2],
title: `Stream ${streamId}`,
};

return (
<>
<DocumentTitleSegment segment="Stream" />
<LandingHeader {...landingHeaderProps} />
<Tabs index={tabIndex} onChange={handleTabChange}>
<TanStackTabLinkList tabs={tabs} />
<React.Suspense fallback={<SuspenseLoader />}>
<TabPanels>
<SafeTabPanel index={0}>
<StreamEdit />
</SafeTabPanel>
<SafeTabPanel index={1}>
<StreamMetrics />
</SafeTabPanel>
</TabPanels>
</React.Suspense>
</Tabs>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useParams } from '@tanstack/react-router';
import * as React from 'react';

import { CloudPulseDashboardWithFilters } from 'src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters';

export const StreamMetrics = () => {
const { streamId } = useParams({
from: '/logs/delivery/streams/$streamId/metrics',
});

return (
<CloudPulseDashboardWithFilters resource={streamId} serviceType="logs" />
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createLazyRoute } from '@tanstack/react-router';

import { StreamLanding } from 'src/features/Delivery/Streams/Stream/StreamLanding';

export const streamLandingLazyRoute = createLazyRoute(
'/logs/delivery/streams/$streamId'
)({
component: StreamLanding,
});
Loading
Loading