Skip to content

Commit

Permalink
Add Generic search (#523)
Browse files Browse the repository at this point in the history
* Add projects generic schema

* Add tags and projects

* Update api_docs.yml

Co-authored-by: Mubangizi Allan <mubangizia22@gmail.com>

* Adding pagination

* update search logic

* Rectify search type

---------

Co-authored-by: Mubangizi Allan <mubangizia22@gmail.com>
  • Loading branch information
LanternNassi and Mubangizi authored Jul 24, 2024
1 parent e8fb586 commit a2bab83
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 5 deletions.
40 changes: 39 additions & 1 deletion api_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ paths:
"/activity_feed":
get:
tags:
- activity_feed
- generic
consumes:
- application/json
produces:
Expand Down Expand Up @@ -3855,6 +3855,44 @@ paths:
500:
description: "Internal Server Error"

"/search":
get:
tags:
- generic
consumes:
- application/json
parameters:
- in: header
name: Authorization
required: true
type: string
- in: query
name: keywords
type: string
description: Keywords to search
- in: query
name: type
type: string
description: Enter search type (projects, users, tags)
- in: query
name: page
type: integer
description: Enter page number default is 1
- in: query
name: per_page
type: integer
description: Enter Items per page default is 10

produces:
- application/json
responses:
200:
description: "Success"
400:
description: "Bad request"
500:
description: "Internal Server Error"

components:
securitySchemes:
bearerAuth:
Expand Down
1 change: 1 addition & 0 deletions app/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
from .project_users import ProjectUsersView, ProjectUsersTransferView, ProjectUsersHandleInviteView, ProjectFollowingView
from .activity_feed import ActivityFeedView
from .tags import TagsView, TagsDetailView, TagFollowingView
from .generic_search import GenericSearchView
116 changes: 116 additions & 0 deletions app/controllers/generic_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from app.models.project import Project
from app.models.user import User
from app.models.tags import Tag
from app.schemas.tags import TagListSchema
from app.schemas.user import UserListSchema
from app.schemas.project import ProjectListSchema
from flask import current_app
from flask_restful import Resource, request
from app.models.project import Project
from flask_jwt_extended import jwt_required
import json
from sqlalchemy import or_


class GenericSearchView(Resource):
@jwt_required
def get(self):
keywords = request.args.get('keywords', '')
search_type = request.args.get('type', None)

search_type_enum = ['projects', 'users', 'tags']
if search_type and search_type not in search_type_enum:
return dict(
message=f"""Invalid type provided, should be one of {
search_type_enum}"""
), 400

page = int(request.args.get('page', 1))
per_page = int(request.args.get('per_page', 10))

# Schemas
projectSchema = ProjectListSchema(many=True)
userSchema = UserListSchema(many=True)
tagSchema = TagListSchema(many=True)

overall_pagination = {
'total': 0,
'pages': 0,
'page': page,
'per_page': per_page,
'next': None,
'prev': page-1 if page > 1 else None
}

def create_pagination(pagination):
overall_pagination['total'] = max(
overall_pagination['total'], pagination.total)
overall_pagination['pages'] = max(
overall_pagination['pages'], pagination.pages)
if pagination.next_num:
if overall_pagination['next'] != None:
overall_pagination['next'] = max(overall_pagination.get(
'next', 0), pagination.next_num) or None
else:
overall_pagination['next'] = pagination.next_num

return {
'total': pagination.total,
'pages': pagination.pages,
'page': pagination.page,
'per_page': pagination.per_page,
'next': pagination.next_num,
'prev': pagination.prev_num
}

return_object = {}

# Projects
if not search_type or search_type == 'projects':
projects_pagination = Project.query.filter(
Project.name.ilike('%'+keywords+'%'),
# Project.is_public == True
).order_by(Project.date_created.desc()).paginate(
page=int(page), per_page=int(per_page), error_out=False)
project_data, _ = projectSchema.dumps(projects_pagination.items)
if projects_pagination.total > 0:
return_object['projects'] = {
'pagination': create_pagination(projects_pagination),
'items': json.loads(project_data)
}

# Tags
if not search_type or search_type == 'tags':
tags_pagination = Tag.query.filter(
Tag.name.ilike('%'+keywords+'%')
).order_by(Tag.date_created.desc()).paginate(
page=int(page), per_page=int(per_page), error_out=False)
tags_data, _ = tagSchema.dumps(tags_pagination.items)
if tags_pagination.total > 0:
return_object['tags'] = {
'pagination': create_pagination(tags_pagination),
'items': json.loads(tags_data)
}

# Users
if not search_type or search_type == 'users':
search_filter = or_(
User.name.ilike(f'%{keywords}%'),
User.email.ilike(f'%{keywords}%')
)
users_pagination = User.query.filter(search_filter).order_by(
User.date_created.desc()
).paginate(
page=int(page), per_page=int(per_page), error_out=False
)
users_data, _ = userSchema.dumps(users_pagination.items)
if users_pagination.total > 0:
return_object['users'] = {
'pagination': create_pagination(users_pagination),
'items': json.loads(users_data)
}

return dict(
pagination=overall_pagination,
data=return_object
), 200
5 changes: 4 additions & 1 deletion app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
UserAdminUpdateView, AppRevertView, ProjectGetCostsView, TransactionRecordView, CreditTransactionRecordView, CreditPurchaseTransactionRecordView,
BillingInvoiceView, BillingInvoiceNotificationView, SystemSummaryView, CreditDetailView, ProjectUsersView, ProjectUsersTransferView, AppReviseView,
ProjectUsersHandleInviteView, ClusterProjectsView, ProjectDisableView, ProjectEnableView, AppRedeployView, AppDisableView, AppEnableView,
TagsView, TagsDetailView, TagFollowingView,
TagsView, TagsDetailView, TagFollowingView,GenericSearchView,
UserDisableView, UserEnableView, AppDockerWebhookListenerView, UserFollowersView, UserFollowView, ProjectFollowingView, ActivityFeedView)
from app.controllers.app import AppRevisionsView
from app.controllers.billing_invoice import BillingInvoiceDetailView
Expand Down Expand Up @@ -52,6 +52,9 @@
# Deployments
api.add_resource(DeploymentsView, '/deployments', endpoint='deployments')

#Generic search
api.add_resource(GenericSearchView, '/search')

# Clusters
api.add_resource(ClustersView, '/clusters', endpoint='clusters')
api.add_resource(ClusterDetailView, '/clusters/<string:cluster_id>')
Expand Down
12 changes: 9 additions & 3 deletions app/schemas/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from app.helpers.age_utility import get_item_age
from .credits import CreditSchema
class UserSchema(Schema):

id = fields.String(dump_only=True)

email = fields.Email(required=True)
Expand Down Expand Up @@ -42,7 +41,14 @@ class UserSchema(Schema):

def get_age(self, obj):
return get_item_age(obj.date_created)



class UserListSchema(Schema):
id = fields.String(dump_only=True)
email = fields.Email(required=True)
name = fields.String(required=True)
organisation = fields.String(required=True)
last_seen = fields.Date(dump_only=True)


class ActivityLogSchema(Schema):
Expand All @@ -59,4 +65,4 @@ class ActivityLogSchema(Schema):
a_app_id = fields.String()
creation_date = fields.Date()
start = fields.Date(load_only=True)
end = fields.Date(load_only=True)
end = fields.Date(load_only=True)

0 comments on commit a2bab83

Please sign in to comment.