Split to kafka backend plugin
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
};
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export interface Config {
|
||||
kafka?: {
|
||||
clientId: string;
|
||||
brokers: string[];
|
||||
};
|
||||
}
|
||||
Generated
+18259
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@backstage/plugin-kafka-backend",
|
||||
"version": "0.1.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/kafka-backend"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"kafka"
|
||||
],
|
||||
"configSchema": "config.d.ts",
|
||||
"scripts": {
|
||||
"start": "backstage-cli backend:dev",
|
||||
"build": "backstage-cli backend:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.4.1",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"kafkajs": "^1.16.0-beta.6",
|
||||
"winston": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.2",
|
||||
"supertest": "^4.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"schema.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import '@backstage/backend-common';
|
||||
|
||||
describe('test', () => {
|
||||
it('unbreaks the test runner', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -14,4 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { KafkaApi } from './KafkaApi';
|
||||
export * from './service/router';
|
||||
export * from './types/types';
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import yn from 'yn';
|
||||
import { getRootLogger } from '@backstage/backend-common';
|
||||
import { startStandaloneServer } from './service/standaloneServer';
|
||||
|
||||
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('CTRL+C pressed; exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Kafka, SeekEntry } from 'kafkajs';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export type PartitionOffset = {
|
||||
id: number;
|
||||
offset: string;
|
||||
};
|
||||
|
||||
export type TopicOffset = {
|
||||
topic: string;
|
||||
partitions: PartitionOffset[];
|
||||
};
|
||||
|
||||
export class KafkaApi {
|
||||
private readonly kafka: Kafka;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(clientId: string, brokers: string[], logger: Logger) {
|
||||
logger.info(
|
||||
`creating kafka client with clientId=${clientId} and brokers=${brokers}`,
|
||||
);
|
||||
|
||||
this.kafka = new Kafka({ clientId, brokers });
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
async fetchTopicOffsets(topic: string): Promise<Array<PartitionOffset>> {
|
||||
this.logger.info(`fetching topic offsets for ${topic}`);
|
||||
|
||||
const admin = this.kafka.admin();
|
||||
await admin.connect();
|
||||
|
||||
try {
|
||||
return KafkaApi.toPartitionOffsets(await admin.fetchTopicOffsets(topic));
|
||||
} finally {
|
||||
await admin.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGroupOffsets(groupId: string): Promise<Array<TopicOffset>> {
|
||||
this.logger.info(`fetching consumer group offsets for ${groupId}`);
|
||||
|
||||
const admin = this.kafka.admin();
|
||||
await admin.connect();
|
||||
|
||||
try {
|
||||
let groupOffsets = await admin.fetchOffsets({ groupId });
|
||||
|
||||
return groupOffsets.map(topicOffset => ({
|
||||
topic: topicOffset.topic,
|
||||
partitions: KafkaApi.toPartitionOffsets(topicOffset.partitions),
|
||||
}));
|
||||
} finally {
|
||||
await admin.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private static toPartitionOffsets(
|
||||
result: Array<SeekEntry>,
|
||||
): Array<PartitionOffset> {
|
||||
return result.map(seekEntry => ({
|
||||
id: seekEntry.partition,
|
||||
offset: seekEntry.offset,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { KafkaApi } from './KafkaApi';
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
export const makeRouter = (
|
||||
logger: Logger,
|
||||
kafkaApi: KafkaApi,
|
||||
): express.Router => {
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/topic/:topicId/offsets', async (req, res) => {
|
||||
const topicId = req.params.topicId;
|
||||
try {
|
||||
const response = await kafkaApi.fetchTopicOffsets(topicId);
|
||||
res.send(response);
|
||||
} catch (e) {
|
||||
logger.error(`action=fetchTopicOffsets topicId=${topicId}, error=${e}`);
|
||||
res.status(500).send({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/consumer/:consumerId/offsets', async (req, res) => {
|
||||
const consumerId = req.params.consumerId;
|
||||
try {
|
||||
const response = await kafkaApi.fetchGroupOffsets(consumerId);
|
||||
res.send(response);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`action=fetchGroupOffsets consumerId=${consumerId}, error=${e}`,
|
||||
);
|
||||
res.status(500).send({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const logger = options.logger;
|
||||
|
||||
logger.info('Initializing Kafka backend');
|
||||
|
||||
const clientId = options.config.getString('kafka.clientId');
|
||||
const brokers = options.config.getStringArray('kafka.brokers');
|
||||
|
||||
const kafkaApi = new KafkaApi(clientId, brokers, logger);
|
||||
|
||||
return makeRouter(logger, kafkaApi);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
errorHandler,
|
||||
notFoundHandler,
|
||||
requestLoggingHandler,
|
||||
} from '@backstage/backend-common';
|
||||
import compression from 'compression';
|
||||
import cors from 'cors';
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
export interface ApplicationOptions {
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function createStandaloneApplication(
|
||||
options: ApplicationOptions,
|
||||
): Promise<express.Application> {
|
||||
const { enableCors, logger } = options;
|
||||
const config = new ConfigReader({});
|
||||
const app = express();
|
||||
|
||||
app.use(helmet());
|
||||
if (enableCors) {
|
||||
app.use(cors());
|
||||
}
|
||||
app.use(compression());
|
||||
app.use(express.json());
|
||||
app.use(requestLoggingHandler());
|
||||
app.use('/', await createRouter({ logger, config }));
|
||||
app.use(notFoundHandler());
|
||||
app.use(errorHandler());
|
||||
|
||||
return app;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createStandaloneApplication } from './standaloneApplication';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'kafka-backend' });
|
||||
|
||||
logger.debug('Creating application...');
|
||||
const app = await createStandaloneApplication({
|
||||
enableCors: options.enableCors,
|
||||
logger,
|
||||
});
|
||||
|
||||
logger.debug('Starting application server...');
|
||||
return await new Promise((resolve, reject) => {
|
||||
const server = app.listen(options.port, (err?: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Listening on port ${options.port}`);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TopicOffset, PartitionOffset } from '../service/KafkaApi';
|
||||
|
||||
export type TopicOffsetsResponse = Array<PartitionOffset>;
|
||||
export type ConsumerGroupOffsetsResponse = Array<TopicOffset>;
|
||||
@@ -25,7 +25,6 @@
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"kafkajs": "^1.15.0",
|
||||
"lodash": "^4.17.20",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
@@ -35,6 +34,7 @@
|
||||
"@backstage/cli": "^0.4.2",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@backstage/plugin-kafka-backend": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigApi, DiscoveryApi, createApiRef } from '@backstage/core';
|
||||
import { Kafka, SeekEntry } from 'kafkajs';
|
||||
|
||||
export const kafkaApiRef = createApiRef<KafkaApi>({
|
||||
id: 'plugin.kafka.service',
|
||||
description: 'Used by the Kafka plugin to connect to the Kafka cluster',
|
||||
});
|
||||
|
||||
export type TopicOffsets = {
|
||||
topic: string;
|
||||
partitions: {
|
||||
id: number;
|
||||
offset: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export class KafkaApi {
|
||||
static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) {
|
||||
const clientId =
|
||||
configApi.getOptionalString('kafka.clientId') ?? 'kafka-backstage';
|
||||
const brokers = configApi.getOptionalStringArray('kafka.brokers') ?? [
|
||||
'/kafka/broker',
|
||||
];
|
||||
|
||||
return new KafkaApi(discoveryApi, clientId, brokers);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly discoveryApi: DiscoveryApi,
|
||||
private readonly clientId: string,
|
||||
private readonly brokers: string[],
|
||||
) {}
|
||||
|
||||
async fetchTopicsOffsets(topics: string[]): Promise<Array<TopicOffsets>> {
|
||||
const admin = await this.getAdmin();
|
||||
|
||||
await admin.connect();
|
||||
try {
|
||||
return await Promise.all(
|
||||
topics.map(async topic =>
|
||||
admin
|
||||
.fetchTopicOffsets(topic)
|
||||
.then(result => KafkaApi.toTopicOffsets(topic, result)),
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
await admin.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGroupOffsets(groupId: string): Promise<Array<TopicOffsets>> {
|
||||
const admin = await this.getAdmin();
|
||||
|
||||
await admin.connect();
|
||||
try {
|
||||
let groupOffsets = [
|
||||
{
|
||||
topic: 'shula',
|
||||
partitions: await admin.fetchOffsets({ groupId, topic: 'shula' }),
|
||||
},
|
||||
{
|
||||
topic: 'test',
|
||||
partitions: await admin.fetchOffsets({ groupId, topic: 'test' }),
|
||||
},
|
||||
];
|
||||
|
||||
return groupOffsets.map(topicOffset =>
|
||||
KafkaApi.toTopicOffsets(topicOffset.topic, topicOffset.partitions),
|
||||
);
|
||||
} finally {
|
||||
await admin.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async getAdmin() {
|
||||
const kafka = await this.getKafka();
|
||||
return kafka.admin();
|
||||
}
|
||||
|
||||
private async getKafka() {
|
||||
const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');
|
||||
|
||||
return new Kafka({
|
||||
clientId: this.clientId,
|
||||
brokers: this.brokers.map(brokerUrl => proxyUrl + brokerUrl),
|
||||
});
|
||||
}
|
||||
|
||||
private static toTopicOffsets(topic: string, result: Array<SeekEntry>) {
|
||||
const partitions = result.map(seekEntry => ({
|
||||
id: seekEntry.partition,
|
||||
offset: seekEntry.offset,
|
||||
}));
|
||||
return { topic, partitions };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { KafkaApi } from './types';
|
||||
import {
|
||||
ConsumerGroupOffsetsResponse,
|
||||
TopicOffsetsResponse,
|
||||
} from '@backstage/plugin-kafka-backend';
|
||||
|
||||
export class KafkaBackendClient implements KafkaApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor(options: { discoveryApi: DiscoveryApi }) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
}
|
||||
|
||||
private async getRequired(path: string): Promise<any> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('kafka')}${path}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await response.text();
|
||||
const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async getTopicOffsets(topic: string): Promise<TopicOffsetsResponse> {
|
||||
return await this.getRequired(`/topic/${topic}/offsets`);
|
||||
}
|
||||
|
||||
async getConsumerGroupOffsets(
|
||||
consumerGroup: string,
|
||||
): Promise<ConsumerGroupOffsetsResponse> {
|
||||
return await this.getRequired(`/consumer/${consumerGroup}/offsets`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core';
|
||||
import {
|
||||
ConsumerGroupOffsetsResponse,
|
||||
TopicOffsetsResponse,
|
||||
} from '@backstage/plugin-kafka-backend';
|
||||
|
||||
export const kafkaApiRef = createApiRef<KafkaApi>({
|
||||
id: 'plugin.kafka.service',
|
||||
description:
|
||||
'Used by the Kafka plugin to make requests to accompanying backend',
|
||||
});
|
||||
|
||||
export interface KafkaApi {
|
||||
getTopicOffsets(topic: string): Promise<TopicOffsetsResponse>;
|
||||
getConsumerGroupOffsets(consumerGroup: string): Promise<ConsumerGroupOffsetsResponse>;
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { errorApiRef, useApi } from '@backstage/core';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { kafkaApiRef } from '../../api/KafkaApi';
|
||||
import { kafkaApiRef } from '../../api/types';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function useConsumerGroupOffsets(groupId: string) {
|
||||
@@ -25,32 +25,32 @@ export function useConsumerGroupOffsets(groupId: string) {
|
||||
|
||||
const { loading, value: topics, retry } = useAsyncRetry(async () => {
|
||||
try {
|
||||
const groupOnlyOffsets = await api.fetchGroupOffsets(groupId);
|
||||
const topicOffsets = _.keyBy(
|
||||
await api.fetchTopicsOffsets(
|
||||
groupOnlyOffsets.map(value => value.topic),
|
||||
),
|
||||
offsets => offsets.topic,
|
||||
);
|
||||
const groupOffsets = await api.getConsumerGroupOffsets(groupId);
|
||||
const groupWithTopicOffsets = await Promise.all(
|
||||
groupOffsets.map(async ({ topic, partitions }) => {
|
||||
debugger;
|
||||
const topicOffsets = _.keyBy(
|
||||
await api.getTopicOffsets(topic),
|
||||
partition => partition.id,
|
||||
);
|
||||
|
||||
return groupOnlyOffsets.flatMap(value => {
|
||||
let topicPartitionOffsets = _.keyBy(
|
||||
topicOffsets[value.topic].partitions,
|
||||
partition => partition.id,
|
||||
);
|
||||
return value.partitions.map(partition => ({
|
||||
topic: value.topic,
|
||||
partitionId: partition.id,
|
||||
groupOffset: partition.offset,
|
||||
topicOffset: topicPartitionOffsets[partition.id].offset,
|
||||
}));
|
||||
});
|
||||
return partitions.map(partition => ({
|
||||
topic: topic,
|
||||
partitionId: partition.id,
|
||||
groupOffset: partition.offset,
|
||||
topicOffset: topicOffsets[partition.id].offset,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
return groupWithTopicOffsets.flat();
|
||||
} catch (e) {
|
||||
errorApi.post(e);
|
||||
throw e;
|
||||
}
|
||||
}, [api, errorApi, groupId]);
|
||||
|
||||
debugger;
|
||||
|
||||
return [
|
||||
{
|
||||
loading,
|
||||
|
||||
@@ -16,4 +16,3 @@
|
||||
export { plugin } from './plugin';
|
||||
export { KAFKA_CONSUMER_GROUP_ANNOTATION } from './constants';
|
||||
export { Router, isPluginApplicableToEntity } from './Router';
|
||||
export * from './api';
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
createRouteRef,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core';
|
||||
import { KafkaApi, kafkaApiRef } from './api/KafkaApi';
|
||||
import { KafkaBackendClient } from './api/KafkaBackendClient';
|
||||
import { kafkaApiRef } from './api/types';
|
||||
|
||||
export const rootCatalogKafkaRouteRef = createRouteRef({
|
||||
path: '*',
|
||||
@@ -33,8 +34,7 @@ export const plugin = createPlugin({
|
||||
createApiFactory({
|
||||
api: kafkaApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, configApi: configApiRef },
|
||||
factory: ({ configApi, discoveryApi }) =>
|
||||
KafkaApi.fromConfig(configApi, discoveryApi),
|
||||
factory: ({ discoveryApi }) => new KafkaBackendClient({ discoveryApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1644,11 +1644,9 @@
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@backstage/catalog-model@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576"
|
||||
integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA==
|
||||
version "0.6.0"
|
||||
dependencies:
|
||||
"@backstage/config" "^0.1.1"
|
||||
"@backstage/config" "^0.1.2"
|
||||
"@types/json-schema" "^7.0.5"
|
||||
"@types/yup" "^0.29.8"
|
||||
json-schema "^0.2.5"
|
||||
@@ -1657,11 +1655,9 @@
|
||||
yup "^0.29.3"
|
||||
|
||||
"@backstage/catalog-model@^0.3.0":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.3.1.tgz#45d08e2f333c9c566b2bf2629fd707fe989bb404"
|
||||
integrity sha512-9XhV7c4rmVW+Yzj2PiwTQ7DsegWGB3C4ELsDRExuEVZONdqNcC02cyJtrt3fT5F31ZS3tHkB9bMUymFOBLqUSA==
|
||||
version "0.6.0"
|
||||
dependencies:
|
||||
"@backstage/config" "^0.1.1"
|
||||
"@backstage/config" "^0.1.2"
|
||||
"@types/json-schema" "^7.0.5"
|
||||
"@types/yup" "^0.29.8"
|
||||
json-schema "^0.2.5"
|
||||
@@ -1670,17 +1666,16 @@
|
||||
yup "^0.29.3"
|
||||
|
||||
"@backstage/core@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.npmjs.org/@backstage/core/-/core-0.3.2.tgz#a8209126d5076cf4a8b9bd632fe4e5e2edb62916"
|
||||
integrity sha512-i5d+Wh8js4qEWoAsPY5L7HVSWpumr1OhfF2dUCGYdyW6AMqVJPca6+n6zp1Rg2CO+J9norp44XAVVCbyhtUpig==
|
||||
version "0.4.1"
|
||||
dependencies:
|
||||
"@backstage/config" "^0.1.1"
|
||||
"@backstage/core-api" "^0.2.1"
|
||||
"@backstage/theme" "^0.2.1"
|
||||
"@backstage/config" "^0.1.2"
|
||||
"@backstage/core-api" "^0.2.6"
|
||||
"@backstage/theme" "^0.2.2"
|
||||
"@material-ui/core" "^4.11.0"
|
||||
"@material-ui/icons" "^4.9.1"
|
||||
"@material-ui/lab" "4.0.0-alpha.45"
|
||||
"@types/dagre" "^0.7.44"
|
||||
"@types/prop-types" "^15.7.3"
|
||||
"@types/react" "^16.9"
|
||||
"@types/react-sparklines" "^1.7.0"
|
||||
classnames "^2.2.6"
|
||||
@@ -1958,6 +1953,15 @@
|
||||
debug "^3.1.0"
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@dabh/diagnostics@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31"
|
||||
integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==
|
||||
dependencies:
|
||||
colorspace "1.1.x"
|
||||
enabled "2.0.x"
|
||||
kuler "^2.0.0"
|
||||
|
||||
"@date-io/core@1.x", "@date-io/core@^1.3.13":
|
||||
version "1.3.13"
|
||||
resolved "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa"
|
||||
@@ -7679,7 +7683,7 @@ async@^2.6.1, async@^2.6.2:
|
||||
dependencies:
|
||||
lodash "^4.17.14"
|
||||
|
||||
async@^3.2.0:
|
||||
async@^3.1.0, async@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
||||
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
|
||||
@@ -11432,6 +11436,11 @@ enabled@1.0.x:
|
||||
dependencies:
|
||||
env-variable "0.0.x"
|
||||
|
||||
enabled@2.0.x:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
|
||||
integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
@@ -12468,6 +12477,11 @@ fecha@^2.3.3:
|
||||
resolved "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
|
||||
integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==
|
||||
|
||||
fecha@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41"
|
||||
integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==
|
||||
|
||||
fetch-readablestream@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795"
|
||||
@@ -12705,6 +12719,11 @@ fn-name@~3.0.0:
|
||||
resolved "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
|
||||
integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==
|
||||
|
||||
fn.name@1.x.x:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
|
||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||
|
||||
follow-redirects@1.5.10:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||
@@ -16198,10 +16217,10 @@ jwt-decode@*, jwt-decode@^3.1.0:
|
||||
resolved "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
||||
|
||||
kafkajs@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.npmjs.org/kafkajs/-/kafkajs-1.15.0.tgz#a5ada0d933edca2149177393562be6fb0875ec3a"
|
||||
integrity sha512-yjPyEnQCkPxAuQLIJnY5dI+xnmmgXmhuOQ1GVxClG5KTOV/rJcW1qA3UfvyEJKTp/RTSqQnUR3HJsKFvHyTpNg==
|
||||
kafkajs@^1.16.0-beta.6:
|
||||
version "1.16.0-beta.6"
|
||||
resolved "https://registry.npmjs.org/kafkajs/-/kafkajs-1.16.0-beta.6.tgz#650f4d16abd60516aa0c375be613368391216df1"
|
||||
integrity sha512-R4DT3s7oCAoxdq3tN8w2WkUxLXcZgLQSjQVSmEZpEmT6hVdZ5AUZWqyLYr1IRbQowsJK4Z7eWJAk68J0JiRlUw==
|
||||
|
||||
keyv@^3.0.0:
|
||||
version "3.1.0"
|
||||
@@ -16291,6 +16310,11 @@ kuler@1.0.x:
|
||||
dependencies:
|
||||
colornames "^1.1.1"
|
||||
|
||||
kuler@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
|
||||
integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
|
||||
|
||||
latest-version@5.1.0, latest-version@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
|
||||
@@ -16856,6 +16880,17 @@ logform@^2.1.1:
|
||||
ms "^2.1.1"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
logform@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2"
|
||||
integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==
|
||||
dependencies:
|
||||
colors "^1.2.1"
|
||||
fast-safe-stringify "^2.0.4"
|
||||
fecha "^4.2.0"
|
||||
ms "^2.1.1"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
loglevel@^1.6.7, loglevel@^1.6.8:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
|
||||
@@ -18540,6 +18575,13 @@ one-time@0.0.4:
|
||||
resolved "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
|
||||
integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=
|
||||
|
||||
one-time@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
|
||||
integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
|
||||
dependencies:
|
||||
fn.name "1.x.x"
|
||||
|
||||
onetime@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
|
||||
@@ -21188,7 +21230,7 @@ read@1, read@~1.0.1:
|
||||
dependencies:
|
||||
mute-stream "~0.0.4"
|
||||
|
||||
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
@@ -25211,6 +25253,14 @@ winston-transport@^4.3.0:
|
||||
readable-stream "^2.3.6"
|
||||
triple-beam "^1.2.0"
|
||||
|
||||
winston-transport@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59"
|
||||
integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==
|
||||
dependencies:
|
||||
readable-stream "^2.3.7"
|
||||
triple-beam "^1.2.0"
|
||||
|
||||
winston@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07"
|
||||
@@ -25226,6 +25276,21 @@ winston@^3.2.1:
|
||||
triple-beam "^1.3.0"
|
||||
winston-transport "^4.3.0"
|
||||
|
||||
winston@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"
|
||||
integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==
|
||||
dependencies:
|
||||
"@dabh/diagnostics" "^2.0.2"
|
||||
async "^3.1.0"
|
||||
is-stream "^2.0.0"
|
||||
logform "^2.2.0"
|
||||
one-time "^1.0.0"
|
||||
readable-stream "^3.4.0"
|
||||
stack-trace "0.0.x"
|
||||
triple-beam "^1.3.0"
|
||||
winston-transport "^4.4.0"
|
||||
|
||||
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
|
||||
Reference in New Issue
Block a user