Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #14786: Suggestions API #14821

Merged
merged 58 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d8f7bc2
Fix #14786: Suggestions API
harshach Jan 23, 2024
cd11252
Handle suggestions in ometa
pmbrull Jan 23, 2024
07f3a18
Minor: Optimise Databricks Client (#14776)
ulixius9 Jan 23, 2024
ffbb682
MINOR - Fix SP topology context & Looker usage context (#14816)
pmbrull Jan 23, 2024
00243ea
Fixes #14598: Fix Tags / Labels ingestion on includeTags as False (#1…
ayush-shah Jan 23, 2024
9bd97c0
fix(ui): password error message for char limits (#14808)
chirag-madlani Jan 23, 2024
993dc56
Fixes #13556: Support for Salesforce table description ingestion (#14…
kwgdaig Jan 23, 2024
d8555fc
MINOR - Better handling of Ingestion Pipeline Status (#14792)
pmbrull Jan 23, 2024
cc00da6
MINOR: Added table validation for cost analysis data (#14793)
OnkarVO7 Jan 23, 2024
97440f7
CYPRESS: simplify side navigation click in cypress (#14818)
Ashish8689 Jan 23, 2024
8d61948
fix(#14326): tier dropdown is not working in advance search (#14780)
Abhishek332 Jan 23, 2024
98a1448
fix(minor): update skip icon for executions (#14809)
chirag-madlani Jan 23, 2024
a6f6fff
Fixes #14803: ignore capitalization when confirming deletes (#14804)
luzlab Jan 23, 2024
6fa3eb4
minor(config): update openmetadata-ui code reviewers (#14823)
chirag-madlani Jan 23, 2024
0b80504
Add Tests
harshach Jan 23, 2024
9188937
Add list/accept/reject apis
harshach Jan 24, 2024
9305724
initial ui changes
karanh37 Jan 24, 2024
6ddea67
localisation
karanh37 Jan 24, 2024
1b278be
Merge branch 'main' into suggestions
karanh37 Jan 25, 2024
84a9213
show suggestion for empty description
karanh37 Jan 25, 2024
05af100
ui feedbacks
karanh37 Jan 25, 2024
ddabf4d
Fix permission check for entities without owner
harshach Jan 25, 2024
cdce603
Fix entityLink and add tests
pmbrull Jan 26, 2024
d6df074
Add update suggestion WIP
pmbrull Jan 26, 2024
db88279
Fix test
pmbrull Jan 26, 2024
22c61e7
Merge remote-tracking branch 'upstream/main' into suggestions
pmbrull Jan 26, 2024
c999676
Fix PUT and Pagination
harshach Jan 27, 2024
233fc2e
Fix styling
harshach Jan 27, 2024
2c6d647
Merge remote-tracking branch 'upstream/main' into suggestions
pmbrull Jan 29, 2024
fb9636d
update test
pmbrull Jan 29, 2024
9633d47
Update status
pmbrull Jan 29, 2024
6cb3d99
add OM server connection in apps
pmbrull Jan 29, 2024
a350230
add permissions check
karanh37 Jan 29, 2024
ec49706
Merge remote-tracking branch 'upstream/main' into suggestions
pmbrull Jan 29, 2024
128645c
Fix CI
pmbrull Jan 29, 2024
91cdf62
Remove TODO
pmbrull Jan 29, 2024
90050d0
Merge branch 'main' into suggestions
harshach Jan 29, 2024
f8876d9
Merge remote-tracking branch 'origin/main' into suggestions
harshach Jan 29, 2024
7645191
Fix feedResourceTest
harshach Jan 29, 2024
3e9f837
fix unit tests
karanh37 Jan 30, 2024
221b3cf
add private configs for apps
pmbrull Jan 30, 2024
1e324b8
Merge remote-tracking branch 'upstream/main' into suggestions
pmbrull Jan 30, 2024
834800f
add private configs for apps
pmbrull Jan 30, 2024
f151418
fix update application icons
karanh37 Jan 30, 2024
36d9ce8
minor center align icon
karanh37 Jan 30, 2024
a9945be
add private configs for apps
pmbrull Jan 30, 2024
8ba8e0a
Format
pmbrull Jan 30, 2024
95a713d
Fix pydantic gen
pmbrull Jan 31, 2024
842623f
Remove token
pmbrull Jan 31, 2024
b8098ee
Update name
pmbrull Jan 31, 2024
73226a9
Rework private conf
pmbrull Jan 31, 2024
49ca846
Fix apps
pmbrull Jan 31, 2024
b3ac6b9
Fix apps
pmbrull Jan 31, 2024
65b736a
Merge branch 'main' into suggestions
pmbrull Jan 31, 2024
10414b9
Format
pmbrull Jan 31, 2024
d5de1e1
Format
pmbrull Jan 31, 2024
e7686c5
Merge branch 'main' into suggestions
pmbrull Jan 31, 2024
285e6a1
show metapilot only if its installed
karanh37 Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion bootstrap/sql/migrations/native/1.3.0/mysql/schemaChanges.sql
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,17 @@ DELETE FROM event_subscription_entity;
DELETE FROM change_event_consumers;
DELETE FROM consumers_dlq;

UPDATE ingestion_pipeline_entity SET json = JSON_SET(json, '$.provider', 'user')
CREATE TABLE IF NOT EXISTS suggestions (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
fqnHash VARCHAR(256) NOT NULL COLLATE ascii_bin,
entityLink VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.entityLink') NOT NULL,
suggestionType VARCHAR(36) GENERATED ALWAYS AS (json_unquote(json ->> '$.type')) NOT NULL,
json JSON NOT NULL,
updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') NOT NULL,
updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
status VARCHAR(256) GENERATED ALWAYS AS (json_unquote(json -> '$.status')) NOT NULL,
PRIMARY KEY (id)
);

UPDATE ingestion_pipeline_entity SET json = JSON_SET(json, '$.provider', 'user')
WHERE JSON_EXTRACT(json, '$.name') = 'OpenMetadata_dataInsight';
12 changes: 12 additions & 0 deletions bootstrap/sql/migrations/native/1.3.0/postgres/schemaChanges.sql
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,17 @@ DELETE FROM event_subscription_entity;
DELETE FROM change_event_consumers;
DELETE FROM consumers_dlq;

CREATE TABLE IF NOT EXISTS suggestions (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
fqnHash VARCHAR(256) NOT NULL,
entityLink VARCHAR(256) GENERATED ALWAYS AS (json ->> 'entityLink') STORED NOT NULL,
suggestionType VARCHAR(36) GENERATED ALWAYS AS (json ->> 'type') STORED NOT NULL,
json JSON NOT NULL,
updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
status VARCHAR(256) GENERATED ALWAYS AS (json ->> 'status') STORED NOT NULL,
PRIMARY KEY (id)
);

UPDATE ingestion_pipeline_entity SET json = JSONB_SET(json::jsonb, '{provider}', '"user"', true)
WHERE json->>'name' = 'OpenMetadata_dataInsight';
1 change: 0 additions & 1 deletion conf/openmetadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,3 @@ web:
permission-policy:
enabled: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
option: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}

11 changes: 8 additions & 3 deletions ingestion/src/metadata/applications/auto_tagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
from metadata.generated.schema.entity.services.ingestionPipelines.status import (
StackTraceError,
)
from metadata.generated.schema.metadataIngestion.application import (
OpenMetadataApplicationConfig,
)
from metadata.generated.schema.type.tagLabel import (
LabelType,
State,
Expand Down Expand Up @@ -60,16 +63,18 @@ class AutoTaggerApp(AppRunner):
jwtToken: "..."
"""

def __init__(self, config: AutoTaggerAppConfig, metadata: OpenMetadata):
def __init__(self, config: OpenMetadataApplicationConfig, metadata: OpenMetadata):
super().__init__(config, metadata)

if not isinstance(config, AutoTaggerAppConfig):
if not isinstance(self.app_config, AutoTaggerAppConfig):
raise InvalidAppConfiguration(
f"AutoTagger Runner expects an AutoTaggerAppConfig, we got [{config}]"
)

self._ner_scanner = None
self.confidence_threshold = config.confidenceLevel or DEFAULT_CONFIDENCE
self.confidence_threshold = (
self.app_config.confidenceLevel or DEFAULT_CONFIDENCE
)

@property
def name(self) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
test_suite_source_factory,
)
from metadata.generated.schema.api.tests.createTestCase import CreateTestCaseRequest
from metadata.generated.schema.entity.data.table import Table
from metadata.generated.schema.entity.services.ingestionPipelines.status import (
StackTraceError,
)
Expand Down Expand Up @@ -200,8 +201,9 @@ def compare_and_create_test_cases(
),
entityLink=EntityLink(
__root__=entity_link.get_entity_link(
table_fqn,
test_case_to_create.columnName,
Table,
fqn=table_fqn,
column_name=test_case_to_create.columnName,
)
),
testSuite=test_suite_fqn,
Expand Down Expand Up @@ -252,8 +254,9 @@ def _update_test_cases(
updated_test_case = self.metadata.patch_test_case_definition(
source=test_case,
entity_link=entity_link.get_entity_link(
table_fqn,
test_case_definition.columnName,
Table,
fqn=table_fqn,
column_name=test_case_definition.columnName,
),
test_case_parameter_values=test_case_definition.parameterValues,
)
Expand Down
5 changes: 3 additions & 2 deletions ingestion/src/metadata/great_expectations/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,9 @@ def _handle_test_case(
test_case = self.ometa_conn.get_or_create_test_case(
test_case_fqn,
entity_link=get_entity_link(
table_entity.fullyQualifiedName.__root__,
fqn.split_test_case_fqn(test_case_fqn).column,
Table,
fqn=table_entity.fullyQualifiedName.__root__,
column_name=fqn.split_test_case_fqn(test_case_fqn).column,
),
test_suite_fqn=test_suite.fullyQualifiedName.__root__,
test_definition_fqn=test_definition.fullyQualifiedName.__root__,
Expand Down
4 changes: 4 additions & 0 deletions ingestion/src/metadata/ingestion/api/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def scanned(self, record: Any) -> None:
else:
self.records.append(log_name)

def updated(self, record: Any) -> None:
if log_name := get_log_name(record):
self.updated_records.append(log_name)

def warning(self, key: str, reason: str) -> None:
self.warnings.append({key: reason})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2021 Collate
# 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.
"""
Mixin class containing Suggestions specific methods

To be used by OpenMetadata class
"""
from metadata.generated.schema.entity.feed.suggestion import Suggestion
from metadata.ingestion.ometa.client import REST
from metadata.utils.logger import ometa_logger

logger = ometa_logger()


class OMetaSuggestionsMixin:
"""
OpenMetadata API methods related to the Suggestion Entity

To be inherited by OpenMetadata
"""

client: REST

def update_suggestion(self, suggestion: Suggestion) -> Suggestion:
"""
Update an existing Suggestion with new fields
"""
resp = self.client.put(
f"{self.get_suffix(Suggestion)}/{str(suggestion.id.__root__)}",
data=suggestion.json(),
)

return Suggestion(**resp)
22 changes: 15 additions & 7 deletions ingestion/src/metadata/ingestion/ometa/ometa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from metadata.ingestion.ometa.mixins.search_index_mixin import OMetaSearchIndexMixin
from metadata.ingestion.ometa.mixins.server_mixin import OMetaServerMixin
from metadata.ingestion.ometa.mixins.service_mixin import OMetaServiceMixin
from metadata.ingestion.ometa.mixins.suggestions_mixin import OMetaSuggestionsMixin
from metadata.ingestion.ometa.mixins.table_mixin import OMetaTableMixin
from metadata.ingestion.ometa.mixins.tests_mixin import OMetaTestsMixin
from metadata.ingestion.ometa.mixins.topic_mixin import OMetaTopicMixin
Expand Down Expand Up @@ -108,6 +109,7 @@ class OpenMetadata(
OMetaRolePolicyMixin,
OMetaSearchIndexMixin,
OMetaCustomPropertyMixin,
OMetaSuggestionsMixin,
Generic[T, C],
):
"""
Expand Down Expand Up @@ -246,11 +248,9 @@ def get_entity_from_create(self, create: Type[C]) -> Type[T]:
)
return entity_class

def create_or_update(self, data: C) -> T:
def _create(self, data: C, method: str) -> T:
"""
We allow CreateEntity for PUT, so we expect a type C.

We PUT to the endpoint and return the Entity generated result
Internal logic to run POST vs. PUT
"""
entity = data.__class__
is_create = "create" in data.__class__.__name__.lower()
Expand All @@ -262,15 +262,23 @@ def create_or_update(self, data: C) -> T:
raise InvalidEntityException(
f"PUT operations need a CreateEntity, not {entity}"
)
resp = self.client.put(
self.get_suffix(entity), data=data.json(encoder=show_secrets_encoder)
)

fn = getattr(self.client, method)
resp = fn(self.get_suffix(entity), data=data.json(encoder=show_secrets_encoder))
if not resp:
raise EmptyPayloadException(
f"Got an empty response when trying to PUT to {self.get_suffix(entity)}, {data.json()}"
)
return entity_class(**resp)

def create_or_update(self, data: C) -> T:
"""Run a PUT requesting via create request C"""
return self._create(data=data, method="put")

def create(self, data: C) -> T:
"""Run a POST requesting via create request C"""
return self._create(data=data, method="post")

def get_by_name(
self,
entity: Type[T],
Expand Down
7 changes: 7 additions & 0 deletions ingestion/src/metadata/ingestion/ometa/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
CreateDataProductRequest,
)
from metadata.generated.schema.api.domains.createDomain import CreateDomainRequest
from metadata.generated.schema.api.feed.createSuggestion import CreateSuggestionRequest
from metadata.generated.schema.api.lineage.addLineage import AddLineageRequest
from metadata.generated.schema.api.policies.createPolicy import CreatePolicyRequest
from metadata.generated.schema.api.services.createDashboardService import (
Expand Down Expand Up @@ -90,6 +91,7 @@
from metadata.generated.schema.dataInsight.dataInsightChart import DataInsightChart
from metadata.generated.schema.dataInsight.kpi.kpi import Kpi
from metadata.generated.schema.entity.automations.workflow import Workflow
from metadata.generated.schema.entity.bot import Bot
from metadata.generated.schema.entity.classification.classification import (
Classification,
)
Expand All @@ -113,6 +115,7 @@
from metadata.generated.schema.entity.data.topic import Topic
from metadata.generated.schema.entity.domains.dataProduct import DataProduct
from metadata.generated.schema.entity.domains.domain import Domain
from metadata.generated.schema.entity.feed.suggestion import Suggestion
from metadata.generated.schema.entity.policies.policy import Policy
from metadata.generated.schema.entity.services.connections.testConnectionDefinition import (
TestConnectionDefinition,
Expand Down Expand Up @@ -181,6 +184,7 @@
User.__name__: "/users",
CreateUserRequest.__name__: "/users",
AuthenticationMechanism.__name__: "/users/auth-mechanism",
Bot.__name__: "/bots", # We won't allow bot creation from the client
# Roles
Role.__name__: "/roles",
CreateRoleRequest.__name__: "/roles",
Expand Down Expand Up @@ -225,4 +229,7 @@
CreateDomainRequest.__name__: "/domains",
DataProduct.__name__: "/dataProducts",
CreateDataProductRequest.__name__: "/dataProducts",
# Suggestions
Suggestion.__name__: "/suggestions",
CreateSuggestionRequest.__name__: "/suggestions",
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import traceback
from typing import Optional, Union

from metadata.generated.schema.entity.data.table import Table
from metadata.generated.schema.tests.testSuite import TestSuite
from metadata.generated.schema.type.entityReference import EntityReference
from metadata.ingestion.ometa.ometa_api import OpenMetadata
Expand Down Expand Up @@ -81,7 +82,8 @@ def generate_entity_link(dbt_test):
manifest_node = dbt_test.get(DbtCommonEnum.MANIFEST_NODE.value)
entity_link_list = [
entity_link.get_entity_link(
table_fqn=table_fqn,
Table,
fqn=table_fqn,
column_name=manifest_node.column_name
if hasattr(manifest_node, "column_name")
else None,
Expand Down
32 changes: 26 additions & 6 deletions ingestion/src/metadata/utils/entity_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,23 @@
Filter information has been taken from the
ES indexes definitions
"""
from typing import List, Optional
from typing import Any, List, Optional, TypeVar

from antlr4.CommonTokenStream import CommonTokenStream
from antlr4.error.ErrorStrategy import BailErrorStrategy
from antlr4.InputStream import InputStream
from antlr4.tree.Tree import ParseTreeWalker
from pydantic import BaseModel
from requests.compat import unquote_plus

from metadata.antlr.split_listener import EntityLinkSplitListener
from metadata.generated.antlr.EntityLinkLexer import EntityLinkLexer
from metadata.generated.antlr.EntityLinkParser import EntityLinkParser
from metadata.generated.schema.entity.data.table import Table
from metadata.utils.constants import ENTITY_REFERENCE_TYPE_MAP
from metadata.utils.dispatch import class_register

T = TypeVar("T", bound=BaseModel)


class EntityLinkBuildingException(Exception):
Expand Down Expand Up @@ -86,16 +92,30 @@ def get_table_or_column_fqn(entity_link: str) -> str:
)


def get_entity_link(table_fqn: str, column_name: Optional[str]) -> str:
get_entity_link_registry = class_register()


def get_entity_link(entity_type: Any, fqn: str, **kwargs) -> str:
"""From table fqn and column name get the entity_link

Args:
table_fqn: table fqn
column_name: Optional param to generate entity link with column name
entity_type: Entity being built
fqn: Entity fqn
"""

func = get_entity_link_registry.registry.get(entity_type.__name__)
if not func:
return f"<#E::{ENTITY_REFERENCE_TYPE_MAP[entity_type.__name__]}::{fqn}>"

return func(fqn, **kwargs)


@get_entity_link_registry.add(Table)
def _(fqn: str, column_name: Optional[str] = None) -> str:
"""From table fqn and column name get the entity_link"""

if column_name:
entity_link = f"<#E::table::" f"{table_fqn}" f"::columns::" f"{column_name}>"
entity_link = f"<#E::{ENTITY_REFERENCE_TYPE_MAP[Table.__name__]}::{fqn}::columns::{column_name}>"
else:
entity_link = f"<#E::table::" f"{table_fqn}>"
entity_link = f"<#E::{ENTITY_REFERENCE_TYPE_MAP[Table.__name__]}::{fqn}>"
return entity_link
32 changes: 30 additions & 2 deletions ingestion/src/metadata/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@

from metadata.generated.schema.entity.data.chart import ChartType
from metadata.generated.schema.entity.data.table import Column, Table
from metadata.generated.schema.entity.feed.suggestion import Suggestion, SuggestionType
from metadata.generated.schema.entity.services.databaseService import DatabaseService
from metadata.generated.schema.type.basic import EntityLink
from metadata.generated.schema.type.tagLabel import TagLabel
from metadata.utils.constants import DEFAULT_DATABASE
from metadata.utils.logger import utils_logger
Expand Down Expand Up @@ -227,12 +229,38 @@ def find_in_iter(element: Any, container: Iterable[Any]) -> Optional[Any]:
return next((elem for elem in container if elem == element), None)


def find_column_in_table(column_name: str, table: Table) -> Optional[Column]:
def find_column_in_table(
column_name: str, table: Table, case_sensitive: bool = True
) -> Optional[Column]:
"""
If the column exists in the table, return it
"""

def equals(first: str, second: str) -> bool:
if case_sensitive:
return first == second
return first.lower() == second.lower()

return next(
(col for col in table.columns if equals(col.name.__root__, column_name)), None
)


def find_suggestion(
suggestions: List[Suggestion],
suggestion_type: SuggestionType,
entity_link: EntityLink,
) -> Optional[Suggestion]:
"""Given a list of suggestions, a suggestion type and an entity link, find
one suggestion in the list that matches the criteria
"""
return next(
(col for col in table.columns if col.name.__root__ == column_name), None
(
sugg
for sugg in suggestions
if sugg.type == suggestion_type and sugg.entityLink == entity_link
),
None,
)


Expand Down
Loading
Loading