Skip to content

Commit

Permalink
Allow comment deletion (#224)
Browse files Browse the repository at this point in the history
* Allow comment deletion

* Add delete_changeset for comment, decrement comments count

* Add comment delete resolver test

* Don't use pipe on single function call

Co-authored-by: Simon Prévost <sprevost@mirego.com>

* Improve controller functions

* Run format

Co-authored-by: Simon Prévost <sprevost@mirego.com>
  • Loading branch information
François Richard and simonprev committed Jan 27, 2021
1 parent 258c64c commit 62ed72e
Show file tree
Hide file tree
Showing 26 changed files with 227 additions and 5 deletions.
6 changes: 5 additions & 1 deletion lib/accent/auth/canada_implementations.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defimpl Canada.Can, for: Accent.User do
alias Accent.{User, Project, Revision}
alias Accent.{User, Project, Revision, Comment}

def can?(%User{permissions: permissions}, action, project_id) when is_binary(project_id) do
validate_role(permissions, action, project_id)
Expand All @@ -13,6 +13,10 @@ defimpl Canada.Can, for: Accent.User do
validate_role(permissions, action, project_id)
end

def can?(%User{id: user_id}, _action, %Comment{user_id: comment_user_id}) do
user_id == comment_user_id
end

def can?(%User{email: email}, action, _) do
Accent.EmailAbilities.can?(email, action)
end
Expand Down
14 changes: 13 additions & 1 deletion lib/accent/schemas/comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Accent.Comment do

@required_fields ~w(text user_id translation_id)a

def changeset(model, params) do
def create_changeset(model, params) do
model
|> cast(params, @required_fields ++ [])
|> validate_required(@required_fields)
Expand All @@ -28,4 +28,16 @@ defmodule Accent.Comment do
changeset
end)
end

def delete_changeset(model) do
model
|> change()
|> prepare_changes(fn changeset ->
Accent.Translation
|> where(id: ^model.translation_id)
|> changeset.repo.update_all(inc: [comments_count: -1])

changeset
end)
end
end
9 changes: 9 additions & 0 deletions lib/graphql/helpers/authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Accent.GraphQL.Helpers.Authorization do

alias Accent.{
Collaborator,
Comment,
Document,
Integration,
Operation,
Expand Down Expand Up @@ -148,4 +149,12 @@ defmodule Accent.GraphQL.Helpers.Authorization do
authorize(action, integration.project_id, info, do: func.(integration, args, info))
end
end

def comment_authorize(action, func) do
fn _, args, info ->
comment = Repo.get(Comment, args.id)

authorize(action, comment, info, do: func.(comment, args, info))
end
end
end
6 changes: 6 additions & 0 deletions lib/graphql/mutations/comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ defmodule Accent.GraphQL.Mutations.Comment do
resolve(translation_authorize(:create_comment, &Accent.GraphQL.Resolvers.Comment.create/3))
end

field :delete_comment, :mutated_comment do
arg(:id, non_null(:id))

resolve(comment_authorize(:delete_comment, &Accent.GraphQL.Resolvers.Comment.delete/3))
end

field :create_translation_comments_subscription, :mutated_translation_comments_subscription do
arg(:translation_id, non_null(:id))
arg(:user_id, non_null(:id))
Expand Down
12 changes: 11 additions & 1 deletion lib/graphql/resolvers/comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Accent.GraphQL.Resolvers.Comment do
"translation_id" => translation.id
}

changeset = Comment.changeset(%Comment{}, comment_params)
changeset = Comment.create_changeset(%Comment{}, comment_params)

case Repo.insert(changeset) do
{:ok, comment} ->
Expand All @@ -45,6 +45,16 @@ defmodule Accent.GraphQL.Resolvers.Comment do
end
end

@spec delete(Comment.t(), any(), GraphQLContext.t()) :: comment_operation
def delete(comment, _, _) do
{:ok, comment} =
comment
|> Comment.delete_changeset()
|> Repo.delete()

{:ok, %{comment: comment, errors: nil}}
end

@spec list_project(Project.t(), %{page: number()}, GraphQLContext.t()) :: {:ok, Paginated.t(Comment.t())}
def list_project(project, args, _) do
Comment
Expand Down
11 changes: 11 additions & 0 deletions test/graphql/resolvers/comment_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ defmodule AccentTest.GraphQL.Resolvers.Comment do
assert get_in(Repo.all(Comment), [Access.all(), Access.key(:text)]) == ["First comment"]
end

test "delete", %{translation: translation, user: user} do
comment = %Comment{translation_id: translation.id, text: "test", user: user} |> Repo.insert!()

assert get_in(Repo.all(Comment), [Access.all(), Access.key(:id)]) == [comment.id]

{:ok, result} = Resolver.delete(comment, nil, nil)

assert get_in(result, [:errors]) == nil
assert Repo.all(Comment) == []
end

test "list project", %{project: project, translation: translation, user: user} do
comment = %Comment{translation_id: translation.id, text: "test", user: user} |> Repo.insert!()

Expand Down
16 changes: 15 additions & 1 deletion webapp/app/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,10 @@
"conflict_on_slave": "The text has been moved to review and is now:"
}
},
"translation_comment_delete": {
"delete": "Delete",
"delete_comment_confirm": "Are you sure you want to delete this comment? This action cannot be undone."
},
"translation_comment_form": {
"comment_button": "Comment",
"comment_placeholder": "Leave a comment…",
Expand Down Expand Up @@ -940,6 +944,12 @@
"promote_master_revision_failure": "The language could not be promoted as master",
"promote_master_revision_success": "The language has been promoted as master with success"
}
},
"comments": {
"flash_messages": {
"delete_error": "The comment could not be deleted",
"delete_success": "The comment has been deleted with success"
}
}
},
"projects": {
Expand Down Expand Up @@ -971,7 +981,11 @@
}
},
"comments": {
"loading_content": "Fetching comments…"
"loading_content": "Fetching comments…",
"flash_messages": {
"delete_error": "The comment could not be deleted",
"delete_success": "The comment has been deleted with success"
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Component from '@glimmer/component';
interface Args {
project: any;
comments: any;
onDeleteComment: (comment: {id: string}) => Promise<void>;
}

export default class ProjectCommentsList extends Component<Args> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import parsedKeyProperty from 'accent-webapp/computed-macros/parsed-key';
interface Args {
groupedComment: any;
project: any;
onDeleteComment: (comment: {id: string}) => Promise<void>;
}

export default class ProjectCommentsListItem extends Component<Args> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@
"translationCommentsList translationRemoved"
"translationCommentsList"
}}
@onDeleteComment={{@onDeleteComment}}
/>
</li>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ProjectCommentsList::Item
@project={{@project}}
@groupedComment={{groupedComment}}
@onDeleteComment={{@onDeleteComment}}
/>
{{else}}
<EmptyContent
Expand Down
28 changes: 28 additions & 0 deletions webapp/app/pods/components/translation-comment-delete/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Component from '@glimmer/component';
import {inject as service} from '@ember/service';
import {action} from '@ember/object';
import IntlService from 'ember-intl/services/intl';

interface Args {
comment: any;
onSubmit: () => void;
}

export default class TranslationCommentDelete extends Component<Args> {
@service('intl')
intl: IntlService;

@action
deleteComment() {
const message = this.intl.t(
'components.translation_comment_delete.delete_comment_confirm'
);

// eslint-disable-next-line no-alert
if (!window.confirm(message)) {
return;
}

this.args.onSubmit();
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<AsyncButton
onClick={{fn this.deleteComment}}
class="button button--red button--small button--filled"
>
{{t "components.translation_comment_delete.delete"}}
</AsyncButton>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Component from '@glimmer/component';

interface Args {
comments: any;
onDeleteComment: (comment: {id: string}) => Promise<void>;
}

export default class TranslationsCommentsList extends Component<Args> {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {inject as service} from '@ember/service';
import {readOnly} from '@ember/object/computed';
import Component from '@glimmer/component';
import MarkdownIt from 'markdown-it';
import {htmlSafe} from '@ember/string';
import Session from 'accent-webapp/services/session';
import {dropTask} from 'ember-concurrency-decorators';

const markdown = MarkdownIt({
html: false,
Expand All @@ -10,17 +14,35 @@ const markdown = MarkdownIt({

interface Args {
comment: {
id: string;
text: string;
insertedAt: Date;
user: {
id: string;
fullname: string;
pictureUrl: string;
};
};
onDeleteComment: (comment: {id: string}) => Promise<void>;
}

export default class TranslationsCommentsListItem extends Component<Args> {
@service('session')
session: Session;

@readOnly('session.credentials.user')
currentUser: any;

get isAuthor() {
return this.currentUser.id === this.args.comment.user.id;
}

get text() {
return htmlSafe(markdown.render(this.args.comment.text));
}

@dropTask
*deleteComment() {
yield this.args.onDeleteComment(this.args.comment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
.date {
color: var(--color-grey);
font-size: 11px;
margin-right: 6px;
}

.content {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<span local-class="date">
<TimeAgoInWordsTag @date={{@comment.insertedAt}} />
</span>

{{#if this.isAuthor }}
<TranslationCommentDelete @onSubmit={{perform this.deleteComment}} />
{{/if}}
</div>

<div local-class="content">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<ul local-class="translation-comments-list" ...attributes>
{{#each @comments key="id" as |comment|}}
<li local-class="itemComment">
<TranslationCommentsList::Item @comment={{comment}} />
<TranslationCommentsList::Item
@comment={{comment}}
@onDeleteComment={{@onDeleteComment}}
/>
</li>
{{else}}
<EmptyContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface Args {
onCreateSubscription: (user: any) => Promise<void>;
onDeleteSubscription: (subscription: any) => Promise<void>;
onSubmit: (text: string) => Promise<void>;
onDeleteComment: (comment: {id: string}) => Promise<void>;
onSelectPage: (page: number) => void;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<div local-class="list">
<TranslationCommentsList
@comments={{@comments.entries}}
@onDeleteComment={{@onDeleteComment}}
local-class="at-translation"
/>

Expand Down
38 changes: 38 additions & 0 deletions webapp/app/pods/logged-in/project/comments/controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import {inject as service} from '@ember/service';
import {action} from '@ember/object';
import {equal, and} from '@ember/object/computed';
import Controller from '@ember/controller';
import {tracked} from '@glimmer/tracking';
import IntlService from 'ember-intl/services/intl';

import commentDeleteQuery from 'accent-webapp/queries/delete-comment';
import ApolloMutate from 'accent-webapp/services/apollo-mutate';
import FlashMessages from 'ember-cli-flash/services/flash-messages';

const FLASH_MESSAGE_PREFIX = 'pods.project.comments.flash_messages.';
const FLASH_MESSAGE_DELETE_COMMENT_SUCCESS = `${FLASH_MESSAGE_PREFIX}delete_success`;
const FLASH_MESSAGE_DELETE_COMMENT_ERROR = `${FLASH_MESSAGE_PREFIX}delete_error`;

export default class CommentsController extends Controller {
queryParams = ['page'];

@service('apollo-mutate')
apolloMutate: ApolloMutate;

@service('flash-messages')
flashMessages: FlashMessages;

@service('intl')
intl: IntlService;

@tracked
page: number | null = 1;

Expand All @@ -21,4 +40,23 @@ export default class CommentsController extends Controller {

this.page = page;
}

@action
async deleteComment(comment: {id: string}) {
const response = await this.apolloMutate.mutate({
mutation: commentDeleteQuery,
refetchQueries: ['ProjectComments'],
variables: {
commentId: comment.id,
},
});

if (response.errors) {
this.flashMessages.error(this.intl.t(FLASH_MESSAGE_DELETE_COMMENT_ERROR));
} else {
this.flashMessages.success(
this.intl.t(FLASH_MESSAGE_DELETE_COMMENT_SUCCESS)
);
}
}
}
1 change: 1 addition & 0 deletions webapp/app/pods/logged-in/project/comments/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ProjectCommentsList
@project={{this.model.project}}
@comments={{this.model.comments.entries}}
@onDeleteComment={{fn this.deleteComment}}
/>
<ResourcePagination
@meta={{this.model.comments.meta}}
Expand Down

0 comments on commit 62ed72e

Please sign in to comment.