diff --git a/ingestion/src/metadata/ingestion/processor/query_parser.py b/ingestion/src/metadata/ingestion/processor/query_parser.py index aaffee72bb5..ecbba14008b 100644 --- a/ingestion/src/metadata/ingestion/processor/query_parser.py +++ b/ingestion/src/metadata/ingestion/processor/query_parser.py @@ -74,6 +74,7 @@ def parse_sql_statement(record: TableQuery) -> Optional[ParsedData]: databaseName=record.databaseName, databaseSchema=record.databaseSchema, sql=record.query, + userName=record.userName, date=start_date.__root__.strftime("%Y-%m-%d"), serviceName=record.serviceName, ) diff --git a/ingestion/src/metadata/ingestion/stage/table_usage.py b/ingestion/src/metadata/ingestion/stage/table_usage.py index 48d1b3f2a2d..ad8a0b3984c 100644 --- a/ingestion/src/metadata/ingestion/stage/table_usage.py +++ b/ingestion/src/metadata/ingestion/stage/table_usage.py @@ -23,9 +23,12 @@ from metadata.generated.schema.entity.data.table import SqlQuery from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import ( OpenMetadataConnection, ) +from metadata.generated.schema.entity.teams.user import User +from metadata.generated.schema.type.entityReference import EntityReference from metadata.generated.schema.type.queryParserData import QueryParserData from metadata.generated.schema.type.tableUsageCount import TableUsageCount from metadata.ingestion.api.stage import Stage, StageStatus +from metadata.ingestion.ometa.ometa_api import OpenMetadata from metadata.utils.constants import UTF_8 from metadata.utils.logger import ingestion_logger @@ -54,6 +57,7 @@ class TableUsageStage(Stage[QueryParserData]): ): self.config = config self.metadata_config = metadata_config + self.metadata = OpenMetadata(self.metadata_config) self.status = StageStatus() self.table_usage = {} self.table_queries = {} @@ -70,11 +74,41 @@ class TableUsageStage(Stage[QueryParserData]): config = TableStageConfig.parse_obj(config_dict) return cls(config, metadata_config) + def _get_user_entity(self, username: str): + if username: + user = self.metadata.get_by_name(entity=User, fqn=username) + if user: + return [ + EntityReference( + id=user.id, + type="user", + name=user.name.__root__, + fullyQualifiedName=user.fullyQualifiedName.__root__, + description=user.description, + displayName=user.displayName, + deleted=user.deleted, + href=user.href, + ) + ] + return [] + def _add_sql_query(self, record, table): if self.table_queries.get((table, record.date)): - self.table_queries[(table, record.date)].append(SqlQuery(query=record.sql)) + self.table_queries[(table, record.date)].append( + SqlQuery( + query=record.sql, + users=self._get_user_entity(record.userName), + queryDate=record.date, + ) + ) else: - self.table_queries[(table, record.date)] = [SqlQuery(query=record.sql)] + self.table_queries[(table, record.date)] = [ + SqlQuery( + query=record.sql, + users=self._get_user_entity(record.userName), + queryDate=record.date, + ) + ] def stage_record(self, record: QueryParserData) -> None: if not record or not record.parsedData: diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java index 0ff8ae820ef..d19cfad08d3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -380,6 +381,14 @@ public class TableRepository extends EntityRepository { storedMapQueries.put(q.getChecksum(), q); } } + SQLQuery oldQuery = storedMapQueries.get(query.getChecksum()); + if (oldQuery != null && query.getUsers() != null) { + // Merge old and new users + List userList = query.getUsers(); + userList.addAll(oldQuery.getUsers()); + HashSet userSet = new HashSet<>(userList); + query.setUsers(new ArrayList<>(userSet)); + } storedMapQueries.put(query.getChecksum(), query); List updatedQueries = new ArrayList<>(storedMapQueries.values()); daoCollection diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json b/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json index 8bbe6203b0f..687d422dffd 100644 --- a/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json +++ b/openmetadata-spec/src/main/resources/json/schema/entity/data/table.json @@ -578,9 +578,12 @@ "description": "How long did the query took to run in seconds.", "type": "number" }, - "user": { - "description": "User who ran this query.", - "$ref": "../../type/entityReference.json", + "users": { + "description": "List of users who ran this query.", + "type":"array", + "items": { + "$ref": "../../type/entityReference.json" + }, "default": null }, "vote": { diff --git a/openmetadata-spec/src/main/resources/json/schema/type/queryParserData.json b/openmetadata-spec/src/main/resources/json/schema/type/queryParserData.json index ea311a81c23..f99a07af78f 100644 --- a/openmetadata-spec/src/main/resources/json/schema/type/queryParserData.json +++ b/openmetadata-spec/src/main/resources/json/schema/type/queryParserData.json @@ -38,6 +38,10 @@ "description": "Name that identifies this database service.", "type": "string" }, + "userName": { + "description": "Name of the user that executed the SQL query", + "type": "string" + }, "date": { "description": "Date of execution of SQL query", "type": "string" diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.test.tsx index c8eb3a6982d..24c228bc6fe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.test.tsx @@ -14,7 +14,7 @@ import { findByTestId, findByText, - getByTestId, + // getByTestId, queryByTestId, render, } from '@testing-library/react'; @@ -25,13 +25,15 @@ import QueryCard from './QueryCard'; const mockQueryData = { query: 'select products from raw_product_catalog', duration: 0.309, - user: { - id: 'd4785e53-bbdb-4dbd-b368-009fdb50c2c6', - type: 'user', - name: 'aaron_johnson0', - displayName: 'Aaron Johnson', - href: 'http://localhost:8585/api/v1/users/d4785e53-bbdb-4dbd-b368-009fdb50c2c6', - }, + users: [ + { + id: 'd4785e53-bbdb-4dbd-b368-009fdb50c2c6', + type: 'user', + name: 'aaron_johnson0', + displayName: 'Aaron Johnson', + href: 'http://localhost:8585/api/v1/users/d4785e53-bbdb-4dbd-b368-009fdb50c2c6', + }, + ], vote: 1, checksum: '0232b0368458aadb29230ccc531462c9', }; @@ -49,7 +51,7 @@ describe('Test QueryCard Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const queryHeader = getByTestId(container, 'query-header'); + // const queryHeader = getByTestId(container, 'query-header'); const query = await findByText(container, /SchemaEditor/i); const copyQueryButton = await findByText( container, @@ -61,7 +63,7 @@ describe('Test QueryCard Component', () => { 'expand-collapse-button' ); - expect(queryHeader).toBeInTheDocument(); + // expect(queryHeader).toBeInTheDocument(); expect(query).toBeInTheDocument(); expect(copyQueryButton).toBeInTheDocument(); expect(expandButton).toBeInTheDocument(); @@ -69,7 +71,7 @@ describe('Test QueryCard Component', () => { it('Should not render header if user is undefined', async () => { const { container } = render( - , + , { wrapper: MemoryRouter, } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.tsx index 04f5fe6b69c..255cca44208 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryCard.tsx @@ -12,10 +12,10 @@ */ import classNames from 'classnames'; -import { isUndefined } from 'lodash'; +// import { isUndefined } from 'lodash'; import React, { FC, HTMLAttributes, useState } from 'react'; -import { Link } from 'react-router-dom'; -import { getUserPath } from '../../constants/constants'; +// import { Link } from 'react-router-dom'; +// import { getUserPath } from '../../constants/constants'; import { CSMode } from '../../enums/codemirror.enum'; import { SQLQuery } from '../../generated/entity/data/table'; import SVGIcons, { Icons } from '../../utils/SvgUtils'; @@ -34,7 +34,7 @@ const QueryCard: FC = ({ className, query }) => { className )} onClick={() => setExpanded((pre) => !pre)}> - {!isUndefined(query.user) && !isUndefined(query.duration) ? ( + {/* {!isUndefined(query.user) && !isUndefined(query.duration) ? (

Last run by{' '} @@ -49,7 +49,7 @@ const QueryCard: FC = ({ className, query }) => { {query.duration} seconds

- ) : null} + ) : null} */}