Merge branch 'master' into ISSUE_754_acl
This commit is contained in:
commit
27fede1d15
21 changed files with 170 additions and 293 deletions
|
@ -123,11 +123,11 @@ public class ConsumerRecordDeserializer {
|
|||
}
|
||||
|
||||
private static Long getKeySize(ConsumerRecord<Bytes, Bytes> consumerRecord) {
|
||||
return consumerRecord.key() != null ? (long) consumerRecord.key().get().length : null;
|
||||
return consumerRecord.key() != null ? (long) consumerRecord.serializedKeySize() : null;
|
||||
}
|
||||
|
||||
private static Long getValueSize(ConsumerRecord<Bytes, Bytes> consumerRecord) {
|
||||
return consumerRecord.value() != null ? (long) consumerRecord.value().get().length : null;
|
||||
return consumerRecord.value() != null ? (long) consumerRecord.serializedValueSize() : null;
|
||||
}
|
||||
|
||||
private static int headerSize(Header header) {
|
||||
|
|
|
@ -122,8 +122,6 @@ public class SerdesInitializer {
|
|||
registeredSerdes,
|
||||
Optional.ofNullable(clusterProperties.getDefaultKeySerde())
|
||||
.map(name -> Preconditions.checkNotNull(registeredSerdes.get(name), "Default key serde not found"))
|
||||
.or(() -> Optional.ofNullable(registeredSerdes.get(SchemaRegistrySerde.name())))
|
||||
.or(() -> Optional.ofNullable(registeredSerdes.get(ProtobufFileSerde.name())))
|
||||
.orElse(null),
|
||||
Optional.ofNullable(clusterProperties.getDefaultValueSerde())
|
||||
.map(name -> Preconditions.checkNotNull(registeredSerdes.get(name), "Default value serde not found"))
|
||||
|
|
|
@ -109,6 +109,7 @@ public class KafkaConnectService {
|
|||
private Stream<String> getStringsForSearch(FullConnectorInfoDTO fullConnectorInfo) {
|
||||
return Stream.of(
|
||||
fullConnectorInfo.getName(),
|
||||
fullConnectorInfo.getConnect(),
|
||||
fullConnectorInfo.getStatus().getState().getValue(),
|
||||
fullConnectorInfo.getType().getValue());
|
||||
}
|
||||
|
|
|
@ -43,8 +43,7 @@ class TopicAnalysisStats {
|
|||
Long max;
|
||||
final UpdateDoublesSketch sizeSketch = DoublesSketch.builder().build();
|
||||
|
||||
void apply(byte[] bytes) {
|
||||
int len = bytes.length;
|
||||
void apply(int len) {
|
||||
sum += len;
|
||||
min = minNullable(min, len);
|
||||
max = maxNullable(max, len);
|
||||
|
@ -98,7 +97,7 @@ class TopicAnalysisStats {
|
|||
|
||||
if (rec.key() != null) {
|
||||
byte[] keyBytes = rec.key().get();
|
||||
keysSize.apply(keyBytes);
|
||||
keysSize.apply(rec.serializedKeySize());
|
||||
uniqKeys.update(keyBytes);
|
||||
} else {
|
||||
nullKeys++;
|
||||
|
@ -106,7 +105,7 @@ class TopicAnalysisStats {
|
|||
|
||||
if (rec.value() != null) {
|
||||
byte[] valueBytes = rec.value().get();
|
||||
valuesSize.apply(valueBytes);
|
||||
valuesSize.apply(rec.serializedValueSize());
|
||||
uniqValues.update(valueBytes);
|
||||
} else {
|
||||
nullValues++;
|
||||
|
|
|
@ -142,6 +142,8 @@ const Message: React.FC<Props> = ({
|
|||
timestampType={timestampType}
|
||||
keySize={keySize}
|
||||
contentSize={valueSize}
|
||||
keySerde={keySerde}
|
||||
valueSerde={valueSerde}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -3,7 +3,6 @@ import EditorViewer from 'components/common/EditorViewer/EditorViewer';
|
|||
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
|
||||
import { SchemaType, TopicMessageTimestampTypeEnum } from 'generated-sources';
|
||||
import { formatTimestamp } from 'lib/dateTimeHelpers';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import * as S from './MessageContent.styled';
|
||||
|
||||
|
@ -17,6 +16,8 @@ export interface MessageContentProps {
|
|||
timestampType?: TopicMessageTimestampTypeEnum;
|
||||
keySize?: number;
|
||||
contentSize?: number;
|
||||
keySerde?: string;
|
||||
valueSerde?: string;
|
||||
}
|
||||
|
||||
const MessageContent: React.FC<MessageContentProps> = ({
|
||||
|
@ -27,12 +28,10 @@ const MessageContent: React.FC<MessageContentProps> = ({
|
|||
timestampType,
|
||||
keySize,
|
||||
contentSize,
|
||||
keySerde,
|
||||
valueSerde,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = React.useState<Tab>('content');
|
||||
const [searchParams] = useSearchParams();
|
||||
const keyFormat = searchParams.get('keySerde') || '';
|
||||
const valueFormat = searchParams.get('valueSerde') || '';
|
||||
|
||||
const activeTabContent = () => {
|
||||
switch (activeTab) {
|
||||
case 'content':
|
||||
|
@ -110,7 +109,7 @@ const MessageContent: React.FC<MessageContentProps> = ({
|
|||
<S.Metadata>
|
||||
<S.MetadataLabel>Key Serde</S.MetadataLabel>
|
||||
<span>
|
||||
<S.MetadataValue>{keyFormat}</S.MetadataValue>
|
||||
<S.MetadataValue>{keySerde}</S.MetadataValue>
|
||||
<S.MetadataMeta>
|
||||
Size: <BytesFormatted value={keySize} />
|
||||
</S.MetadataMeta>
|
||||
|
@ -120,7 +119,7 @@ const MessageContent: React.FC<MessageContentProps> = ({
|
|||
<S.Metadata>
|
||||
<S.MetadataLabel>Value Serde</S.MetadataLabel>
|
||||
<span>
|
||||
<S.MetadataValue>{valueFormat}</S.MetadataValue>
|
||||
<S.MetadataValue>{valueSerde}</S.MetadataValue>
|
||||
<S.MetadataMeta>
|
||||
Size: <BytesFormatted value={contentSize} />
|
||||
</S.MetadataMeta>
|
||||
|
|
|
@ -20,6 +20,8 @@ const setupWrapper = (props?: Partial<MessageContentProps>) => {
|
|||
headers={{ header: 'test' }}
|
||||
timestamp={new Date(0)}
|
||||
timestampType={TopicMessageTimestampTypeEnum.CREATE_TIME}
|
||||
keySerde="SchemaRegistry"
|
||||
valueSerde="Avro"
|
||||
{...props}
|
||||
/>
|
||||
</tbody>
|
||||
|
@ -27,42 +29,20 @@ const setupWrapper = (props?: Partial<MessageContentProps>) => {
|
|||
);
|
||||
};
|
||||
|
||||
const proto =
|
||||
'syntax = "proto3";\npackage com.provectus;\n\nmessage TestProtoRecord {\n string f1 = 1;\n int32 f2 = 2;\n}\n';
|
||||
|
||||
global.TextEncoder = TextEncoder;
|
||||
|
||||
const searchParamsContentAVRO = new URLSearchParams({
|
||||
keySerde: 'SchemaRegistry',
|
||||
valueSerde: 'AVRO',
|
||||
limit: '100',
|
||||
});
|
||||
|
||||
const searchParamsContentJSON = new URLSearchParams({
|
||||
keySerde: 'SchemaRegistry',
|
||||
valueSerde: 'JSON',
|
||||
limit: '100',
|
||||
});
|
||||
|
||||
const searchParamsContentPROTOBUF = new URLSearchParams({
|
||||
keySerde: 'SchemaRegistry',
|
||||
valueSerde: 'PROTOBUF',
|
||||
limit: '100',
|
||||
});
|
||||
describe('MessageContent screen', () => {
|
||||
beforeEach(() => {
|
||||
render(setupWrapper(), {
|
||||
initialEntries: [`/messages?${searchParamsContentAVRO}`],
|
||||
});
|
||||
render(setupWrapper());
|
||||
});
|
||||
|
||||
describe('renders', () => {
|
||||
it('key format in document', () => {
|
||||
describe('Checking keySerde and valueSerde', () => {
|
||||
it('keySerde in document', () => {
|
||||
expect(screen.getByText('SchemaRegistry')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('content format in document', () => {
|
||||
expect(screen.getByText('AVRO')).toBeInTheDocument();
|
||||
it('valueSerde in document', () => {
|
||||
expect(screen.getByText('Avro')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -98,42 +78,3 @@ describe('MessageContent screen', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checking content type depend on message type', () => {
|
||||
it('renders component with message having JSON type', () => {
|
||||
render(
|
||||
setupWrapper({
|
||||
messageContent: '{"data": "test"}',
|
||||
}),
|
||||
{ initialEntries: [`/messages?${searchParamsContentJSON}`] }
|
||||
);
|
||||
expect(screen.getByText('JSON')).toBeInTheDocument();
|
||||
});
|
||||
it('renders component with message having AVRO type', () => {
|
||||
render(
|
||||
setupWrapper({
|
||||
messageContent: '{"data": "test"}',
|
||||
}),
|
||||
{ initialEntries: [`/messages?${searchParamsContentAVRO}`] }
|
||||
);
|
||||
expect(screen.getByText('AVRO')).toBeInTheDocument();
|
||||
});
|
||||
it('renders component with message having PROTOBUF type', () => {
|
||||
render(
|
||||
setupWrapper({
|
||||
messageContent: proto,
|
||||
}),
|
||||
{ initialEntries: [`/messages?${searchParamsContentPROTOBUF}`] }
|
||||
);
|
||||
expect(screen.getByText('PROTOBUF')).toBeInTheDocument();
|
||||
});
|
||||
it('renders component with message having no type which is equal to having PROTOBUF type', () => {
|
||||
render(
|
||||
setupWrapper({
|
||||
messageContent: '',
|
||||
}),
|
||||
{ initialEntries: [`/messages?${searchParamsContentPROTOBUF}`] }
|
||||
);
|
||||
expect(screen.getByText('PROTOBUF')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,15 +8,29 @@ export const Wrapper = styled.div`
|
|||
export const Columns = styled.div`
|
||||
margin: -0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.75rem;
|
||||
gap: 8px;
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Column = styled.div`
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
padding: 0.75rem;
|
||||
export const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
@media screen and (max-width: 1200px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
export const FlexItem = styled.div`
|
||||
width: 18rem;
|
||||
@media screen and (max-width: 1450px) {
|
||||
width: 50%;
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { RouteParamsClusterTopic } from 'lib/paths';
|
|||
import { Button } from 'components/common/Button/Button';
|
||||
import Editor from 'components/common/Editor/Editor';
|
||||
import Select, { SelectOption } from 'components/common/Select/Select';
|
||||
import Switch from 'components/common/Switch/Switch';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { showAlert } from 'lib/errorHandling';
|
||||
import { useSendMessage, useTopicDetails } from 'lib/hooks/api/topics';
|
||||
|
@ -26,9 +27,12 @@ interface FormType {
|
|||
partition: number;
|
||||
keySerde: string;
|
||||
valueSerde: string;
|
||||
keepContents: boolean;
|
||||
}
|
||||
|
||||
const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
||||
const SendMessage: React.FC<{ closeSidebar: () => void }> = ({
|
||||
closeSidebar,
|
||||
}) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
const { data: topic } = useTopicDetails({ clusterName, topicName });
|
||||
const { data: serdes = {} } = useSerdes({
|
||||
|
@ -47,11 +51,13 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
control,
|
||||
setValue,
|
||||
} = useForm<FormType>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
...defaultValues,
|
||||
partition: Number(partitionOptions[0].value),
|
||||
keepContents: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -62,6 +68,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
content,
|
||||
headers,
|
||||
partition,
|
||||
keepContents,
|
||||
}: FormType) => {
|
||||
let errors: string[] = [];
|
||||
|
||||
|
@ -110,7 +117,11 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
keySerde,
|
||||
valueSerde,
|
||||
});
|
||||
onSubmit();
|
||||
if (!keepContents) {
|
||||
setValue('key', '');
|
||||
setValue('content', '');
|
||||
closeSidebar();
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -120,7 +131,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
<S.Wrapper>
|
||||
<form onSubmit={handleSubmit(submit)}>
|
||||
<S.Columns>
|
||||
<S.Column>
|
||||
<S.FlexItem>
|
||||
<InputLabel>Partition</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -137,47 +148,58 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
</S.Column>
|
||||
<S.Column>
|
||||
<InputLabel>Key Serde</InputLabel>
|
||||
</S.FlexItem>
|
||||
<S.Flex>
|
||||
<S.FlexItem>
|
||||
<InputLabel>Key Serde</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name="keySerde"
|
||||
render={({ field: { name, onChange, value } }) => (
|
||||
<Select
|
||||
id="selectKeySerdeOptions"
|
||||
aria-labelledby="selectKeySerdeOptions"
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
minWidth="100%"
|
||||
options={getSerdeOptions(serdes.key || [])}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</S.FlexItem>
|
||||
<S.FlexItem>
|
||||
<InputLabel>Value Serde</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name="valueSerde"
|
||||
render={({ field: { name, onChange, value } }) => (
|
||||
<Select
|
||||
id="selectValueSerdeOptions"
|
||||
aria-labelledby="selectValueSerdeOptions"
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
minWidth="100%"
|
||||
options={getSerdeOptions(serdes.value || [])}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</S.FlexItem>
|
||||
</S.Flex>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="keySerde"
|
||||
name="keepContents"
|
||||
render={({ field: { name, onChange, value } }) => (
|
||||
<Select
|
||||
id="selectKeySerdeOptions"
|
||||
aria-labelledby="selectKeySerdeOptions"
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
minWidth="100%"
|
||||
options={getSerdeOptions(serdes.key || [])}
|
||||
value={value}
|
||||
/>
|
||||
<Switch name={name} onChange={onChange} checked={value} />
|
||||
)}
|
||||
/>
|
||||
</S.Column>
|
||||
<S.Column>
|
||||
<InputLabel>Value Serde</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name="valueSerde"
|
||||
render={({ field: { name, onChange, value } }) => (
|
||||
<Select
|
||||
id="selectValueSerdeOptions"
|
||||
aria-labelledby="selectValueSerdeOptions"
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
minWidth="100%"
|
||||
options={getSerdeOptions(serdes.value || [])}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</S.Column>
|
||||
<InputLabel>Keep contents</InputLabel>
|
||||
</div>
|
||||
</S.Columns>
|
||||
|
||||
<S.Columns>
|
||||
<S.Column>
|
||||
<div>
|
||||
<InputLabel>Key</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -191,8 +213,8 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
</S.Column>
|
||||
<S.Column>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel>Value</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -206,10 +228,10 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
</S.Column>
|
||||
</div>
|
||||
</S.Columns>
|
||||
<S.Columns>
|
||||
<S.Column>
|
||||
<div>
|
||||
<InputLabel>Headers</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -224,7 +246,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
</S.Column>
|
||||
</div>
|
||||
</S.Columns>
|
||||
<Button
|
||||
buttonSize="M"
|
||||
|
|
|
@ -49,7 +49,7 @@ const renderComponent = async () => {
|
|||
const path = clusterTopicPath(clusterName, topicName);
|
||||
await render(
|
||||
<WithRoute path={clusterTopicPath()}>
|
||||
<SendMessage onSubmit={mockOnSubmit} />
|
||||
<SendMessage closeSidebar={mockOnSubmit} />
|
||||
</WithRoute>,
|
||||
{ initialEntries: [path] }
|
||||
);
|
||||
|
|
|
@ -236,7 +236,7 @@ const Topic: React.FC = () => {
|
|||
title="Produce Message"
|
||||
>
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<SendMessage onSubmit={closeSidebar} />
|
||||
<SendMessage closeSidebar={closeSidebar} />
|
||||
</Suspense>
|
||||
</SlidingSidebar>
|
||||
</>
|
||||
|
|
|
@ -1,52 +1,38 @@
|
|||
import React from 'react';
|
||||
import WarningIcon from 'components/common/Icons/WarningIcon';
|
||||
import { gitCommitPath } from 'lib/paths';
|
||||
import { useActuatorInfo } from 'lib/hooks/api/actuatorInfo';
|
||||
import { BUILD_VERSION_PATTERN } from 'lib/constants';
|
||||
import { useLatestVersion } from 'lib/hooks/api/latestVersion';
|
||||
import { formatTimestamp } from 'lib/dateTimeHelpers';
|
||||
|
||||
import * as S from './Version.styled';
|
||||
import compareVersions from './compareVersions';
|
||||
|
||||
const Version: React.FC = () => {
|
||||
const { data: actuatorInfo = {} } = useActuatorInfo();
|
||||
const { data: latestVersionInfo = {} } = useLatestVersion();
|
||||
|
||||
const tag = actuatorInfo?.build?.version;
|
||||
const commit = actuatorInfo?.git?.commit.id;
|
||||
const { tag_name: latestTag } = latestVersionInfo;
|
||||
|
||||
const outdated = compareVersions(tag, latestTag);
|
||||
|
||||
const currentVersion = tag?.match(BUILD_VERSION_PATTERN)
|
||||
? tag
|
||||
: formatTimestamp(actuatorInfo?.build?.time);
|
||||
|
||||
if (!tag) return null;
|
||||
const { buildTime, commitId, isLatestRelease } = latestVersionInfo.build;
|
||||
const { versionTag } = latestVersionInfo?.latestRelease || '';
|
||||
|
||||
return (
|
||||
<S.Wrapper>
|
||||
{!!outdated && (
|
||||
{!isLatestRelease && (
|
||||
<S.OutdatedWarning
|
||||
title={`Your app version is outdated. Current latest version is ${latestTag}`}
|
||||
title={`Your app version is outdated. Current latest version is ${versionTag}`}
|
||||
>
|
||||
<WarningIcon />
|
||||
</S.OutdatedWarning>
|
||||
)}
|
||||
|
||||
{commit && (
|
||||
{commitId && (
|
||||
<div>
|
||||
<S.CurrentCommitLink
|
||||
title="Current commit"
|
||||
target="__blank"
|
||||
href={gitCommitPath(commit)}
|
||||
href={gitCommitPath(commitId)}
|
||||
>
|
||||
{commit}
|
||||
{commitId}
|
||||
</S.CurrentCommitLink>
|
||||
</div>
|
||||
)}
|
||||
<S.CurrentVersion>{currentVersion}</S.CurrentVersion>
|
||||
<S.CurrentVersion>{formatTimestamp(buildTime)}</S.CurrentVersion>
|
||||
</S.Wrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,87 +2,40 @@ import React from 'react';
|
|||
import { screen } from '@testing-library/dom';
|
||||
import Version from 'components/Version/Version';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { formatTimestamp } from 'lib/dateTimeHelpers';
|
||||
import { useActuatorInfo } from 'lib/hooks/api/actuatorInfo';
|
||||
import { useLatestVersion } from 'lib/hooks/api/latestVersion';
|
||||
import { actuatorInfoPayload } from 'lib/fixtures/actuatorInfo';
|
||||
import { latestVersionPayload } from 'lib/fixtures/latestVersion';
|
||||
import {
|
||||
deprecatedVersionPayload,
|
||||
latestVersionPayload,
|
||||
} from 'lib/fixtures/latestVersion';
|
||||
|
||||
jest.mock('lib/hooks/api/actuatorInfo', () => ({
|
||||
useActuatorInfo: jest.fn(),
|
||||
}));
|
||||
jest.mock('lib/hooks/api/latestVersion', () => ({
|
||||
useLatestVersion: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Version Component', () => {
|
||||
const versionTag = 'v0.5.0';
|
||||
const snapshotTag = 'test-SNAPSHOT';
|
||||
const commitTag = 'befd3b328e2c9c7df57b0c5746561b2f7fee8813';
|
||||
const commitId = '96a577a';
|
||||
|
||||
const actuatorVersionPayload = actuatorInfoPayload(versionTag);
|
||||
const formattedTimestamp = formatTimestamp(actuatorVersionPayload.build.time);
|
||||
describe('render latest version', () => {
|
||||
beforeEach(() => {
|
||||
(useLatestVersion as jest.Mock).mockImplementation(() => ({
|
||||
data: latestVersionPayload,
|
||||
}));
|
||||
});
|
||||
it('renders latest release version as current version', async () => {
|
||||
render(<Version />);
|
||||
expect(screen.getByText(commitId)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(useActuatorInfo as jest.Mock).mockImplementation(() => ({
|
||||
data: actuatorVersionPayload,
|
||||
}));
|
||||
it('should not show warning icon if it is last release', async () => {
|
||||
render(<Version />);
|
||||
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('show warning icon if it is not last release', async () => {
|
||||
(useLatestVersion as jest.Mock).mockImplementation(() => ({
|
||||
data: latestVersionPayload,
|
||||
data: deprecatedVersionPayload,
|
||||
}));
|
||||
});
|
||||
|
||||
describe('tag does not exist', () => {
|
||||
it('does not render component', async () => {
|
||||
(useActuatorInfo as jest.Mock).mockImplementation(() => ({
|
||||
data: null,
|
||||
}));
|
||||
const { container } = render(<Version />);
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
describe('renders current version', () => {
|
||||
it('renders release build version as current version', async () => {
|
||||
render(<Version />);
|
||||
expect(screen.getByText(versionTag)).toBeInTheDocument();
|
||||
});
|
||||
it('renders formatted timestamp as current version when version is commit', async () => {
|
||||
(useActuatorInfo as jest.Mock).mockImplementation(() => ({
|
||||
data: actuatorInfoPayload(commitTag),
|
||||
}));
|
||||
render(<Version />);
|
||||
expect(screen.getByText(formattedTimestamp)).toBeInTheDocument();
|
||||
});
|
||||
it('renders formatted timestamp as current version when version contains -SNAPSHOT', async () => {
|
||||
(useActuatorInfo as jest.Mock).mockImplementation(() => ({
|
||||
data: actuatorInfoPayload(snapshotTag),
|
||||
}));
|
||||
render(<Version />);
|
||||
expect(screen.getByText(formattedTimestamp)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('outdated build version', () => {
|
||||
it('renders warning message', async () => {
|
||||
(useActuatorInfo as jest.Mock).mockImplementation(() => ({
|
||||
data: actuatorInfoPayload('v0.3.0'),
|
||||
}));
|
||||
render(<Version />);
|
||||
expect(
|
||||
screen.getByTitle(
|
||||
`Your app version is outdated. Current latest version is ${latestVersionPayload.tag_name}`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('current commit id with link', () => {
|
||||
it('renders', async () => {
|
||||
render(<Version />);
|
||||
expect(
|
||||
screen.getByText(actuatorVersionPayload.git.commit.id)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
render(<Version />);
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ const WarningIcon: React.FC = () => {
|
|||
return (
|
||||
<WarningIconContainer>
|
||||
<svg
|
||||
role="img"
|
||||
width="14"
|
||||
height="13"
|
||||
viewBox="0 0 14 13"
|
||||
|
|
|
@ -6,7 +6,7 @@ export const Wrapper = styled.div<{ $open?: boolean }>(
|
|||
position: fixed;
|
||||
top: ${theme.layout.navBarHeight};
|
||||
bottom: 0;
|
||||
width: 60vw;
|
||||
width: 37vw;
|
||||
right: calc(${$open ? '0px' : theme.layout.rightSidebarWidth} * -1);
|
||||
box-shadow: -1px 0px 10px 0px rgba(0, 0, 0, 0.2);
|
||||
transition: right 0.3s linear;
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
export const actuatorInfoPayload = (
|
||||
version = 'befd3b328e2c9c7df57b0c5746561b2f7fee8813'
|
||||
) => ({
|
||||
git: { commit: { id: 'befd3b3' } },
|
||||
build: {
|
||||
artifact: 'kafka-ui-api',
|
||||
name: 'kafka-ui-api',
|
||||
time: '2022-09-15T09:52:21.753Z',
|
||||
version,
|
||||
group: 'com.provectus',
|
||||
},
|
||||
});
|
|
@ -1,3 +1,16 @@
|
|||
export const latestVersionPayload = {
|
||||
tag_name: 'v0.4.0',
|
||||
export const deprecatedVersionPayload = {
|
||||
build: {
|
||||
buildTime: '2023-04-14T09:47:35.463Z',
|
||||
commitId: '96a577a',
|
||||
isLatestRelease: false,
|
||||
version: '96a577a98c6069376c5d22ed49cffd3739f1bbdc',
|
||||
},
|
||||
};
|
||||
export const latestVersionPayload = {
|
||||
build: {
|
||||
buildTime: '2023-04-14T09:47:35.463Z',
|
||||
commitId: '96a577a',
|
||||
isLatestRelease: true,
|
||||
version: '96a577a98c6069376c5d22ed49cffd3739f1bbdc',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import fetchMock from 'fetch-mock';
|
||||
import * as hooks from 'lib/hooks/api/actuatorInfo';
|
||||
import { expectQueryWorks, renderQueryHook } from 'lib/testHelpers';
|
||||
import { actuatorInfoPayload } from 'lib/fixtures/actuatorInfo';
|
||||
|
||||
const actuatorInfoPath = '/actuator/info';
|
||||
|
||||
describe('Actuator info hooks', () => {
|
||||
beforeEach(() => fetchMock.restore());
|
||||
describe('useActuatorInfo', () => {
|
||||
it('returns the correct data', async () => {
|
||||
const mock = fetchMock.getOnce(actuatorInfoPath, actuatorInfoPayload());
|
||||
const { result } = renderQueryHook(() => hooks.useActuatorInfo());
|
||||
await expectQueryWorks(mock, result);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,18 +1,16 @@
|
|||
import fetchMock from 'fetch-mock';
|
||||
import { expectQueryWorks, renderQueryHook } from 'lib/testHelpers';
|
||||
import * as hooks from 'lib/hooks/api/latestVersion';
|
||||
import { GIT_REPO_LATEST_RELEASE_LINK } from 'lib/constants';
|
||||
import { latestVersionPayload } from 'lib/fixtures/latestVersion';
|
||||
import { useLatestVersion } from 'lib/hooks/api/latestVersion';
|
||||
|
||||
const latestVersionPath = '/api/info';
|
||||
|
||||
describe('Latest version hooks', () => {
|
||||
beforeEach(() => fetchMock.restore());
|
||||
describe('useLatestVersion', () => {
|
||||
it('returns the correct data', async () => {
|
||||
const mock = fetchMock.getOnce(
|
||||
GIT_REPO_LATEST_RELEASE_LINK,
|
||||
latestVersionPayload
|
||||
);
|
||||
const { result } = renderQueryHook(() => hooks.useLatestVersion());
|
||||
const mock = fetchMock.getOnce(latestVersionPath, latestVersionPayload);
|
||||
const { result } = renderQueryHook(() => useLatestVersion());
|
||||
await expectQueryWorks(mock, result);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { BASE_PARAMS, QUERY_REFETCH_OFF_OPTIONS } from 'lib/constants';
|
||||
|
||||
const fetchActuatorInfo = async () => {
|
||||
const data = await fetch(
|
||||
`${BASE_PARAMS.basePath}/actuator/info`,
|
||||
BASE_PARAMS
|
||||
).then((res) => res.json());
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export function useActuatorInfo() {
|
||||
return useQuery(
|
||||
['actuatorInfo'],
|
||||
fetchActuatorInfo,
|
||||
QUERY_REFETCH_OFF_OPTIONS
|
||||
);
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
QUERY_REFETCH_OFF_OPTIONS,
|
||||
GIT_REPO_LATEST_RELEASE_LINK,
|
||||
} from 'lib/constants';
|
||||
import { BASE_PARAMS, QUERY_REFETCH_OFF_OPTIONS } from 'lib/constants';
|
||||
|
||||
const fetchLatestVersion = async () => {
|
||||
const data = await fetch(GIT_REPO_LATEST_RELEASE_LINK).then((res) =>
|
||||
res.json()
|
||||
);
|
||||
const fetchLatestVersionInfo = async () => {
|
||||
const data = await fetch(
|
||||
`${BASE_PARAMS.basePath}/api/info`,
|
||||
BASE_PARAMS
|
||||
).then((res) => res.json());
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export function useLatestVersion() {
|
||||
return useQuery(
|
||||
['latestVersion'],
|
||||
fetchLatestVersion,
|
||||
['versionInfo'],
|
||||
fetchLatestVersionInfo,
|
||||
QUERY_REFETCH_OFF_OPTIONS
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue