mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-12 09:18:20 +00:00
parent
ef5108c47b
commit
fa68a1f18f
@ -74,6 +74,7 @@ def parse_sql_statement(record: TableQuery) -> Optional[ParsedData]:
|
|||||||
databaseName=record.databaseName,
|
databaseName=record.databaseName,
|
||||||
databaseSchema=record.databaseSchema,
|
databaseSchema=record.databaseSchema,
|
||||||
sql=record.query,
|
sql=record.query,
|
||||||
|
userName=record.userName,
|
||||||
date=start_date.__root__.strftime("%Y-%m-%d"),
|
date=start_date.__root__.strftime("%Y-%m-%d"),
|
||||||
serviceName=record.serviceName,
|
serviceName=record.serviceName,
|
||||||
)
|
)
|
||||||
|
@ -23,9 +23,12 @@ from metadata.generated.schema.entity.data.table import SqlQuery
|
|||||||
from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
|
from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
|
||||||
OpenMetadataConnection,
|
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.queryParserData import QueryParserData
|
||||||
from metadata.generated.schema.type.tableUsageCount import TableUsageCount
|
from metadata.generated.schema.type.tableUsageCount import TableUsageCount
|
||||||
from metadata.ingestion.api.stage import Stage, StageStatus
|
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.constants import UTF_8
|
||||||
from metadata.utils.logger import ingestion_logger
|
from metadata.utils.logger import ingestion_logger
|
||||||
|
|
||||||
@ -54,6 +57,7 @@ class TableUsageStage(Stage[QueryParserData]):
|
|||||||
):
|
):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.metadata_config = metadata_config
|
self.metadata_config = metadata_config
|
||||||
|
self.metadata = OpenMetadata(self.metadata_config)
|
||||||
self.status = StageStatus()
|
self.status = StageStatus()
|
||||||
self.table_usage = {}
|
self.table_usage = {}
|
||||||
self.table_queries = {}
|
self.table_queries = {}
|
||||||
@ -70,11 +74,41 @@ class TableUsageStage(Stage[QueryParserData]):
|
|||||||
config = TableStageConfig.parse_obj(config_dict)
|
config = TableStageConfig.parse_obj(config_dict)
|
||||||
return cls(config, metadata_config)
|
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):
|
def _add_sql_query(self, record, table):
|
||||||
if self.table_queries.get((table, record.date)):
|
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:
|
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:
|
def stage_record(self, record: QueryParserData) -> None:
|
||||||
if not record or not record.parsedData:
|
if not record or not record.parsedData:
|
||||||
|
@ -39,6 +39,7 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -380,6 +381,14 @@ public class TableRepository extends EntityRepository<Table> {
|
|||||||
storedMapQueries.put(q.getChecksum(), q);
|
storedMapQueries.put(q.getChecksum(), q);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SQLQuery oldQuery = storedMapQueries.get(query.getChecksum());
|
||||||
|
if (oldQuery != null && query.getUsers() != null) {
|
||||||
|
// Merge old and new users
|
||||||
|
List<EntityReference> userList = query.getUsers();
|
||||||
|
userList.addAll(oldQuery.getUsers());
|
||||||
|
HashSet<EntityReference> userSet = new HashSet<>(userList);
|
||||||
|
query.setUsers(new ArrayList<>(userSet));
|
||||||
|
}
|
||||||
storedMapQueries.put(query.getChecksum(), query);
|
storedMapQueries.put(query.getChecksum(), query);
|
||||||
List<SQLQuery> updatedQueries = new ArrayList<>(storedMapQueries.values());
|
List<SQLQuery> updatedQueries = new ArrayList<>(storedMapQueries.values());
|
||||||
daoCollection
|
daoCollection
|
||||||
|
@ -578,9 +578,12 @@
|
|||||||
"description": "How long did the query took to run in seconds.",
|
"description": "How long did the query took to run in seconds.",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"user": {
|
"users": {
|
||||||
"description": "User who ran this query.",
|
"description": "List of users who ran this query.",
|
||||||
"$ref": "../../type/entityReference.json",
|
"type":"array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "../../type/entityReference.json"
|
||||||
|
},
|
||||||
"default": null
|
"default": null
|
||||||
},
|
},
|
||||||
"vote": {
|
"vote": {
|
||||||
|
@ -38,6 +38,10 @@
|
|||||||
"description": "Name that identifies this database service.",
|
"description": "Name that identifies this database service.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"userName": {
|
||||||
|
"description": "Name of the user that executed the SQL query",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"description": "Date of execution of SQL query",
|
"description": "Date of execution of SQL query",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
import {
|
import {
|
||||||
findByTestId,
|
findByTestId,
|
||||||
findByText,
|
findByText,
|
||||||
getByTestId,
|
// getByTestId,
|
||||||
queryByTestId,
|
queryByTestId,
|
||||||
render,
|
render,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
@ -25,13 +25,15 @@ import QueryCard from './QueryCard';
|
|||||||
const mockQueryData = {
|
const mockQueryData = {
|
||||||
query: 'select products from raw_product_catalog',
|
query: 'select products from raw_product_catalog',
|
||||||
duration: 0.309,
|
duration: 0.309,
|
||||||
user: {
|
users: [
|
||||||
id: 'd4785e53-bbdb-4dbd-b368-009fdb50c2c6',
|
{
|
||||||
type: 'user',
|
id: 'd4785e53-bbdb-4dbd-b368-009fdb50c2c6',
|
||||||
name: 'aaron_johnson0',
|
type: 'user',
|
||||||
displayName: 'Aaron Johnson',
|
name: 'aaron_johnson0',
|
||||||
href: 'http://localhost:8585/api/v1/users/d4785e53-bbdb-4dbd-b368-009fdb50c2c6',
|
displayName: 'Aaron Johnson',
|
||||||
},
|
href: 'http://localhost:8585/api/v1/users/d4785e53-bbdb-4dbd-b368-009fdb50c2c6',
|
||||||
|
},
|
||||||
|
],
|
||||||
vote: 1,
|
vote: 1,
|
||||||
checksum: '0232b0368458aadb29230ccc531462c9',
|
checksum: '0232b0368458aadb29230ccc531462c9',
|
||||||
};
|
};
|
||||||
@ -49,7 +51,7 @@ describe('Test QueryCard Component', () => {
|
|||||||
const { container } = render(<QueryCard query={mockQueryData} />, {
|
const { container } = render(<QueryCard query={mockQueryData} />, {
|
||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
});
|
});
|
||||||
const queryHeader = getByTestId(container, 'query-header');
|
// const queryHeader = getByTestId(container, 'query-header');
|
||||||
const query = await findByText(container, /SchemaEditor/i);
|
const query = await findByText(container, /SchemaEditor/i);
|
||||||
const copyQueryButton = await findByText(
|
const copyQueryButton = await findByText(
|
||||||
container,
|
container,
|
||||||
@ -61,7 +63,7 @@ describe('Test QueryCard Component', () => {
|
|||||||
'expand-collapse-button'
|
'expand-collapse-button'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryHeader).toBeInTheDocument();
|
// expect(queryHeader).toBeInTheDocument();
|
||||||
expect(query).toBeInTheDocument();
|
expect(query).toBeInTheDocument();
|
||||||
expect(copyQueryButton).toBeInTheDocument();
|
expect(copyQueryButton).toBeInTheDocument();
|
||||||
expect(expandButton).toBeInTheDocument();
|
expect(expandButton).toBeInTheDocument();
|
||||||
@ -69,7 +71,7 @@ describe('Test QueryCard Component', () => {
|
|||||||
|
|
||||||
it('Should not render header if user is undefined', async () => {
|
it('Should not render header if user is undefined', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<QueryCard query={{ ...mockQueryData, user: undefined }} />,
|
<QueryCard query={{ ...mockQueryData, users: undefined }} />,
|
||||||
{
|
{
|
||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isUndefined } from 'lodash';
|
// import { isUndefined } from 'lodash';
|
||||||
import React, { FC, HTMLAttributes, useState } from 'react';
|
import React, { FC, HTMLAttributes, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
// import { Link } from 'react-router-dom';
|
||||||
import { getUserPath } from '../../constants/constants';
|
// import { getUserPath } from '../../constants/constants';
|
||||||
import { CSMode } from '../../enums/codemirror.enum';
|
import { CSMode } from '../../enums/codemirror.enum';
|
||||||
import { SQLQuery } from '../../generated/entity/data/table';
|
import { SQLQuery } from '../../generated/entity/data/table';
|
||||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||||
@ -34,7 +34,7 @@ const QueryCard: FC<QueryCardProp> = ({ className, query }) => {
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={() => setExpanded((pre) => !pre)}>
|
onClick={() => setExpanded((pre) => !pre)}>
|
||||||
{!isUndefined(query.user) && !isUndefined(query.duration) ? (
|
{/* {!isUndefined(query.user) && !isUndefined(query.duration) ? (
|
||||||
<div data-testid="query-header">
|
<div data-testid="query-header">
|
||||||
<p>
|
<p>
|
||||||
Last run by{' '}
|
Last run by{' '}
|
||||||
@ -49,7 +49,7 @@ const QueryCard: FC<QueryCardProp> = ({ className, query }) => {
|
|||||||
<span className="tw-font-medium">{query.duration} seconds</span>
|
<span className="tw-font-medium">{query.duration} seconds</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null} */}
|
||||||
<div className="tw-border tw-border-main tw-rounded-md tw-p-px">
|
<div className="tw-border tw-border-main tw-rounded-md tw-p-px">
|
||||||
<div
|
<div
|
||||||
className={classNames('tw-overflow-hidden tw-relative', {
|
className={classNames('tw-overflow-hidden tw-relative', {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user