Skip to content

Commit 58bee40

Browse files
authored
upcoming: [DPS-35926] - Custom HTTPS form - Edit and selected destination summary (#13380)
1 parent b8f3057 commit 58bee40

File tree

15 files changed

+408
-101
lines changed

15 files changed

+408
-101
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/api-v4": Changed
3+
---
4+
5+
Delivery Logs - adjust DestinationDetailsPayload type for Custom HTTPS destinations ([#13380](https://github.com/linode/manager/pull/13380))

packages/api-v4/src/delivery/types.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ export interface CustomHTTPSDetails {
9696
endpoint_url: string;
9797
}
9898

99+
export interface CustomHTTPSDetailsExtended extends CustomHTTPSDetails {
100+
authentication: Authentication & {
101+
details?: AuthenticationDetailsExtended;
102+
};
103+
}
104+
99105
interface ClientCertificateDetails {
100106
client_ca_certificate?: string;
101107
client_certificate?: string;
@@ -117,10 +123,13 @@ interface Authentication {
117123
}
118124

119125
interface AuthenticationDetails {
120-
basic_authentication_password: string;
121126
basic_authentication_user: string;
122127
}
123128

129+
interface AuthenticationDetailsExtended extends AuthenticationDetails {
130+
basic_authentication_password: string;
131+
}
132+
124133
export interface CustomHeader {
125134
name: string;
126135
value: string;
@@ -152,7 +161,7 @@ export interface AkamaiObjectStorageDetailsPayload
152161

153162
export type DestinationDetailsPayload =
154163
| AkamaiObjectStorageDetailsPayload
155-
| CustomHTTPSDetails;
164+
| CustomHTTPSDetailsExtended;
156165

157166
export interface CreateDestinationPayload {
158167
details: DestinationDetailsPayload;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
Delivery Logs - selected destination summary in a Create Stream form for Custom HTTPS destinations, edit Custom HTTPS destination ([#13380](https://github.com/linode/manager/pull/13380))
Lines changed: 78 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,108 @@
11
import { Box, Tooltip, Typography } from '@linode/ui';
22
import { styled, useTheme } from '@mui/material/styles';
3-
import useMediaQuery from '@mui/material/useMediaQuery';
43
import * as React from 'react';
4+
import { useEffect, useRef, useState } from 'react';
5+
6+
const maxWidth = 416;
7+
const labelWidth = 160;
8+
const valueWidth = maxWidth - labelWidth;
59

610
interface LabelValueProps {
7-
children?: React.ReactNode;
8-
compact?: boolean;
911
'data-testid'?: string;
1012
label: string;
11-
smHideTooltip?: boolean;
1213
value: string;
1314
}
14-
1515
export const LabelValue = (props: LabelValueProps) => {
16-
const {
17-
compact = false,
18-
label,
19-
value,
20-
'data-testid': dataTestId,
21-
children,
22-
smHideTooltip,
23-
} = props;
16+
const { label, value, 'data-testid': dataTestId } = props;
2417
const theme = useTheme();
25-
const matchesSmDown = useMediaQuery(theme.breakpoints.down('sm'));
18+
const labelRef = useRef<HTMLDivElement>(null);
19+
const [isLabelOverflowing, setIsLabelOverflowing] = useState(false);
20+
const valueRef = useRef<HTMLDivElement>(null);
21+
const [isValueOverflowing, setIsValueOverflowing] = useState(false);
22+
23+
useEffect(() => {
24+
const checkLabelOverflow = () => {
25+
if (labelRef.current) {
26+
setIsLabelOverflowing(
27+
labelRef.current.scrollWidth > labelRef.current.clientWidth
28+
);
29+
}
30+
};
31+
const checkValueOverflow = () => {
32+
if (valueRef.current) {
33+
setIsValueOverflowing(
34+
valueRef.current.scrollWidth > valueRef.current.clientWidth
35+
);
36+
}
37+
};
38+
checkLabelOverflow();
39+
checkValueOverflow();
40+
});
2641

2742
return (
2843
<Box
2944
alignItems="center"
3045
display="flex"
46+
justifyContent="space-between"
3147
marginTop={theme.spacingFunction(16)}
48+
width={maxWidth}
3249
>
33-
<Typography
34-
sx={{
35-
mr: 1,
36-
font: theme.font.bold,
37-
width: compact ? 'auto' : 160,
38-
}}
39-
>
40-
{label}:
41-
</Typography>
42-
<StyledValue
43-
data-testid={dataTestId}
44-
title={!smHideTooltip && matchesSmDown ? value : undefined}
45-
>
46-
<Typography>{value}</Typography>
47-
</StyledValue>
48-
{children}
50+
<StyledLabel title={isLabelOverflowing ? label : undefined}>
51+
<Box alignItems="center" display="flex" maxWidth={labelWidth}>
52+
<Typography
53+
ref={labelRef}
54+
sx={{
55+
font: theme.font.bold,
56+
overflow: 'hidden',
57+
textOverflow: 'ellipsis',
58+
whiteSpace: 'nowrap',
59+
}}
60+
>
61+
{label}
62+
</Typography>
63+
<Typography sx={{ font: theme.font.bold, flexShrink: 0 }}>
64+
:
65+
</Typography>
66+
</Box>
67+
</StyledLabel>
68+
<Box width={valueWidth}>
69+
<StyledValue
70+
data-testid={dataTestId}
71+
title={isValueOverflowing ? value : undefined}
72+
>
73+
<Typography
74+
ref={valueRef}
75+
sx={{
76+
overflow: 'hidden',
77+
textOverflow: 'ellipsis',
78+
whiteSpace: 'nowrap',
79+
width: 'fit-content',
80+
}}
81+
>
82+
{value}
83+
</Typography>
84+
</StyledValue>
85+
</Box>
4986
</Box>
5087
);
5188
};
5289

5390
const StyledValue = styled(Tooltip, {
5491
label: 'StyledValue',
5592
})(({ theme }) => ({
56-
alignItems: 'center',
5793
backgroundColor: theme.tokens.alias.Interaction.Background.Disabled,
5894
border: `1px solid ${theme.tokens.alias.Border.Neutral}`,
5995
borderRadius: 4,
60-
display: 'flex',
6196
height: theme.spacingFunction(24),
62-
padding: theme.spacingFunction(4, 8),
63-
[theme.breakpoints.down('sm')]: {
64-
display: 'block',
65-
maxWidth: '174px',
66-
textOverflow: 'ellipsis',
67-
overflow: 'hidden',
68-
whiteSpace: 'nowrap',
69-
padding: theme.spacingFunction(1, 8),
70-
},
97+
lineHeight: theme.spacingFunction(24),
98+
maxWidth: valueWidth,
99+
padding: theme.spacingFunction(1, 8),
100+
}));
101+
102+
const StyledLabel = styled(Tooltip, {
103+
label: 'StyledLabel',
104+
})(({ theme }) => ({
105+
height: theme.spacingFunction(24),
106+
lineHeight: theme.spacingFunction(24),
107+
maxWidth: labelWidth,
71108
}));

packages/manager/src/features/Delivery/Shared/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import type {
1010
AkamaiObjectStorageDetailsExtended,
1111
CreateDestinationPayload,
12-
CustomHTTPSDetails,
12+
CustomHTTPSDetailsExtended,
1313
} from '@linode/api-v4';
1414

1515
export type FormMode = 'create' | 'edit';
@@ -85,7 +85,7 @@ export const contentTypeOptions: AutocompleteOption[] = [
8585

8686
export type DestinationDetailsForm =
8787
| AkamaiObjectStorageDetailsExtended
88-
| CustomHTTPSDetails;
88+
| CustomHTTPSDetailsExtended;
8989

9090
export interface DestinationForm
9191
extends Omit<CreateDestinationPayload, 'details'> {

packages/manager/src/features/Delivery/Streams/StreamForm/Delivery/DestinationAkamaiObjectStorageDetailsSummary.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ export const DestinationAkamaiObjectStorageDetailsSummary = (
1616
<LabelValue
1717
data-testid="access-key-id"
1818
label="Access Key ID"
19-
smHideTooltip={true}
2019
value="*****************"
2120
/>
2221
<LabelValue
2322
data-testid="secret-access-key"
2423
label="Secret Access Key"
25-
smHideTooltip={true}
2624
value="*****************"
2725
/>
2826
{!!path && <LabelValue label="Log Path" value={path} />}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { dataCompressionType } from '@linode/api-v4';
2+
import { screen } from '@testing-library/react';
3+
import React from 'react';
4+
import { expect } from 'vitest';
5+
6+
import { renderWithTheme } from 'src/utilities/testHelpers';
7+
8+
import { DestinationCustomHTTPSDetailsSummary } from './DestinationCustomHTTPSDetailsSummary';
9+
10+
import type { CustomHTTPSDetails } from '@linode/api-v4';
11+
12+
describe('DestinationCustomHTTPSDetailsSummary', () => {
13+
it('renders basic authentication details correctly', () => {
14+
const details: CustomHTTPSDetails = {
15+
authentication: {
16+
type: 'basic',
17+
details: {
18+
basic_authentication_user: 'testuser',
19+
},
20+
},
21+
endpoint_url: 'https://example.com/',
22+
data_compression: dataCompressionType.Gzip,
23+
};
24+
25+
renderWithTheme(<DestinationCustomHTTPSDetailsSummary {...details} />);
26+
27+
// Authentication:
28+
expect(screen.getByText('basic')).toBeVisible();
29+
// Endpoint URL:
30+
expect(screen.getByText('https://example.com/')).toBeVisible();
31+
// Username:
32+
expect(screen.getByText('testuser')).toBeVisible();
33+
// Password:
34+
expect(screen.getByTestId('password')).toHaveTextContent(
35+
'*****************'
36+
);
37+
});
38+
39+
it('renders none authentication without username and password', () => {
40+
const details: CustomHTTPSDetails = {
41+
authentication: {
42+
type: 'none',
43+
},
44+
endpoint_url: 'https://example.com/',
45+
data_compression: dataCompressionType.Gzip,
46+
};
47+
48+
renderWithTheme(<DestinationCustomHTTPSDetailsSummary {...details} />);
49+
50+
// Authentication:
51+
expect(screen.getByText('none')).toBeVisible();
52+
// Endpoint URL:
53+
expect(screen.getByText('https://example.com/')).toBeVisible();
54+
// Username:
55+
expect(screen.queryByText('Username')).not.toBeInTheDocument();
56+
// Password:
57+
expect(screen.queryByTestId('password')).not.toBeInTheDocument();
58+
});
59+
60+
it('renders client certificate details when provided', () => {
61+
const details: CustomHTTPSDetails = {
62+
authentication: { type: 'none' },
63+
endpoint_url: 'https://example.com/',
64+
client_certificate_details: {
65+
tls_hostname: 'tls.example.com',
66+
client_ca_certificate: 'ca-cert-content',
67+
client_certificate: 'client-cert-content',
68+
client_private_key: 'private-key-content',
69+
},
70+
data_compression: dataCompressionType.Gzip,
71+
};
72+
73+
renderWithTheme(<DestinationCustomHTTPSDetailsSummary {...details} />);
74+
75+
expect(screen.getByText('Additional Options')).toBeVisible();
76+
expect(screen.queryByTestId('client-certificate-header')).toBeVisible();
77+
// TLS Hostname:
78+
expect(screen.getByText('tls.example.com')).toBeVisible();
79+
// CA Certificate:
80+
expect(screen.getByText('ca-cert-content')).toBeVisible();
81+
// Client Certificate:
82+
expect(screen.getByText('client-cert-content')).toBeVisible();
83+
// Client Key:
84+
expect(screen.getByText('private-key-content')).toBeVisible();
85+
});
86+
87+
it('renders content type when provided', () => {
88+
const details: CustomHTTPSDetails = {
89+
authentication: { type: 'none' },
90+
endpoint_url: 'https://example.com/',
91+
content_type: 'application/json',
92+
data_compression: dataCompressionType.Gzip,
93+
};
94+
95+
renderWithTheme(<DestinationCustomHTTPSDetailsSummary {...details} />);
96+
97+
expect(screen.getByText('HTTPS Headers')).toBeVisible();
98+
expect(screen.getByText('application/json')).toBeVisible();
99+
});
100+
101+
it('renders custom headers when provided', () => {
102+
const details: CustomHTTPSDetails = {
103+
authentication: { type: 'none' },
104+
endpoint_url: 'https://example.com/',
105+
custom_headers: [
106+
{ name: 'X-Custom-Header', value: 'custom-value' },
107+
{ name: 'Authorization', value: 'Bearer token123' },
108+
],
109+
data_compression: dataCompressionType.Gzip,
110+
};
111+
112+
renderWithTheme(<DestinationCustomHTTPSDetailsSummary {...details} />);
113+
114+
expect(screen.getByText('HTTPS Headers')).toBeVisible();
115+
// Custom Header 1:
116+
expect(screen.getByText('X-Custom-Header')).toBeVisible();
117+
expect(screen.getByText('custom-value')).toBeVisible();
118+
// Custom Header 2:
119+
expect(screen.getByText('Authorization')).toBeVisible();
120+
expect(screen.getByText('Bearer token123')).toBeVisible();
121+
});
122+
123+
it('does not render Additional Options section when no optional fields provided', () => {
124+
const details: CustomHTTPSDetails = {
125+
authentication: { type: 'none' },
126+
endpoint_url: 'https://example.com/',
127+
data_compression: dataCompressionType.Gzip,
128+
};
129+
130+
renderWithTheme(<DestinationCustomHTTPSDetailsSummary {...details} />);
131+
132+
expect(screen.queryByText('Additional Options')).not.toBeInTheDocument();
133+
expect(screen.queryByText('HTTPS Headers')).not.toBeInTheDocument();
134+
});
135+
});

0 commit comments

Comments
 (0)