Skip to content

Commit

Permalink
Fix #14786: Suggestions API (#14821)
Browse files Browse the repository at this point in the history
* Fix #14786: Suggestions API

* Handle suggestions in ometa

* Minor: Optimise Databricks Client (#14776)

* MINOR - Fix SP topology context & Looker usage context (#14816)

* MINOR - Fix SP topology context & Looker usage context

* MINOR - Fix SP topology context & Looker usage context

* Fix tests

* Fixes #14598: Fix Tags / Labels ingestion on includeTags as False (#14782)

* fix(ui): password error message for char limits (#14808)

* fix(ui): password error message for char limits

* fix java side code

* Fixes #13556: Support for Salesforce table description ingestion (#14733)

* ISSUE-13556: Add suport for Salesforce table description ingestion

* ISSUE-13556: Remove unnecessary blank line

* ISSUE-13556: Fix to get description for each table

---------

Co-authored-by: Teddy <teddy.crepineau@gmail.com>

* MINOR - Better handling of Ingestion Pipeline Status (#14792)

* MINOR - Better handling of Ingestion Pipeline Status

* format

* format

* MINOR: Added table validation for cost analysis data (#14793)

* Added validation for cost analysis source

* centralized life cycle logic

* CYPRESS: simplify side navigation click in cypress (#14818)

* simplify side navigation click in cypress

* make sidbar item uses common enum

* fix cypress failure of outside import

* fix(#14326): tier dropdown is not working in advance search (#14780)

* improvement in advance search based on custom property

* fix a reading undefined property issue

* wip: advance search based on tier

* some code cleanup and improvement

* some fixes

* fix: ui flicker when advanceSearched is apply and refresh the page

* some cleanup

* no need to call customproperty api call, if entity not suppport customProperties

* minor change

* fix: autocomplete not working in tier search option in advance search modal

* added unit test for advance search provider component

* some cleanup

* added testcase for open modal

* added testcase for resetAllFilters method

* removed unwanted code

* added e2e test for testing tier advance search

* fix: e2e search flow for single field

* fix: string field not working after giving listValues in TierSearch

* fix: group query e2e test fix

* used asyncFetch way to get the tierOptions synchronously

* some cleanup

* remove unwanted lines

* some cleanup

* fix: selected option show option value instead of option title

* fix(minor): update skip icon for executions (#14809)

* Fixes #14803: ignore capitalization when confirming deletes  (#14804)

* ignore case when confirming deletes

* Test confirmation of deletes works when case differs 

Added test case for 'delete' as the confirmation text.

* minor(config): update openmetadata-ui code reviewers (#14823)

* Add Tests

* Add list/accept/reject apis

* initial ui changes

* localisation

* show suggestion for empty description

* ui feedbacks

* Fix permission check for entities without owner

* Fix entityLink and add tests

* Add update suggestion WIP

* Fix test

* Fix PUT and Pagination

* Fix styling

* update test

* Update status

* add OM server connection in apps

* add permissions check

* Fix CI

* Remove TODO

* Fix feedResourceTest

* fix unit tests

* add private configs for apps

* add private configs for apps

* fix update application icons

* minor center align icon

* add private configs for apps

* Format

* Fix pydantic gen

* Remove token

* Update name

* Rework private conf

* Fix apps

* Fix apps

* Format

* Format

* show metapilot only if its installed

---------

Co-authored-by: Pere Miquel Brull <peremiquelbrull@gmail.com>
Co-authored-by: Mayur Singal <39544459+ulixius9@users.noreply.github.com>
Co-authored-by: Ayush Shah <ayush@getcollate.io>
Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
Co-authored-by: kwgdaig <18678754+kwgdaig@users.noreply.github.com>
Co-authored-by: Teddy <teddy.crepineau@gmail.com>
Co-authored-by: Onkar Ravgan <onkar.10r@gmail.com>
Co-authored-by: Ashish Gupta <ashish@getcollate.io>
Co-authored-by: Abhishek Porwal <80886271+Abhishek332@users.noreply.github.com>
Co-authored-by: Carlo Q <carlo@machina.bio>
Co-authored-by: karanh37 <karanh37@gmail.com>
  • Loading branch information
12 people committed Feb 1, 2024
1 parent 0d66c0e commit 2e95fcb
Show file tree
Hide file tree
Showing 87 changed files with 3,091 additions and 203 deletions.
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
41 changes: 41 additions & 0 deletions ingestion/src/metadata/ingestion/ometa/mixins/suggestions_mixin.py
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

0 comments on commit 2e95fcb

Please sign in to comment.