ISSUE-309: Topic create & update models split (#312)

* ISSUE-309: Topic create & update models split
This commit is contained in:
iliax 2021-03-25 11:24:51 +03:00 committed by GitHub
parent 752c19dd9c
commit 8d2f929a52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 88 additions and 47 deletions

View file

@ -3,8 +3,9 @@ package com.provectus.kafka.ui.controller;
import com.provectus.kafka.ui.api.TopicsApi; import com.provectus.kafka.ui.api.TopicsApi;
import com.provectus.kafka.ui.model.Topic; import com.provectus.kafka.ui.model.Topic;
import com.provectus.kafka.ui.model.TopicConfig; import com.provectus.kafka.ui.model.TopicConfig;
import com.provectus.kafka.ui.model.TopicCreation;
import com.provectus.kafka.ui.model.TopicDetails; import com.provectus.kafka.ui.model.TopicDetails;
import com.provectus.kafka.ui.model.TopicFormData; import com.provectus.kafka.ui.model.TopicUpdate;
import com.provectus.kafka.ui.model.TopicsResponse; import com.provectus.kafka.ui.model.TopicsResponse;
import com.provectus.kafka.ui.service.ClusterService; import com.provectus.kafka.ui.service.ClusterService;
import java.util.Optional; import java.util.Optional;
@ -26,8 +27,8 @@ public class TopicsController implements TopicsApi {
@Override @Override
public Mono<ResponseEntity<Topic>> createTopic( public Mono<ResponseEntity<Topic>> createTopic(
String clusterName, @Valid Mono<TopicFormData> topicFormData, ServerWebExchange exchange) { String clusterName, @Valid Mono<TopicCreation> topicCreation, ServerWebExchange exchange) {
return clusterService.createTopic(clusterName, topicFormData) return clusterService.createTopic(clusterName, topicCreation)
.map(s -> new ResponseEntity<>(s, HttpStatus.OK)) .map(s -> new ResponseEntity<>(s, HttpStatus.OK))
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build())); .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
} }
@ -70,8 +71,8 @@ public class TopicsController implements TopicsApi {
@Override @Override
public Mono<ResponseEntity<Topic>> updateTopic( public Mono<ResponseEntity<Topic>> updateTopic(
String clusterId, String topicName, @Valid Mono<TopicFormData> topicFormData, String clusterId, String topicName, @Valid Mono<TopicUpdate> topicUpdate,
ServerWebExchange exchange) { ServerWebExchange exchange) {
return clusterService.updateTopic(clusterId, topicName, topicFormData).map(ResponseEntity::ok); return clusterService.updateTopic(clusterId, topicName, topicUpdate).map(ResponseEntity::ok);
} }
} }

View file

@ -71,7 +71,7 @@ public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHan
private Mono<ServerResponse> renderDefault(Throwable throwable, ServerRequest request) { private Mono<ServerResponse> renderDefault(Throwable throwable, ServerRequest request) {
var response = new ErrorResponse() var response = new ErrorResponse()
.code(ErrorCode.UNEXPECTED.code()) .code(ErrorCode.UNEXPECTED.code())
.message(throwable.getMessage()) .message(coalesce(throwable.getMessage(), "Unexpected internal error"))
.requestId(requestId(request)) .requestId(requestId(request))
.timestamp(currentTimestamp()); .timestamp(currentTimestamp());
return ServerResponse return ServerResponse
@ -84,7 +84,7 @@ public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHan
ErrorCode errorCode = baseException.getErrorCode(); ErrorCode errorCode = baseException.getErrorCode();
var response = new ErrorResponse() var response = new ErrorResponse()
.code(errorCode.code()) .code(errorCode.code())
.message(baseException.getMessage()) .message(coalesce(baseException.getMessage(), "Internal error"))
.requestId(requestId(request)) .requestId(requestId(request))
.timestamp(currentTimestamp()); .timestamp(currentTimestamp());
return ServerResponse return ServerResponse

View file

@ -15,9 +15,10 @@ import com.provectus.kafka.ui.model.InternalTopic;
import com.provectus.kafka.ui.model.KafkaCluster; import com.provectus.kafka.ui.model.KafkaCluster;
import com.provectus.kafka.ui.model.Topic; import com.provectus.kafka.ui.model.Topic;
import com.provectus.kafka.ui.model.TopicConfig; import com.provectus.kafka.ui.model.TopicConfig;
import com.provectus.kafka.ui.model.TopicCreation;
import com.provectus.kafka.ui.model.TopicDetails; import com.provectus.kafka.ui.model.TopicDetails;
import com.provectus.kafka.ui.model.TopicFormData;
import com.provectus.kafka.ui.model.TopicMessage; import com.provectus.kafka.ui.model.TopicMessage;
import com.provectus.kafka.ui.model.TopicUpdate;
import com.provectus.kafka.ui.model.TopicsResponse; import com.provectus.kafka.ui.model.TopicsResponse;
import com.provectus.kafka.ui.util.ClusterUtil; import com.provectus.kafka.ui.util.ClusterUtil;
import java.util.Collection; import java.util.Collection;
@ -125,9 +126,9 @@ public class ClusterService {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
public Mono<Topic> createTopic(String clusterName, Mono<TopicFormData> topicFormData) { public Mono<Topic> createTopic(String clusterName, Mono<TopicCreation> topicCreation) {
return clustersStorage.getClusterByName(clusterName).map(cluster -> return clustersStorage.getClusterByName(clusterName).map(cluster ->
kafkaService.createTopic(cluster, topicFormData) kafkaService.createTopic(cluster, topicCreation)
.doOnNext(t -> updateCluster(t, clusterName, cluster)) .doOnNext(t -> updateCluster(t, clusterName, cluster))
.map(clusterMapper::toTopic) .map(clusterMapper::toTopic)
).orElse(Mono.empty()); ).orElse(Mono.empty());
@ -200,9 +201,9 @@ public class ClusterService {
@SneakyThrows @SneakyThrows
public Mono<Topic> updateTopic(String clusterName, String topicName, public Mono<Topic> updateTopic(String clusterName, String topicName,
Mono<TopicFormData> topicFormData) { Mono<TopicUpdate> topicUpdate) {
return clustersStorage.getClusterByName(clusterName).map(cl -> return clustersStorage.getClusterByName(clusterName).map(cl ->
topicFormData topicUpdate
.flatMap(t -> kafkaService.updateTopic(cl, topicName, t)) .flatMap(t -> kafkaService.updateTopic(cl, topicName, t))
.doOnNext(t -> updateCluster(t, clusterName, cl)) .doOnNext(t -> updateCluster(t, clusterName, cl))
.map(clusterMapper::toTopic) .map(clusterMapper::toTopic)

View file

@ -12,7 +12,8 @@ import com.provectus.kafka.ui.model.InternalTopicConfig;
import com.provectus.kafka.ui.model.KafkaCluster; import com.provectus.kafka.ui.model.KafkaCluster;
import com.provectus.kafka.ui.model.Metric; import com.provectus.kafka.ui.model.Metric;
import com.provectus.kafka.ui.model.ServerStatus; import com.provectus.kafka.ui.model.ServerStatus;
import com.provectus.kafka.ui.model.TopicFormData; import com.provectus.kafka.ui.model.TopicCreation;
import com.provectus.kafka.ui.model.TopicUpdate;
import com.provectus.kafka.ui.util.ClusterUtil; import com.provectus.kafka.ui.util.ClusterUtil;
import com.provectus.kafka.ui.util.JmxClusterUtil; import com.provectus.kafka.ui.util.JmxClusterUtil;
import com.provectus.kafka.ui.util.JmxMetricsName; import com.provectus.kafka.ui.util.JmxMetricsName;
@ -223,8 +224,8 @@ public class KafkaService {
@SneakyThrows @SneakyThrows
public Mono<InternalTopic> createTopic(AdminClient adminClient, public Mono<InternalTopic> createTopic(AdminClient adminClient,
Mono<TopicFormData> topicFormData) { Mono<TopicCreation> topicCreation) {
return topicFormData.flatMap( return topicCreation.flatMap(
topicData -> { topicData -> {
NewTopic newTopic = new NewTopic(topicData.getName(), topicData.getPartitions(), NewTopic newTopic = new NewTopic(topicData.getName(), topicData.getPartitions(),
topicData.getReplicationFactor().shortValue()); topicData.getReplicationFactor().shortValue());
@ -242,9 +243,9 @@ public class KafkaService {
); );
} }
public Mono<InternalTopic> createTopic(KafkaCluster cluster, Mono<TopicFormData> topicFormData) { public Mono<InternalTopic> createTopic(KafkaCluster cluster, Mono<TopicCreation> topicCreation) {
return getOrCreateAdminClient(cluster) return getOrCreateAdminClient(cluster)
.flatMap(ac -> createTopic(ac.getAdminClient(), topicFormData)); .flatMap(ac -> createTopic(ac.getAdminClient(), topicCreation));
} }
public Mono<Void> deleteTopic(KafkaCluster cluster, String topicName) { public Mono<Void> deleteTopic(KafkaCluster cluster, String topicName) {
@ -320,16 +321,16 @@ public class KafkaService {
@SneakyThrows @SneakyThrows
public Mono<InternalTopic> updateTopic(KafkaCluster cluster, String topicName, public Mono<InternalTopic> updateTopic(KafkaCluster cluster, String topicName,
TopicFormData topicFormData) { TopicUpdate topicUpdate) {
ConfigResource topicCr = new ConfigResource(ConfigResource.Type.TOPIC, topicName); ConfigResource topicCr = new ConfigResource(ConfigResource.Type.TOPIC, topicName);
return getOrCreateAdminClient(cluster) return getOrCreateAdminClient(cluster)
.flatMap(ac -> { .flatMap(ac -> {
if (ac.getSupportedFeatures() if (ac.getSupportedFeatures()
.contains(ExtendedAdminClient.SupportedFeature.INCREMENTAL_ALTER_CONFIGS)) { .contains(ExtendedAdminClient.SupportedFeature.INCREMENTAL_ALTER_CONFIGS)) {
return incrementalAlterConfig(topicFormData, topicCr, ac) return incrementalAlterConfig(topicUpdate, topicCr, ac)
.flatMap(c -> getUpdatedTopic(ac, topicName)); .flatMap(c -> getUpdatedTopic(ac, topicName));
} else { } else {
return alterConfig(topicFormData, topicCr, ac) return alterConfig(topicUpdate, topicCr, ac)
.flatMap(c -> getUpdatedTopic(ac, topicName)); .flatMap(c -> getUpdatedTopic(ac, topicName));
} }
}); });
@ -341,9 +342,9 @@ public class KafkaService {
.filter(t -> t.getName().equals(topicName)).findFirst().orElseThrow()); .filter(t -> t.getName().equals(topicName)).findFirst().orElseThrow());
} }
private Mono<String> incrementalAlterConfig(TopicFormData topicFormData, ConfigResource topicCr, private Mono<String> incrementalAlterConfig(TopicUpdate topicUpdate, ConfigResource topicCr,
ExtendedAdminClient ac) { ExtendedAdminClient ac) {
List<AlterConfigOp> listOp = topicFormData.getConfigs().entrySet().stream() List<AlterConfigOp> listOp = topicUpdate.getConfigs().entrySet().stream()
.flatMap(cfg -> Stream.of(new AlterConfigOp(new ConfigEntry(cfg.getKey(), cfg.getValue()), .flatMap(cfg -> Stream.of(new AlterConfigOp(new ConfigEntry(cfg.getKey(), cfg.getValue()),
AlterConfigOp.OpType.SET))).collect(Collectors.toList()); AlterConfigOp.OpType.SET))).collect(Collectors.toList());
return ClusterUtil.toMono( return ClusterUtil.toMono(
@ -352,9 +353,9 @@ public class KafkaService {
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private Mono<String> alterConfig(TopicFormData topicFormData, ConfigResource topicCr, private Mono<String> alterConfig(TopicUpdate topicUpdate, ConfigResource topicCr,
ExtendedAdminClient ac) { ExtendedAdminClient ac) {
List<ConfigEntry> configEntries = topicFormData.getConfigs().entrySet().stream() List<ConfigEntry> configEntries = topicUpdate.getConfigs().entrySet().stream()
.flatMap(cfg -> Stream.of(new ConfigEntry(cfg.getKey(), cfg.getValue()))) .flatMap(cfg -> Stream.of(new ConfigEntry(cfg.getKey(), cfg.getValue())))
.collect(Collectors.toList()); .collect(Collectors.toList());
Config config = new Config(configEntries); Config config = new Config(configEntries);

View file

@ -1,6 +1,6 @@
package com.provectus.kafka.ui; package com.provectus.kafka.ui;
import com.provectus.kafka.ui.model.TopicFormData; import com.provectus.kafka.ui.model.TopicCreation;
import com.provectus.kafka.ui.model.TopicMessage; import com.provectus.kafka.ui.model.TopicMessage;
import com.provectus.kafka.ui.producer.KafkaTestProducer; import com.provectus.kafka.ui.producer.KafkaTestProducer;
import java.util.Map; import java.util.Map;
@ -27,7 +27,7 @@ public class KafkaConsumerTests extends AbstractBaseTest {
var topicName = UUID.randomUUID().toString(); var topicName = UUID.randomUUID().toString();
webTestClient.post() webTestClient.post()
.uri("/api/clusters/{clusterName}/topics", LOCAL) .uri("/api/clusters/{clusterName}/topics", LOCAL)
.bodyValue(new TopicFormData() .bodyValue(new TopicCreation()
.name(topicName) .name(topicName)
.partitions(1) .partitions(1)
.replicationFactor(1) .replicationFactor(1)

View file

@ -1,6 +1,7 @@
package com.provectus.kafka.ui; package com.provectus.kafka.ui;
import com.provectus.kafka.ui.model.TopicFormData; import com.provectus.kafka.ui.model.TopicCreation;
import com.provectus.kafka.ui.model.TopicUpdate;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@ -24,7 +25,7 @@ public class ReadOnlyModeTests extends AbstractBaseTest {
var topicName = UUID.randomUUID().toString(); var topicName = UUID.randomUUID().toString();
webTestClient.post() webTestClient.post()
.uri("/api/clusters/{clusterName}/topics", LOCAL) .uri("/api/clusters/{clusterName}/topics", LOCAL)
.bodyValue(new TopicFormData() .bodyValue(new TopicCreation()
.name(topicName) .name(topicName)
.partitions(1) .partitions(1)
.replicationFactor(1) .replicationFactor(1)
@ -40,7 +41,7 @@ public class ReadOnlyModeTests extends AbstractBaseTest {
var topicName = UUID.randomUUID().toString(); var topicName = UUID.randomUUID().toString();
webTestClient.post() webTestClient.post()
.uri("/api/clusters/{clusterName}/topics", SECOND_LOCAL) .uri("/api/clusters/{clusterName}/topics", SECOND_LOCAL)
.bodyValue(new TopicFormData() .bodyValue(new TopicCreation()
.name(topicName) .name(topicName)
.partitions(1) .partitions(1)
.replicationFactor(1) .replicationFactor(1)
@ -56,7 +57,7 @@ public class ReadOnlyModeTests extends AbstractBaseTest {
var topicName = UUID.randomUUID().toString(); var topicName = UUID.randomUUID().toString();
webTestClient.post() webTestClient.post()
.uri("/api/clusters/{clusterName}/topics", LOCAL) .uri("/api/clusters/{clusterName}/topics", LOCAL)
.bodyValue(new TopicFormData() .bodyValue(new TopicCreation()
.name(topicName) .name(topicName)
.partitions(1) .partitions(1)
.replicationFactor(1) .replicationFactor(1)
@ -67,10 +68,7 @@ public class ReadOnlyModeTests extends AbstractBaseTest {
.isOk(); .isOk();
webTestClient.patch() webTestClient.patch()
.uri("/api/clusters/{clusterName}/topics/{topicName}", LOCAL, topicName) .uri("/api/clusters/{clusterName}/topics/{topicName}", LOCAL, topicName)
.bodyValue(new TopicFormData() .bodyValue(new TopicUpdate()
.name(topicName)
.partitions(2)
.replicationFactor(1)
.configs(Map.of()) .configs(Map.of())
) )
.exchange() .exchange()
@ -83,10 +81,7 @@ public class ReadOnlyModeTests extends AbstractBaseTest {
var topicName = UUID.randomUUID().toString(); var topicName = UUID.randomUUID().toString();
webTestClient.patch() webTestClient.patch()
.uri("/api/clusters/{clusterName}/topics/{topicName}", SECOND_LOCAL, topicName) .uri("/api/clusters/{clusterName}/topics/{topicName}", SECOND_LOCAL, topicName)
.bodyValue(new TopicFormData() .bodyValue(new TopicUpdate()
.name(topicName)
.partitions(1)
.replicationFactor(1)
.configs(Map.of()) .configs(Map.of())
) )
.exchange() .exchange()

View file

@ -162,7 +162,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/TopicFormData' $ref: '#/components/schemas/TopicCreation'
responses: responses:
201: 201:
description: Created description: Created
@ -215,7 +215,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/TopicFormData' $ref: '#/components/schemas/TopicUpdate'
responses: responses:
200: 200:
description: Updated description: Updated
@ -1281,7 +1281,7 @@ components:
required: required:
- name - name
TopicFormData: TopicCreation:
type: object type: object
properties: properties:
name: name:
@ -1296,6 +1296,18 @@ components:
type: string type: string
required: required:
- name - name
- partitions
- replicationFactor
TopicUpdate:
type: object
properties:
configs:
type: object
additionalProperties:
type: string
required:
- configs
Broker: Broker:
type: object type: object

View file

@ -4,7 +4,8 @@ import {
MessagesApi, MessagesApi,
Configuration, Configuration,
Topic, Topic,
TopicFormData, TopicCreation,
TopicUpdate,
TopicConfig, TopicConfig,
} from 'generated-sources'; } from 'generated-sources';
import { import {
@ -136,7 +137,7 @@ export const fetchTopicConfig = (
} }
}; };
const formatTopicFormData = (form: TopicFormDataRaw): TopicFormData => { const formatTopicCreation = (form: TopicFormDataRaw): TopicCreation => {
const { const {
name, name,
partitions, partitions,
@ -172,6 +173,36 @@ const formatTopicFormData = (form: TopicFormDataRaw): TopicFormData => {
}; };
}; };
const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => {
const {
cleanupPolicy,
retentionBytes,
retentionMs,
maxMessageBytes,
minInSyncReplicas,
customParams,
} = form;
return {
configs: {
'cleanup.policy': cleanupPolicy,
'retention.ms': retentionMs,
'retention.bytes': retentionBytes,
'max.message.bytes': maxMessageBytes,
'min.insync.replicas': minInSyncReplicas,
...Object.values(customParams || {}).reduce(
(result: TopicFormFormattedParams, customParam: TopicConfig) => {
return {
...result,
[customParam.name]: customParam.value,
};
},
{}
),
},
};
};
export const createTopic = ( export const createTopic = (
clusterName: ClusterName, clusterName: ClusterName,
form: TopicFormDataRaw form: TopicFormDataRaw
@ -180,7 +211,7 @@ export const createTopic = (
try { try {
const topic: Topic = await topicsApiClient.createTopic({ const topic: Topic = await topicsApiClient.createTopic({
clusterName, clusterName,
topicFormData: formatTopicFormData(form), topicCreation: formatTopicCreation(form),
}); });
const state = getState().topics; const state = getState().topics;
@ -210,7 +241,7 @@ export const updateTopic = (
const topic: Topic = await topicsApiClient.updateTopic({ const topic: Topic = await topicsApiClient.updateTopic({
clusterName, clusterName,
topicName: form.name, topicName: form.name,
topicFormData: formatTopicFormData(form), topicUpdate: formatTopicUpdate(form),
}); });
const state = getState().topics; const state = getState().topics;

View file

@ -3,7 +3,7 @@ import {
TopicDetails, TopicDetails,
TopicMessage, TopicMessage,
TopicConfig, TopicConfig,
TopicFormData, TopicCreation,
GetTopicMessagesRequest, GetTopicMessagesRequest,
} from 'generated-sources'; } from 'generated-sources';
@ -50,7 +50,7 @@ export interface TopicsState {
messages: TopicMessage[]; messages: TopicMessage[];
} }
export type TopicFormFormattedParams = TopicFormData['configs']; export type TopicFormFormattedParams = TopicCreation['configs'];
export interface TopicFormDataRaw { export interface TopicFormDataRaw {
name: string; name: string;