Skip to content

Commit

Permalink
DA v2: Modularized structure of feeds and votes (#735)
Browse files Browse the repository at this point in the history
### Feature or Bugfix
- Refactoring

### Detail
- Adjust `feed` module to the new code layer design
- Adjust `vote` module to the new code layer design
- Clean up unused code

### Relates
- V2.0

### Security
Please answer the questions below briefly where applicable, or write
`N/A`. Based on
[OWASP 10](https://owasp.org/Top10/en/).

`N/A`

- Does this PR introduce or modify any input fields or queries - this
includes
fetching data from storage outside the application (e.g. a database, an
S3 bucket)?
  - Is the input sanitized?
- What precautions are you taking before deserializing the data you
consume?
  - Is injection prevented by parametrizing queries?
  - Have you ensured no `eval` or similar functions are used?
- Does this PR introduce any functionality or component that requires
authorization?
- How have you ensured it respects the existing AuthN/AuthZ mechanisms?
  - Are you logging failed auth attempts?
- Are you using or adding any cryptographic features?
  - Do you use a standard proven implementations?
  - Are the used keys controlled by the customer? Where are they stored?
- Are you introducing any new policies/roles/users?
  - Have you used the least-privilege principle? How?


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
dlpzx authored Sep 8, 2023
1 parent a4b831e commit 8d95241
Show file tree
Hide file tree
Showing 17 changed files with 248 additions and 137 deletions.
2 changes: 1 addition & 1 deletion backend/dataall/modules/dashboards/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self):
import dataall.modules.dashboards.api
from dataall.modules.feed.api.registry import FeedRegistry, FeedDefinition
from dataall.modules.catalog.api.registry import GlossaryRegistry, GlossaryDefinition
from dataall.modules.vote.api.resolvers import add_vote_type
from dataall.modules.vote.services.vote_service import add_vote_type
from dataall.modules.dashboards.indexers.dashboard_indexer import DashboardIndexer

FeedRegistry.register(FeedDefinition("Dashboard", Dashboard))
Expand Down
4 changes: 2 additions & 2 deletions backend/dataall/modules/dashboards/api/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataall.base.api.context import Context
from dataall.modules.catalog.db.glossary_repositories import Glossary
from dataall.core.organizations.db.organization_repositories import Organization
from dataall.modules.vote.db.vote_repositories import Vote
from dataall.modules.vote.db.vote_repositories import VoteRepository
from dataall.base.db.exceptions import RequiredParameter
from dataall.modules.dashboards.api.enums import DashboardRole
from dataall.modules.dashboards.db.dashboard_repositories import DashboardRepository
Expand Down Expand Up @@ -113,7 +113,7 @@ def resolve_glossary_terms(context: Context, source: Dashboard, **kwargs):

def resolve_upvotes(context: Context, source: Dashboard, **kwargs):
with context.engine.scoped_session() as session:
return Vote.count_upvotes(
return VoteRepository.count_upvotes(
session, source.dashboardUri, target_type='dashboard'
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataall.core.environment.services.environment_service import EnvironmentService
from dataall.core.organizations.db.organization_repositories import Organization
from dataall.modules.vote.db.vote_repositories import Vote
from dataall.modules.vote.db.vote_repositories import VoteRepository
from dataall.modules.dashboards import DashboardRepository
from dataall.modules.catalog.indexers.base_indexer import BaseIndexer
from dataall.modules.dashboards.db.dashboard_models import Dashboard
Expand All @@ -20,7 +20,7 @@ def upsert(cls, session, dashboard_uri: str):
org = Organization.get_organization_by_uri(session, env.organizationUri)

glossary = BaseIndexer._get_target_glossary_terms(session, dashboard_uri)
count_upvotes = Vote.count_upvotes(
count_upvotes = VoteRepository.count_upvotes(
session, dashboard_uri, target_type='dashboard'
)
BaseIndexer._index(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dataall.modules.catalog.db.glossary_repositories import Glossary
from dataall.core.permissions.db.resource_policy_repositories import ResourcePolicy
from dataall.core.permissions.permission_checker import has_tenant_permission, has_resource_permission
from dataall.modules.vote.db.vote_repositories import Vote
from dataall.modules.vote.db.vote_repositories import VoteRepository
from dataall.base.db.exceptions import UnauthorizedOperation
from dataall.modules.dashboards import DashboardRepository, Dashboard
from dataall.modules.dashboards.aws.dashboard_quicksight_client import DashboardQuicksightClient
Expand Down Expand Up @@ -104,7 +104,7 @@ def delete_dashboard(uri) -> bool:
Glossary.delete_glossary_terms_links(
session, target_uri=dashboard.dashboardUri, target_type='Dashboard'
)
Vote.delete_votes(session, dashboard.dashboardUri, 'dashboard')
VoteRepository.delete_votes(session, dashboard.dashboardUri, 'dashboard')

DashboardIndexer.delete_doc(doc_id=uri)
return True
Expand Down
2 changes: 1 addition & 1 deletion backend/dataall/modules/datasets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def depends_on() -> List[Type['ModuleInterface']]:
def __init__(self):
# these imports are placed inside the method because they are only related to GraphQL api.
from dataall.core.stacks.db.target_type_repositories import TargetType
from dataall.modules.vote.api.resolvers import add_vote_type
from dataall.modules.vote.services.vote_service import add_vote_type
from dataall.modules.feed.api.registry import FeedRegistry, FeedDefinition
from dataall.modules.catalog.api.registry import GlossaryRegistry, GlossaryDefinition
from dataall.core.environment.services.environment_resource_manager import EnvironmentResourceManager
Expand Down
4 changes: 2 additions & 2 deletions backend/dataall/modules/datasets/indexers/dataset_indexer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Indexes Datasets in OpenSearch"""
from dataall.core.environment.services.environment_service import EnvironmentService
from dataall.core.organizations.db.organization_repositories import Organization
from dataall.modules.vote.db.vote_repositories import Vote
from dataall.modules.vote.db.vote_repositories import VoteRepository
from dataall.modules.datasets_base.db.dataset_repositories import DatasetRepository
from dataall.modules.datasets.db.dataset_location_repositories import DatasetLocationRepository
from dataall.modules.catalog.indexers.base_indexer import BaseIndexer
Expand All @@ -17,7 +17,7 @@ def upsert(cls, session, dataset_uri: str):

count_tables = DatasetRepository.count_dataset_tables(session, dataset_uri)
count_folders = DatasetLocationRepository.count_dataset_locations(session, dataset_uri)
count_upvotes = Vote.count_upvotes(
count_upvotes = VoteRepository.count_upvotes(
session, dataset_uri, target_type='dataset'
)

Expand Down
6 changes: 3 additions & 3 deletions backend/dataall/modules/datasets/services/dataset_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from dataall.core.stacks.db.stack_repositories import Stack
from dataall.core.tasks.db.task_models import Task
from dataall.modules.catalog.db.glossary_repositories import Glossary
from dataall.modules.vote.db.vote_repositories import Vote
from dataall.modules.vote.db.vote_repositories import VoteRepository
from dataall.base.db.exceptions import AWSResourceNotFound, UnauthorizedOperation
from dataall.modules.dataset_sharing.aws.kms_client import KmsClient
from dataall.modules.dataset_sharing.db.share_object_models import ShareObject
Expand Down Expand Up @@ -221,7 +221,7 @@ def get_dataset_statistics(dataset: Dataset):
count_locations = DatasetLocationRepository.count_dataset_locations(
session, dataset.datasetUri
)
count_upvotes = Vote.count_upvotes(
count_upvotes = VoteRepository.count_upvotes(
session, dataset.datasetUri, target_type='dataset'
)
return {
Expand Down Expand Up @@ -372,7 +372,7 @@ def delete_dataset(uri: str, delete_from_aws: bool = False):
DatasetTableRepository.delete_dataset_tables(session, dataset.datasetUri)
DatasetLocationRepository.delete_dataset_locations(session, dataset.datasetUri)
KeyValueTag.delete_key_value_tags(session, dataset.datasetUri, 'dataset')
Vote.delete_votes(session, dataset.datasetUri, 'dataset')
VoteRepository.delete_votes(session, dataset.datasetUri, 'dataset')

ResourcePolicy.delete_resource_policy(
session=session, resource_uri=uri, group=dataset.SamlAdminGroupName
Expand Down
4 changes: 2 additions & 2 deletions backend/dataall/modules/feed/api/mutations.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from dataall.base.api import gql
from dataall.modules.feed.api.resolvers import post_message
from dataall.modules.feed.api.resolvers import post_feed_message


postFeedMessage = gql.MutationField(
name='postFeedMessage',
resolver=post_message,
resolver=post_feed_message,
args=[
gql.Argument(name='targetUri', type=gql.NonNullableType(gql.String)),
gql.Argument(name='targetType', type=gql.NonNullableType(gql.String)),
Expand Down
85 changes: 25 additions & 60 deletions backend/dataall/modules/feed/api/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,50 @@
from sqlalchemy import or_

from dataall.base.api.context import Context
from dataall.base.db import paginate
from dataall.base.db import exceptions
from dataall.modules.feed.api.registry import FeedRegistry
from dataall.modules.feed.db.feed_models import FeedMessage


class Feed:
def __init__(self, targetUri: str = None, targetType: str = None):
self._targetUri = targetUri
self._targetType = targetType

@property
def targetUri(self):
return self._targetUri
from dataall.modules.feed.services.feed_service import Feed, FeedService

@property
def targetType(self):
return self._targetType


def resolve_feed_target_type(obj, *_):
return FeedRegistry.find_target(obj)
def _required_uri(uri):
if not uri:
raise exceptions.RequiredParameter('URI')


def resolve_target(context: Context, source: Feed, **kwargs):
if not source:
return None
with context.engine.scoped_session() as session:
model = FeedRegistry.find_model(source.targetType)
target = session.query(model).get(source.targetUri)
return target
def _required_type(type):
if not type:
raise exceptions.RequiredParameter('TargetType')


def get_feed(
context: Context,
source,
targetUri: str = None,
targetType: str = None,
filter: dict = None,
) -> Feed:
return Feed(targetUri=targetUri, targetType=targetType)
):
_required_uri(targetUri)
_required_type(targetType)
return FeedService.get_feed(targetUri=targetUri, targetType=targetType)


def post_message(
def post_feed_message(
context: Context,
source,
targetUri: str = None,
targetType: str = None,
input: dict = None,
):
with context.engine.scoped_session() as session:
m = FeedMessage(
targetUri=targetUri,
targetType=targetType,
creator=context.username,
content=input.get('content'),
)
session.add(m)
return m
return FeedService.post_feed_message(
targetUri=targetUri,
targetType=targetType,
content=input.get('content')
)


def resolve_feed_target_type(obj, *_):
return FeedRegistry.find_target(obj)


def resolve_messages(context: Context, source: Feed, filter: dict = None):
if not source:
return None
def resolve_feed_messages(context: Context, source: Feed, filter: dict = None):
_required_uri(source.targetUri)
if not filter:
filter = {}
with context.engine.scoped_session() as session:
q = session.query(FeedMessage).filter(
FeedMessage.targetUri == source.targetUri
)
term = filter.get('term')
if term:
q = q.filter(
or_(
FeedMessage.content.ilike('%' + term + '%'),
FeedMessage.creator.ilike('%' + term + '%'),
)
)
q = q.order_by(FeedMessage.created.desc())

return paginate(
q, page=filter.get('page', 1), page_size=filter.get('pageSize', 10)
).to_dict()
return FeedService.list_feed_messages(targetUri=source.targetUri, filter=filter)
4 changes: 2 additions & 2 deletions backend/dataall/modules/feed/api/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataall.base.api import gql
from dataall.modules.feed.api.resolvers import resolve_feed_target_type, resolve_messages
from dataall.modules.feed.api.resolvers import resolve_feed_target_type, resolve_feed_messages
from dataall.modules.feed.api.registry import FeedRegistry


Expand All @@ -17,7 +17,7 @@
gql.Field(
name='messages',
args=[gql.Argument(name='filter', type=gql.Ref('FeedMessageFilter'))],
resolver=resolve_messages,
resolver=resolve_feed_messages,
type=gql.Ref('FeedMessages'),
),
],
Expand Down
31 changes: 31 additions & 0 deletions backend/dataall/modules/feed/db/feed_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
DAO layer that encapsulates the logic and interaction with the database for Feeds
Provides the API to retrieve / update / delete FeedS
"""
from sqlalchemy import or_

from dataall.base.db import paginate
from dataall.modules.feed.db.feed_models import FeedMessage


class FeedRepository:
def __init__(self, session):
self._session = session

def paginated_feed_messages(self, uri, filter):
q = self._session.query(FeedMessage).filter(
FeedMessage.targetUri == uri
)
term = filter.get('term')
if term:
q = q.filter(
or_(
FeedMessage.content.ilike('%' + term + '%'),
FeedMessage.creator.ilike('%' + term + '%'),
)
)
q = q.order_by(FeedMessage.created.desc())

return paginate(
q, page=filter.get('page', 1), page_size=filter.get('pageSize', 10)
).to_dict()
7 changes: 7 additions & 0 deletions backend/dataall/modules/feed/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Contains the code needed for service layer.
The service layer is a layer where all business logic is aggregated
"""
from dataall.modules.feed.services import feed_service

__all__ = ["feed_service"]
64 changes: 64 additions & 0 deletions backend/dataall/modules/feed/services/feed_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
A service layer for Feeds
Central part for working with Feeds
"""
import logging

from dataall.base.context import get_context
from dataall.modules.feed.db.feed_models import FeedMessage
from dataall.modules.feed.db.feed_repository import FeedRepository


logger = logging.getLogger(__name__)


class Feed:
def __init__(self, targetUri: str = None, targetType: str = None):
self._targetUri = targetUri
self._targetType = targetType

@property
def targetUri(self):
return self._targetUri

@property
def targetType(self):
return self._targetType


def _session():
return get_context().db_engine.scoped_session()


class FeedService:
"""
Encapsulate the logic of interactions with Feeds.
"""

@staticmethod
def get_feed(
targetUri: str = None,
targetType: str = None,
) -> Feed:
return Feed(targetUri=targetUri, targetType=targetType)

@staticmethod
def post_feed_message(
targetUri: str = None,
targetType: str = None,
content: str = None,
):
with _session() as session:
m = FeedMessage(
targetUri=targetUri,
targetType=targetType,
creator=get_context().username,
content=content,
)
session.add(m)
return m

@staticmethod
def list_feed_messages(targetUri: str, filter: dict = None):
with _session() as session:
return FeedRepository(session).paginated_feed_messages(uri=targetUri, filter=filter)
Loading

0 comments on commit 8d95241

Please sign in to comment.