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

feat: able to delete and add tags to project #518

Merged
merged 3 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 97 additions & 0 deletions api_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2529,6 +2529,14 @@ paths:
type: string
project_type:
type: string
tags_add:
type: array
items:
type: string
tags_remove:
type: array
items:
type: string
responses:
200:
description: "Success"
Expand Down Expand Up @@ -3752,6 +3760,95 @@ paths:
500:
description: "Internal Server Error"

"/tags/{tag_id}/following":
get:
tags:
- tags
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
required: true
description: "Bearer [token]"
- in: path
name: tag_id
required: true
type: string
- in: query
name: page
type: integer
description: Page number
- in: query
name: per_page
type: integer
description: Number of items per page
responses:
200:
description: "Success"
404:
description: "Tag not found"
400:
description: "Bad request"
500:
description: "Internal Server Error"

post:
tags:
- tags
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
required: true
description: "Bearer [token]"
type: string
- in: path
name: tag_id
required: true
type: string
responses:
201:
description: "Success"
404:
description: "Tag not found"
400:
description: "Bad request"
500:
description: "Internal Server Error"

delete:
tags:
- tags
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
required: true
description: "Bearer [token]"
type: string
- in: path
name: tag_id
required: true
type: string
responses:
200:
description: "Success"
404:
description: "Tag not found"
400:
description: "Bad request"
500:
description: "Internal Server Error"

components:
securitySchemes:
bearerAuth:
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@
from .system_status import SystemSummaryView
from .project_users import ProjectUsersView, ProjectUsersTransferView, ProjectUsersHandleInviteView, ProjectFollowingView
from .activity_feed import ActivityFeedView
from .tags import TagsView, TagsDetailView
from .tags import TagsView, TagsDetailView, TagFollowingView
21 changes: 20 additions & 1 deletion app/controllers/project.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import datetime
import json
from types import SimpleNamespace
import uuid
from app.helpers.cost_modal import CostModal
from app.helpers.alias import create_alias
from app.helpers.admin import is_authorised_project_user, is_owner_or_admin, is_current_or_admin, is_admin
from app.helpers.role_search import has_role
from app.helpers.activity_logger import log_activity
from app.helpers.kube import create_kube_clients, delete_cluster_app, disable_project, enable_project, check_kube_error_code
from app.helpers.tags import add_tags_to_project, create_tags, remove_tags_from_project
from app.models.billing_invoice import BillingInvoice
from app.models.project_users import ProjectUser
from app.models.user import User
Expand All @@ -15,6 +17,7 @@
from app.schemas import ProjectSchema, AppSchema, ProjectUserSchema, ClusterSchema
from app.helpers.decorators import admin_required
import datetime
from app.schemas.tags import TagSchema
from flask_restful import Resource, request
from kubernetes import client
from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt_claims
Expand Down Expand Up @@ -57,6 +60,7 @@ def post(self):
message=f'''project with name {
validated_project_data["name"]} already exists'''
), 409

try:
validated_project_data['alias'] =\
create_alias(validated_project_data['name'])
Expand Down Expand Up @@ -131,6 +135,10 @@ def post(self):
)
project.users.append(new_role)

if validated_project_data['tags_add']:
tags = validated_project_data['tags']
validated_project_data.pop('tags', None)

saved = project.save()

if not saved:
Expand All @@ -143,6 +151,8 @@ def post(self):
kube_client.kube.delete_namespace(namespace_name)

return dict(status="fail", message="Internal Server Error"), 500
if tags:
add_tags_to_project(tags, project)

# create a billing invoice on project creation
new_invoice = BillingInvoice(project_id=project.id)
Expand Down Expand Up @@ -491,7 +501,7 @@ def patch(self, project_id):
current_user_roles = get_jwt_claims()['roles']

project_schema = ProjectSchema(
only=("name", "description", "organisation", "project_type"), partial=True)
only=("name", "description", "organisation", "project_type", "tags_add", "tags_remove"), partial=True)

project_data = request.get_json()

Expand Down Expand Up @@ -523,6 +533,15 @@ def patch(self, project_id):
if not is_authorised_project_user(project, current_user_id, 'admin'):
return dict(status='fail', message='unauthorised'), 403

if validate_project_data.get('tags_add'):
add_tags_to_project(
validate_project_data['tags_add'], project)
validate_project_data.pop('tags_add', None)
if validate_project_data.get('tags_remove'):
remove_tags_from_project(
validate_project_data['tags_remove'], project)
validate_project_data.pop('tags_remove', None)

updated = Project.update(project, **validate_project_data)

if not updated:
Expand Down
5 changes: 2 additions & 3 deletions app/controllers/project_users.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from functools import partial
import json
from app.models.project_users import ProjectUser
from flask_restful import Resource, request
from app.schemas import ProjectUserSchema, UserSchema, AnonymousUsersSchema, ProjectFollowerSchema
from app.schemas import ProjectUserSchema, AnonymousUsersSchema, UserIndexSchema
from app.models.user import User
from app.models.role import User
from app.models.project import Project
Expand Down Expand Up @@ -522,7 +521,7 @@ def post(self, project_id):
@ jwt_required
def get(self, project_id):
project = Project.get_by_id(project_id)
follower_schema = ProjectFollowerSchema(many=True)
follower_schema = UserIndexSchema(many=True)

followers = project.followers
users_data, errors = follower_schema.dumps(followers)
Expand Down
80 changes: 75 additions & 5 deletions app/controllers/tags.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@

import json
from app.schemas.project_users import UserIndexSchema
from app.schemas.tags import TagSchema, TagsDetailSchema
from flask_restful import Resource, request
from flask_jwt_extended import jwt_required
from app.schemas.tags import TagSchema
from app.models.tags import Tag
from flask_jwt_extended import jwt_required, get_jwt_identity
from app.models.tags import Tag, TagFollowers
from app.helpers.decorators import admin_required


Expand Down Expand Up @@ -62,11 +64,11 @@ class TagsDetailView(Resource):

@jwt_required
def get(self, tag_id):
tag_id_schema = TagSchema()
tag_schema = TagsDetailSchema()

tag = Tag.get_by_id(tag_id)

tags_data = tag_id_schema.dump(tag)
tags_data = tag_schema.dump(tag)

return dict(
status="success",
Expand All @@ -90,3 +92,71 @@ def delete(self, tag_id):
status='success',
message=f"Tag {tag_id} successfully deleted"
), 200


class TagFollowingView(Resource):
@ jwt_required
def post(self, tag_id):
current_user_id = get_jwt_identity()
tag = Tag.get_by_id(tag_id)

if not tag:
return dict(status='fail', message=f'Tag with id {tag_id} not found'), 404


existing_tag_follow = TagFollowers.find_first(
user_id=current_user_id, tag_id=tag_id)
if existing_tag_follow:
return dict(status='fail', message=f'You are already following tag with id {tag_id}'), 409

new_tag_follow = TagFollowers(
user_id=current_user_id, tag_id=tag_id)

saved_tag_follow = new_tag_follow.save()

if not saved_tag_follow:
return dict(status='fail', message='Internal Server Error'), 500

return dict(
status='success',
message=f'You are now following tag with id {tag_id}'
), 201

@ jwt_required
def get(self, tag_id):
tag = Tag.get_by_id(tag_id)
follower_schema = UserIndexSchema(many=True)

followers = tag.followers
users_data, errors = follower_schema.dumps(followers)

if errors:
return dict(status='fail', message=errors), 400

return dict(
status='success',
data=dict(followers=json.loads(users_data))
), 200

@ jwt_required
def delete(self, tag_id):
current_user_id = get_jwt_identity()
tag = Tag.get_by_id(tag_id)

if not tag:
return dict(status='fail', message=f'Tag with id {tag_id} not found'), 404

existing_tag_follow = TagFollowers.find_first(
user_id=current_user_id, tag_id=tag_id)
if not existing_tag_follow:
return dict(status='fail', message=f'You are not following tag with id {tag_id}'), 409

deleted_tag = existing_tag_follow.delete()

if not deleted_tag:
return dict(status='fail', message='Internal Server Error'), 500

return dict(
status='success',
message=f'You are nolonger following tag with id {tag_id}'
), 201
54 changes: 54 additions & 0 deletions app/helpers/tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from app.models.tags import ProjectTag, Tag


def create_tags(tag_names):
"""
Create tags
"""
none_existing_tags = []
existing_tags = []
for tag in tag_names:
tag = tag.strip()
tag_rec = Tag.find_first(name=tag)
if not tag_rec:
none_existing_tags.append(Tag(name=tag))
else:
existing_tags.append(tag_rec)
if none_existing_tags:
Tag.bulk_save(none_existing_tags)
new_tags = []
for tag in none_existing_tags:
new_tag = Tag.find_first(name=tag.name)
new_tags.append(new_tag)
if new_tags:
existing_tags.extend(new_tags)
return existing_tags


def add_tags_to_project(tag_names, project):
tags = create_tags(tag_names)
project_tags = []
for tag in tags:
project_tag = ProjectTag.find_first(
tag_id=tag.id, project_id=project.id)
if not project_tag:
project_tags.append(ProjectTag(
tag_id=tag.id, project_id=project.id))

if project_tags:
saved_tags = ProjectTag.bulk_save(project_tags)
if not saved_tags:
return False
return True


def remove_tags_from_project(tag_names, project):
for tag in tag_names:
existing_tag = Tag.find_first(name=tag)
if not existing_tag:
continue
project_tag = ProjectTag.find_first(
tag_id=existing_tag.id, project_id=project.id)
if project_tag:
project_tag.delete()
return True
2 changes: 1 addition & 1 deletion app/models/model_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def check_exists(cls, **kwargs):

if result > 0:
return False
return False
return True

@classmethod
def get_by_id(cls, id):
Expand Down
1 change: 1 addition & 0 deletions app/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Project(ModelMixin):
deleted = db.Column(db.Boolean, default=False)
disabled = db.Column(db.Boolean, default=False)
admin_disabled = db.Column(db.Boolean, default=False)
tags = relationship('ProjectTag', back_populates='project')

def is_followed_by(self, user):
return any(follower.user_id == user.id for follower in self.followers)
Expand Down
Loading
Loading