diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json
index f5dc01ec45..098d9a426c 100644
--- a/plugins/kafka/package.json
+++ b/plugins/kafka/package.json
@@ -23,9 +23,10 @@
"@backstage/core": "^0.4.1",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
- "@material-ui/icons": "^4.9.1",
+ "@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",
"react-use": "^15.3.3"
@@ -38,6 +39,7 @@
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
+ "@types/lodash": "^4.14.166",
"@types/node": "^12.0.0",
"cross-fetch": "^3.0.6",
"msw": "^0.21.2"
diff --git a/plugins/kafka/src/Router.tsx b/plugins/kafka/src/Router.tsx
new file mode 100644
index 0000000000..9de9588bd6
--- /dev/null
+++ b/plugins/kafka/src/Router.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 { Entity } from '@backstage/catalog-model';
+import { MissingAnnotationEmptyState } from '@backstage/core';
+import React from 'react';
+import { Route, Routes } from 'react-router';
+import { KAFKA_CONSUMER_GROUP_ANNOTATION } from './constants';
+import { KafkaTopicsForConsumer } from './components/ConsumerGroupOffsets/ConsumerGroupOffsets';
+
+export const isPluginApplicableToEntity = (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[KAFKA_CONSUMER_GROUP_ANNOTATION]);
+
+export const Router = ({ entity }: { entity: Entity }) => {
+ return !isPluginApplicableToEntity(entity) ? (
+
+ ) : (
+
+ }
+ />
+
+ );
+};
diff --git a/plugins/kafka/src/api/KafkaApi.ts b/plugins/kafka/src/api/KafkaApi.ts
new file mode 100644
index 0000000000..63ac948047
--- /dev/null
+++ b/plugins/kafka/src/api/KafkaApi.ts
@@ -0,0 +1,112 @@
+/*
+ * 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({
+ 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> {
+ 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> {
+ 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) {
+ const partitions = result.map(seekEntry => ({
+ id: seekEntry.partition,
+ offset: seekEntry.offset,
+ }));
+ return { topic, partitions };
+ }
+}
diff --git a/plugins/kafka/src/components/ExampleComponent/index.ts b/plugins/kafka/src/api/index.ts
similarity index 92%
rename from plugins/kafka/src/components/ExampleComponent/index.ts
rename to plugins/kafka/src/api/index.ts
index 2c6f338ec7..cd78c09cad 100644
--- a/plugins/kafka/src/components/ExampleComponent/index.ts
+++ b/plugins/kafka/src/api/index.ts
@@ -13,4 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { default } from './ExampleComponent';
+
+export { KafkaApi } from './KafkaApi';
diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx
new file mode 100644
index 0000000000..0f697e4994
--- /dev/null
+++ b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx
@@ -0,0 +1,111 @@
+/*
+ * 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 { Table, TableColumn } from '@backstage/core';
+import { Box, Typography } from '@material-ui/core';
+import RetryIcon from '@material-ui/icons/Replay';
+import React from 'react';
+import { useConsumerGroupFromEntity } from './useConsumerGroupsFromEntity';
+import { useConsumerGroupOffsets } from './useConsumerGroupOffsets';
+
+export type TopicPartitionInfo = {
+ topic: string;
+ partitionId: number;
+ topicOffset: string;
+ groupOffset: string;
+};
+
+const generatedColumns: TableColumn[] = [
+ {
+ title: 'Topic',
+ field: 'topic',
+ highlight: true,
+ render: (row: Partial) => {
+ return <>{row.topic ?? ''}>;
+ },
+ },
+ {
+ title: 'Partition',
+ field: 'partitionId',
+ render: (row: Partial) => {
+ return <>{row.partitionId ?? ''}>;
+ },
+ },
+ {
+ title: 'Topic Offset',
+ field: 'topicOffset',
+ render: (row: Partial) => {
+ return <>{row.topicOffset ?? ''}>;
+ },
+ },
+ {
+ title: 'Group Offset',
+ field: 'groupOffset',
+ render: (row: Partial) => {
+ return <>{row.groupOffset ?? ''}>;
+ },
+ },
+];
+
+type Props = {
+ loading: boolean;
+ retry: () => void;
+ consumerGroup: string;
+ topics?: TopicPartitionInfo[];
+};
+
+export const ConsumerGroupOffsets = ({
+ loading,
+ topics,
+ consumerGroup,
+ retry,
+}: Props) => {
+ return (
+ ,
+ tooltip: 'Refresh Data',
+ isFreeAction: true,
+ onClick: () => retry(),
+ },
+ ]}
+ data={topics ?? []}
+ title={
+
+
+ Consumed Topics for {consumerGroup}
+
+
+ }
+ columns={generatedColumns}
+ />
+ );
+};
+
+export const KafkaTopicsForConsumer = () => {
+ const consumerGroup = useConsumerGroupFromEntity();
+ const [tableProps, { retry }] = useConsumerGroupOffsets(consumerGroup);
+
+ return (
+
+ );
+};
diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupOffsets.ts b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupOffsets.ts
new file mode 100644
index 0000000000..95c6e0892d
--- /dev/null
+++ b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupOffsets.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { errorApiRef, useApi } from '@backstage/core';
+import { useAsyncRetry } from 'react-use';
+import { kafkaApiRef } from '../../api/KafkaApi';
+import _ from 'lodash';
+
+export function useConsumerGroupOffsets(groupId: string) {
+ const api = useApi(kafkaApiRef);
+ const errorApi = useApi(errorApiRef);
+
+ 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,
+ );
+
+ 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,
+ }));
+ });
+ } catch (e) {
+ errorApi.post(e);
+ throw e;
+ }
+ }, [api, errorApi, groupId]);
+
+ return [
+ {
+ loading,
+ topics,
+ },
+ {
+ retry,
+ },
+ ] as const;
+}
diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsFromEntity.ts b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsFromEntity.ts
new file mode 100644
index 0000000000..8bad96b61c
--- /dev/null
+++ b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsFromEntity.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { useEntity } from '@backstage/plugin-catalog';
+import { KAFKA_CONSUMER_GROUP_ANNOTATION } from '../../constants';
+
+export const useConsumerGroupFromEntity = () => {
+ const { entity } = useEntity();
+
+ return entity.metadata.annotations?.[KAFKA_CONSUMER_GROUP_ANNOTATION] ?? '';
+};
diff --git a/plugins/kafka/src/components/ExampleComponent/ExampleComponent.test.tsx b/plugins/kafka/src/components/ExampleComponent/ExampleComponent.test.tsx
deleted file mode 100644
index 680714eeda..0000000000
--- a/plugins/kafka/src/components/ExampleComponent/ExampleComponent.test.tsx
+++ /dev/null
@@ -1,45 +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 React from 'react';
-import { render } from '@testing-library/react';
-import ExampleComponent from './ExampleComponent';
-import { ThemeProvider } from '@material-ui/core';
-import { lightTheme } from '@backstage/theme';
-import { rest } from 'msw';
-import { setupServer } from 'msw/node';
-import { msw } from '@backstage/test-utils';
-
-describe('ExampleComponent', () => {
- const server = setupServer();
- // Enable sane handlers for network requests
- msw.setupDefaultHandlers(server);
-
- // setup mock response
- beforeEach(() => {
- server.use(
- rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
- );
- });
-
- it('should render', () => {
- const rendered = render(
-
-
- ,
- );
- expect(rendered.getByText('Welcome to kafka!')).toBeInTheDocument();
- });
-});
diff --git a/plugins/kafka/src/components/ExampleComponent/ExampleComponent.tsx b/plugins/kafka/src/components/ExampleComponent/ExampleComponent.tsx
deleted file mode 100644
index cfa36acd7b..0000000000
--- a/plugins/kafka/src/components/ExampleComponent/ExampleComponent.tsx
+++ /dev/null
@@ -1,55 +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 React from 'react';
-import { Typography, Grid } from '@material-ui/core';
-import {
- InfoCard,
- Header,
- Page,
- Content,
- ContentHeader,
- HeaderLabel,
- SupportButton,
-} from '@backstage/core';
-import ExampleFetchComponent from '../ExampleFetchComponent';
-
-const ExampleComponent = () => (
-
-
-
-
- A description of your plugin goes here.
-
-
-
-
-
- All content should be wrapped in a card like this.
-
-
-
-
-
-
-
-
-
-);
-
-export default ExampleComponent;
diff --git a/plugins/kafka/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx b/plugins/kafka/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx
deleted file mode 100644
index 0a73ed0fc1..0000000000
--- a/plugins/kafka/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx
+++ /dev/null
@@ -1,40 +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 React from 'react';
-import { render } from '@testing-library/react';
-import ExampleFetchComponent from './ExampleFetchComponent';
-import { rest } from 'msw';
-import { setupServer } from 'msw/node';
-import { msw } from '@backstage/test-utils';
-
-describe('ExampleFetchComponent', () => {
- const server = setupServer();
- // Enable sane handlers for network requests
- msw.setupDefaultHandlers(server);
-
- // setup mock response
- beforeEach(() => {
- server.use(
- rest.get('https://randomuser.me/*', (_, res, ctx) =>
- res(ctx.status(200), ctx.delay(2000), ctx.json({})),
- ),
- );
- });
- it('should render', async () => {
- const rendered = render();
- expect(await rendered.findByTestId('progress')).toBeInTheDocument();
- });
-});
diff --git a/plugins/kafka/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx b/plugins/kafka/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx
deleted file mode 100644
index 8a4aef11b8..0000000000
--- a/plugins/kafka/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx
+++ /dev/null
@@ -1,107 +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 React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
-import { Table, TableColumn, Progress } from '@backstage/core';
-import Alert from '@material-ui/lab/Alert';
-import { useAsync } from 'react-use';
-
-const useStyles = makeStyles({
- avatar: {
- height: 32,
- width: 32,
- borderRadius: '50%',
- },
-});
-
-type User = {
- gender: string; // "male"
- name: {
- title: string; // "Mr",
- first: string; // "Duane",
- last: string; // "Reed"
- };
- location: object; // {street: {number: 5060, name: "Hickory Creek Dr"}, city: "Albany", state: "New South Wales",…}
- email: string; // "duane.reed@example.com"
- login: object; // {uuid: "4b785022-9a23-4ab9-8a23-cb3fb43969a9", username: "blackdog796", password: "patch",…}
- dob: object; // {date: "1983-06-22T12:30:23.016Z", age: 37}
- registered: object; // {date: "2006-06-13T18:48:28.037Z", age: 14}
- phone: string; // "07-2154-5651"
- cell: string; // "0405-592-879"
- id: {
- name: string; // "TFN",
- value: string; // "796260432"
- };
- picture: { medium: string }; // {medium: "https://randomuser.me/api/portraits/men/95.jpg",…}
- nat: string; // "AU"
-};
-
-type DenseTableProps = {
- users: User[];
-};
-
-export const DenseTable = ({ users }: DenseTableProps) => {
- const classes = useStyles();
-
- const columns: TableColumn[] = [
- { title: 'Avatar', field: 'avatar' },
- { title: 'Name', field: 'name' },
- { title: 'Email', field: 'email' },
- { title: 'Nationality', field: 'nationality' },
- ];
-
- const data = users.map(user => {
- return {
- avatar: (
-
- ),
- name: `${user.name.first} ${user.name.last}`,
- email: user.email,
- nationality: user.nat,
- };
- });
-
- return (
-
- );
-};
-
-const ExampleFetchComponent = () => {
- const { value, loading, error } = useAsync(async (): Promise => {
- const response = await fetch('https://randomuser.me/api/?results=20');
- const data = await response.json();
- return data.results;
- }, []);
-
- if (loading) {
- return ;
- } else if (error) {
- return {error.message};
- }
-
- return ;
-};
-
-export default ExampleFetchComponent;
diff --git a/plugins/kafka/src/components/ExampleFetchComponent/index.ts b/plugins/kafka/src/constants.ts
similarity index 87%
rename from plugins/kafka/src/components/ExampleFetchComponent/index.ts
rename to plugins/kafka/src/constants.ts
index e0d021564d..652911a91f 100644
--- a/plugins/kafka/src/components/ExampleFetchComponent/index.ts
+++ b/plugins/kafka/src/constants.ts
@@ -13,4 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { default } from './ExampleFetchComponent';
+export const KAFKA_CONSUMER_GROUP_ANNOTATION =
+ 'kafka.apache.org/consumer-groups';
diff --git a/plugins/kafka/src/index.ts b/plugins/kafka/src/index.ts
index 224e293890..80670acb81 100644
--- a/plugins/kafka/src/index.ts
+++ b/plugins/kafka/src/index.ts
@@ -14,3 +14,6 @@
* limitations under the License.
*/
export { plugin } from './plugin';
+export { KAFKA_CONSUMER_GROUP_ANNOTATION } from './constants';
+export { Router, isPluginApplicableToEntity } from './Router';
+export * from './api';
diff --git a/plugins/kafka/src/plugin.ts b/plugins/kafka/src/plugin.ts
index f0bc22481e..a666defa44 100644
--- a/plugins/kafka/src/plugin.ts
+++ b/plugins/kafka/src/plugin.ts
@@ -13,8 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { createPlugin, createRouteRef } from '@backstage/core';
-import ExampleComponent from './components/ExampleComponent';
+import {
+ configApiRef,
+ createApiFactory,
+ createPlugin,
+ createRouteRef,
+ discoveryApiRef,
+} from '@backstage/core';
+import { KafkaApi, kafkaApiRef } from './api/KafkaApi';
export const rootRouteRef = createRouteRef({
path: '/kafka',
@@ -23,7 +29,12 @@ export const rootRouteRef = createRouteRef({
export const plugin = createPlugin({
id: 'kafka',
- register({ router }) {
- router.addRoute(rootRouteRef, ExampleComponent);
- },
+ apis: [
+ createApiFactory({
+ api: kafkaApiRef,
+ deps: { discoveryApi: discoveryApiRef, configApi: configApiRef },
+ factory: ({ configApi, discoveryApi }) =>
+ KafkaApi.fromConfig(configApi, discoveryApi),
+ }),
+ ],
});
diff --git a/yarn.lock b/yarn.lock
index bb8a845850..0ad51ca447 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3734,6 +3734,13 @@
react-is "^16.8.0"
react-transition-group "^4.4.0"
+"@material-ui/icons@^4.11.2":
+ version "4.11.2"
+ resolved "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5"
+ integrity sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+
"@material-ui/icons@^4.9.1":
version "4.9.1"
resolved "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz#fdeadf8cb3d89208945b33dbc50c7c616d0bd665"
@@ -5866,6 +5873,11 @@
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f"
integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==
+"@types/lodash@^4.14.166":
+ version "4.14.166"
+ resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.166.tgz#07e7f2699a149219dbc3c35574f126ec8737688f"
+ integrity sha512-A3YT/c1oTlyvvW/GQqG86EyqWNrT/tisOIh2mW3YCgcx71TNjiTZA3zYZWA5BCmtsOTXjhliy4c4yEkErw6njA==
+
"@types/long@^4.0.0":
version "4.0.1"
resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"