Skip to content

Commit ff310be

Browse files
committed
Merge branch 'open-source-contribution' of https://github.com/colesmith54/kafka-ui into open-source-contribution
2 parents ebd94c3 + 92dc618 commit ff310be

File tree

12 files changed

+145
-36
lines changed

12 files changed

+145
-36
lines changed

api/src/main/java/io/kafbat/ui/service/ApplicationInfoService.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.kafbat.ui.service;
22

33
import static io.kafbat.ui.model.ApplicationInfoDTO.EnabledFeaturesEnum;
4+
import static io.kafbat.ui.util.GithubReleaseInfo.GITHUB_RELEASE_INFO_TIMEOUT;
45

6+
import com.google.common.annotations.VisibleForTesting;
57
import io.kafbat.ui.model.ApplicationInfoBuildDTO;
68
import io.kafbat.ui.model.ApplicationInfoDTO;
79
import io.kafbat.ui.model.ApplicationInfoLatestReleaseDTO;
@@ -13,27 +15,27 @@
1315
import java.util.Optional;
1416
import java.util.Properties;
1517
import org.springframework.beans.factory.annotation.Autowired;
18+
import org.springframework.beans.factory.annotation.Value;
1619
import org.springframework.boot.info.BuildProperties;
1720
import org.springframework.boot.info.GitProperties;
1821
import org.springframework.scheduling.annotation.Scheduled;
1922
import org.springframework.stereotype.Service;
20-
import reactor.core.publisher.Mono;
2123

2224
@Service
2325
public class ApplicationInfoService {
24-
25-
private final GithubReleaseInfo githubReleaseInfo = new GithubReleaseInfo();
26-
26+
private final GithubReleaseInfo githubReleaseInfo;
2727
private final DynamicConfigOperations dynamicConfigOperations;
2828
private final BuildProperties buildProperties;
2929
private final GitProperties gitProperties;
3030

3131
public ApplicationInfoService(DynamicConfigOperations dynamicConfigOperations,
3232
@Autowired(required = false) BuildProperties buildProperties,
33-
@Autowired(required = false) GitProperties gitProperties) {
33+
@Autowired(required = false) GitProperties gitProperties,
34+
@Value("${" + GITHUB_RELEASE_INFO_TIMEOUT + ":10}") int githubApiMaxWaitTime) {
3435
this.dynamicConfigOperations = dynamicConfigOperations;
3536
this.buildProperties = Optional.ofNullable(buildProperties).orElse(new BuildProperties(new Properties()));
3637
this.gitProperties = Optional.ofNullable(gitProperties).orElse(new GitProperties(new Properties()));
38+
githubReleaseInfo = new GithubReleaseInfo(githubApiMaxWaitTime);
3739
}
3840

3941
public ApplicationInfoDTO getApplicationInfo() {
@@ -74,4 +76,8 @@ public void updateGithubReleaseInfo() {
7476
githubReleaseInfo.refresh().subscribe();
7577
}
7678

79+
@VisibleForTesting
80+
GithubReleaseInfo githubReleaseInfo() {
81+
return githubReleaseInfo;
82+
}
7783
}

api/src/main/java/io/kafbat/ui/util/GithubReleaseInfo.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
import com.google.common.annotations.VisibleForTesting;
44
import java.time.Duration;
5+
import lombok.Getter;
56
import lombok.extern.slf4j.Slf4j;
67
import reactor.core.publisher.Mono;
78

89
@Slf4j
910
public class GithubReleaseInfo {
11+
public static final String GITHUB_RELEASE_INFO_TIMEOUT = "github.release.info.timeout";
1012

1113
private static final String GITHUB_LATEST_RELEASE_RETRIEVAL_URL =
1214
"https://api.github.com/repos/kafbat/kafka-ui/releases/latest";
1315

14-
private static final Duration GITHUB_API_MAX_WAIT_TIME = Duration.ofSeconds(10);
15-
1616
public record GithubReleaseDto(String html_url, String tag_name, String published_at) {
1717

1818
static GithubReleaseDto empty() {
@@ -24,17 +24,21 @@ static GithubReleaseDto empty() {
2424

2525
private final Mono<Void> refreshMono;
2626

27-
public GithubReleaseInfo() {
28-
this(GITHUB_LATEST_RELEASE_RETRIEVAL_URL);
27+
@Getter
28+
private final int githubApiMaxWaitTime;
29+
30+
public GithubReleaseInfo(int githubApiMaxWaitTime) {
31+
this(GITHUB_LATEST_RELEASE_RETRIEVAL_URL, githubApiMaxWaitTime);
2932
}
3033

3134
@VisibleForTesting
32-
GithubReleaseInfo(String url) {
35+
GithubReleaseInfo(String url, int githubApiMaxWaitTime) {
36+
this.githubApiMaxWaitTime = githubApiMaxWaitTime;
3337
this.refreshMono = new WebClientConfigurator().build()
3438
.get()
3539
.uri(url)
3640
.exchangeToMono(resp -> resp.bodyToMono(GithubReleaseDto.class))
37-
.timeout(GITHUB_API_MAX_WAIT_TIME)
41+
.timeout(Duration.ofSeconds(this.githubApiMaxWaitTime))
3842
.doOnError(th -> log.trace("Error getting latest github release info", th))
3943
.onErrorResume(th -> true, th -> Mono.just(GithubReleaseDto.empty()))
4044
.doOnNext(release -> this.release = release)

api/src/test/java/io/kafbat/ui/AbstractIntegrationTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.kafbat.ui;
22

3+
import static io.kafbat.ui.util.GithubReleaseInfo.GITHUB_RELEASE_INFO_TIMEOUT;
4+
35
import io.kafbat.ui.container.KafkaConnectContainer;
46
import io.kafbat.ui.container.KsqlDbContainer;
57
import io.kafbat.ui.container.SchemaRegistryContainer;
@@ -98,6 +100,7 @@ public void initialize(@NotNull ConfigurableApplicationContext context) {
98100

99101
System.setProperty("dynamic.config.enabled", "true");
100102
System.setProperty("config.related.uploads.dir", tmpDir.toString());
103+
System.setProperty(GITHUB_RELEASE_INFO_TIMEOUT, String.valueOf(100));
101104
}
102105
}
103106

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.kafbat.ui.service;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import io.kafbat.ui.AbstractIntegrationTest;
6+
import org.junit.jupiter.api.Test;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
9+
public class ApplicationInfoServiceTest extends AbstractIntegrationTest {
10+
@Autowired
11+
private ApplicationInfoService service;
12+
13+
@Test
14+
public void testCustomGithubReleaseInfoTimeout() {
15+
assertEquals(100, service.githubReleaseInfo().getGithubApiMaxWaitTime());
16+
}
17+
}

api/src/test/java/io/kafbat/ui/util/GithubReleaseInfoTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ void test() {
3737
"""));
3838
var url = mockWebServer.url("repos/kafbat/kafka-ui/releases/latest").toString();
3939

40-
var infoHolder = new GithubReleaseInfo(url);
40+
var infoHolder = new GithubReleaseInfo(url, 10);
4141
infoHolder.refresh().block();
4242

4343
var i = infoHolder.get();

frontend/src/components/Schemas/Edit/Edit.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import useAppParams from 'lib/hooks/useAppParams';
44
import PageLoader from 'components/common/PageLoader/PageLoader';
55
import { useNavigate } from 'react-router-dom';
66
import { useGetLatestSchema } from 'lib/hooks/api/schemas';
7+
import { QUERY_REFETCH_LIMITED_OPTIONS } from 'lib/constants';
78

89
import Form from './Form';
910

@@ -14,10 +15,13 @@ const Edit: React.FC = () => {
1415
isFetching,
1516
isError,
1617
data: schema,
17-
} = useGetLatestSchema({
18-
clusterName,
19-
subject,
20-
});
18+
} = useGetLatestSchema(
19+
{
20+
clusterName,
21+
subject,
22+
},
23+
QUERY_REFETCH_LIMITED_OPTIONS
24+
);
2125

2226
useEffect(() => {
2327
if (isError) {

frontend/src/components/Topics/Topic/Edit/Edit.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { yupResolver } from '@hookform/resolvers/yup';
77
import { topicFormValidationSchema } from 'lib/yupExtended';
88
import useAppParams from 'lib/hooks/useAppParams';
99
import topicParamsTransformer from 'components/Topics/Topic/Edit/topicParamsTransformer';
10-
import { MILLISECONDS_IN_WEEK } from 'lib/constants';
10+
import {
11+
MILLISECONDS_IN_WEEK,
12+
QUERY_REFETCH_LIMITED_OPTIONS,
13+
} from 'lib/constants';
1114
import {
1215
useTopicConfig,
1316
useTopicDetails,
@@ -30,8 +33,14 @@ export const TOPIC_EDIT_FORM_DEFAULT_PROPS = {
3033

3134
const Edit: React.FC = () => {
3235
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
33-
const { data: topic } = useTopicDetails({ clusterName, topicName });
34-
const { data: topicConfig } = useTopicConfig({ clusterName, topicName });
36+
const { data: topic } = useTopicDetails(
37+
{ clusterName, topicName },
38+
QUERY_REFETCH_LIMITED_OPTIONS
39+
);
40+
const { data: topicConfig } = useTopicConfig(
41+
{ clusterName, topicName },
42+
QUERY_REFETCH_LIMITED_OPTIONS
43+
);
3544
const updateTopic = useUpdateTopic({ clusterName, topicName });
3645

3746
const defaultValues = topicParamsTransformer(topic, topicConfig);

frontend/src/lib/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ export const QUERY_REFETCH_OFF_OPTIONS = {
7373
refetchIntervalInBackground: false,
7474
};
7575

76+
export const QUERY_REFETCH_LIMITED_OPTIONS = {
77+
refetchOnWindowFocus: false,
78+
refetchIntervalInBackground: false,
79+
};
80+
7681
// Cluster Form Constants
7782
export const AUTH_OPTIONS = [
7883
{ value: 'SASL/JAAS', label: 'SASL/JAAS' },

frontend/src/lib/hooks/api/__tests__/schema.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from 'lib/testHelpers';
66
import fetchMock from 'fetch-mock';
77
import * as hooks from 'lib/hooks/api/schemas';
8+
import { QUERY_REFETCH_LIMITED_OPTIONS } from 'lib/constants';
89
import { act } from 'react-dom/test-utils';
910
import { renderHook, waitFor } from '@testing-library/react';
1011
import { CompatibilityLevelCompatibilityEnum } from 'generated-sources';
@@ -47,6 +48,16 @@ describe('Schema hooks', () => {
4748
);
4849
await expectQueryWorks(mock, result);
4950
});
51+
it('returns the correct data with queryOptions', async () => {
52+
const mock = fetchMock.getOnce(schemasAPILatestUrl, schemaVersion);
53+
const { result } = renderQueryHook(() =>
54+
hooks.useGetLatestSchema(
55+
{ clusterName, subject },
56+
QUERY_REFETCH_LIMITED_OPTIONS
57+
)
58+
);
59+
await expectQueryWorks(mock, result);
60+
});
5061
});
5162

5263
describe('useGetSchemasVersions', () => {

frontend/src/lib/hooks/api/__tests__/topics.spec.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import fetchMock from 'fetch-mock';
99
import { externalTopicPayload, topicConfigPayload } from 'lib/fixtures/topics';
1010
import { CreateTopicMessage } from 'generated-sources';
1111
import { TopicFormData, TopicFormDataRaw } from 'lib/interfaces/topic';
12+
import { QUERY_REFETCH_LIMITED_OPTIONS } from 'lib/constants';
1213

1314
const clusterName = 'test-cluster';
1415
const topicName = 'test-topic';
@@ -30,17 +31,37 @@ describe('Topics hooks', () => {
3031
const { result } = renderQueryHook(() => hooks.useTopics({ clusterName }));
3132
await expectQueryWorks(mock, result);
3233
});
33-
it('handles useTopicDetails', async () => {
34-
const mock = fetchMock.getOnce(topicPath, externalTopicPayload);
35-
const { result } = renderQueryHook(() =>
36-
hooks.useTopicDetails(topicParams)
37-
);
38-
await expectQueryWorks(mock, result);
34+
describe('useTopicDetails', () => {
35+
it('handles useTopicDetails', async () => {
36+
const mock = fetchMock.getOnce(topicPath, externalTopicPayload);
37+
const { result } = renderQueryHook(() =>
38+
hooks.useTopicDetails(topicParams)
39+
);
40+
await expectQueryWorks(mock, result);
41+
});
42+
it('handles useTopicDetails with queryOptions', async () => {
43+
const mock = fetchMock.getOnce(topicPath, externalTopicPayload);
44+
const { result } = renderQueryHook(() =>
45+
hooks.useTopicDetails(topicParams, QUERY_REFETCH_LIMITED_OPTIONS)
46+
);
47+
await expectQueryWorks(mock, result);
48+
});
3949
});
40-
it('handles useTopicConfig', async () => {
41-
const mock = fetchMock.getOnce(`${topicPath}/config`, topicConfigPayload);
42-
const { result } = renderQueryHook(() => hooks.useTopicConfig(topicParams));
43-
await expectQueryWorks(mock, result);
50+
describe('useTopicConfig', () => {
51+
it('handles useTopicConfig', async () => {
52+
const mock = fetchMock.getOnce(`${topicPath}/config`, topicConfigPayload);
53+
const { result } = renderQueryHook(() =>
54+
hooks.useTopicConfig(topicParams)
55+
);
56+
await expectQueryWorks(mock, result);
57+
});
58+
it('handles useTopicConfig with queryOptions', async () => {
59+
const mock = fetchMock.getOnce(`${topicPath}/config`, topicConfigPayload);
60+
const { result } = renderQueryHook(() =>
61+
hooks.useTopicConfig(topicParams, QUERY_REFETCH_LIMITED_OPTIONS)
62+
);
63+
await expectQueryWorks(mock, result);
64+
});
4465
});
4566
it('handles useTopicConsumerGroups', async () => {
4667
const mock = fetchMock.getOnce(`${topicPath}/consumer-groups`, []);

frontend/src/lib/hooks/api/schemas.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
1+
import {
2+
useMutation,
3+
useQuery,
4+
useQueryClient,
5+
UseQueryOptions,
6+
} from '@tanstack/react-query';
27
import {
38
GLOBAL_COMPATIBILITY_SCHEMAS_QUERY_KEY,
49
LATEST_SCHEMA_QUERY_KEY,
@@ -19,7 +24,10 @@ import {
1924
import { schemasApiClient } from 'lib/api';
2025
import { ClusterName } from 'lib/interfaces/cluster';
2126

22-
export function useGetLatestSchema(param: GetLatestSchemaRequest) {
27+
export function useGetLatestSchema(
28+
param: GetLatestSchemaRequest,
29+
options?: UseQueryOptions<SchemaSubject>
30+
) {
2331
return useQuery<SchemaSubject>({
2432
queryKey: [
2533
SCHEMA_QUERY_KEY,
@@ -28,6 +36,7 @@ export function useGetLatestSchema(param: GetLatestSchemaRequest) {
2836
param.subject,
2937
],
3038
queryFn: () => schemasApiClient.getLatestSchema(param),
39+
...options,
3140
});
3241
}
3342

frontend/src/lib/hooks/api/topics.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@ import {
44
consumerGroupsApiClient,
55
messagesApiClient,
66
} from 'lib/api';
7-
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
7+
import {
8+
useMutation,
9+
useQuery,
10+
useQueryClient,
11+
UseQueryOptions,
12+
} from '@tanstack/react-query';
813
import {
914
CreateTopicMessage,
1015
GetTopicDetailsRequest,
1116
GetTopicsRequest,
1217
Topic,
1318
TopicConfig,
1419
TopicCreation,
20+
TopicDetails,
1521
TopicUpdate,
1622
} from 'generated-sources';
1723
import { showServerError, showSuccessAlert } from 'lib/errorHandling';
@@ -49,11 +55,25 @@ export function useTopics(props: GetTopicsRequest) {
4955
{ keepPreviousData: true }
5056
);
5157
}
52-
export function useTopicDetails(props: GetTopicDetailsRequest) {
53-
return useQuery(topicKeys.details(props), () => api.getTopicDetails(props));
58+
export function useTopicDetails(
59+
props: GetTopicDetailsRequest,
60+
queryOptions?: UseQueryOptions<TopicDetails>
61+
) {
62+
return useQuery<TopicDetails>(
63+
topicKeys.details(props),
64+
() => api.getTopicDetails(props),
65+
queryOptions
66+
);
5467
}
55-
export function useTopicConfig(props: GetTopicDetailsRequest) {
56-
return useQuery(topicKeys.config(props), () => api.getTopicConfigs(props));
68+
export function useTopicConfig(
69+
props: GetTopicDetailsRequest,
70+
queryOptions?: UseQueryOptions<TopicConfig[]>
71+
) {
72+
return useQuery<TopicConfig[]>(
73+
topicKeys.config(props),
74+
() => api.getTopicConfigs(props),
75+
queryOptions
76+
);
5777
}
5878
export function useTopicConsumerGroups(props: GetTopicDetailsRequest) {
5979
return useQuery(topicKeys.consumerGroups(props), () =>

0 commit comments

Comments
 (0)