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

Ability to delete a team #877

Merged
merged 15 commits into from Sep 23, 2020
Merged

Ability to delete a team #877

merged 15 commits into from Sep 23, 2020

Conversation

slorek
Copy link
Contributor

@slorek slorek commented Sep 11, 2020

https://trello.com/c/FRRtOhuq/668-5-absorb-opss-product-safety-enforcement-into-opss-enforcement

Description

This allows us to delete a team from service.

When a team is deleted:

  • all users on that team are transferred to another designated team. Any users that own a case retain ownership. All user roles are transferred to the new team
  • all case collaborations for that team are transferred to another designated team. If the team owns any cases, the designated team will inherit ownership.
  • audit activity is generated as normal - except when a user on the deleted team is the owner of a case. In this event we add an extra activity showing the team ownership has changed
  • no emails are sent
  • the team will no longer be in the filter cases list
  • the team will no longer appear in the list to transfer case ownership
  • the team will no longer appear in the list to add a team to the case

In our initial use case this process would ordinarily generate many hundreds of emails to the affected users. This implementation suppresses those emails. This could be made optional in a future iteration.

Checklist:

  • Have you documented your changes in the pull request description?
  • Does the change present any security considerations?
  • Is any gem functionality overloaded? Eg: Devise controller methods being overloaded.
  • Has acceptance criteria been tested by a peer?
  • Has the CHANGELOG been updated? (If change is worth telling users about.)

General testing (author)

  • Test without JavaScript
  • Test on small screen

Accessibility testing (author)

  • Works keyboard only
  • Tested with one screen reader
  • Zoom page to 400% - content still visible
  • Disable CSS - does content make sense and appear in a logical order?

@opss-bot opss-bot temporarily deployed to review-app-877 September 11, 2020 10:53 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 11, 2020 12:36 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 11, 2020 14:40 Inactive

delegate :team, :new_team, :user, to: :context

def call
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method call has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

@opss-bot opss-bot temporarily deployed to review-app-877 September 14, 2020 14:34 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 14, 2020 14:47 Inactive
@@ -36,6 +36,9 @@ Style/TrailingCommaInArguments:
Rails/HelperInstanceVariable:
Enabled: false

Lint/AmbiguousBlockAssociation:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled this because fixing it introduces syntax errors: rubocop/rubocop#4222

@opss-bot opss-bot temporarily deployed to review-app-877 September 15, 2020 10:21 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 15, 2020 10:25 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 15, 2020 12:29 Inactive
@slorek slorek changed the title WIP: Delete team Ability to delete a team Sep 15, 2020
@opss-bot opss-bot temporarily deployed to review-app-877 September 15, 2020 15:17 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 21, 2020 11:03 Inactive
@@ -1,6 +1,6 @@
module Investigations::UserFiltersHelper
def entities
User.get_owners(except: current_user).decorate + Team.all_with_organisation.decorate
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need the team organisation. This was previously used in the display_name method but was changed a while ago to no longer require it.

@opss-bot opss-bot temporarily deployed to review-app-877 September 21, 2020 11:14 Inactive
@slorek slorek marked this pull request as ready for review September 21, 2020 11:14
@slorek slorek self-assigned this Sep 21, 2020
@opss-bot opss-bot temporarily deployed to review-app-877 September 21, 2020 11:17 Inactive
app/services/delete_team.rb Outdated Show resolved Hide resolved
@opss-bot opss-bot temporarily deployed to review-app-877 September 21, 2020 11:19 Inactive
Comment on lines +1 to +4
class DeleteTeam
include Interactor

delegate :team, :new_team, :user, to: :context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest rewording this to be clearer that it’s deleting a team and merging all its users & cases into another team:

Suggested change
class DeleteTeam
include Interactor
delegate :team, :new_team, :user, to: :context
class MergeTeams
include Interactor
delegate :team_to_delete, :team_to_keep, :user, to: :context

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 not sure about this. The merging is the thing you have to do to accomplish the team deletion.

Comment on lines 31 to 53
def change_case_owner_to_new_team(collaboration)
ChangeCaseOwner.call!(
investigation: collaboration.investigation,
owner: new_team,
user: user,
rationale: change_case_owner_rationale,
silent: true
)
end

def change_case_owner_team(collaboration)
collaboration.update!(collaborator_id: new_team.id)

metadata = update_owner_activity_class.build_metadata(new_team, change_case_owner_rationale)

update_owner_activity_class.create!(
source: UserSource.new(user: user),
investigation: collaboration.investigation,
title: nil,
body: nil,
metadata: metadata
)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These method names are so similar it's kinda hard to follow. I think I understand what’s going on, but it's taken a bit of head-scratching. I don't have any immediate suggestions on how to make it clearer though. 😬

Copy link
Contributor Author

@slorek slorek Sep 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I'll try to think of better names.


def change
add_column :teams, :deleted_at, :datetime
add_index :teams, :deleted_at, algorithm: :concurrently
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if a partial index would be helpful here, as we'd normally be querying for non-deleted teams?

Suggested change
add_index :teams, :deleted_at, algorithm: :concurrently
add_index :teams, :deleted_at, name: 'not_deleted_index', where: {deleted_at: nil), algorithm: :concurrently

(Probably marginal gains at our scale though)

Comment on lines 50 to 52
# Check deleted teams are not listed
expect(page).to have_css("#team option[value=\"#{team.id}\"]")
expect(page).not_to have_css("#team option[value=\"#{deleted_team.id}\"]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use the Capybara method here to check the values of the select?

Suggested change
# Check deleted teams are not listed
expect(page).to have_css("#team option[value=\"#{team.id}\"]")
expect(page).not_to have_css("#team option[value=\"#{deleted_team.id}\"]")
# Check deleted teams are not listed
expect(page).not_to have_select("Select other team name", with_options: [deleted_team.name])
expect(page).to have_select("Select other team name", with_options: [team.name])

Copy link
Contributor

@nasirkhanbeis nasirkhanbeis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good as expected!

context.fail!(error: "No team supplied") unless team.is_a?(Team)
context.fail!(error: "No new team supplied to absorb cases and users") unless new_team.is_a?(Team)
context.fail!(error: "No user supplied") unless user.is_a?(User)
context.fail!(error: "Team cannot already be deleted") if team.deleted?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context.fail!(error: "Team cannot already be deleted") if team.deleted?
context.fail!(error: "Team already deleted") if team.deleted?

context.fail!(error: "No new team supplied to absorb cases and users") unless new_team.is_a?(Team)
context.fail!(error: "No user supplied") unless user.is_a?(User)
context.fail!(error: "Team cannot already be deleted") if team.deleted?
context.fail!(error: "New team cannot already be deleted") if new_team.deleted?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context.fail!(error: "New team cannot already be deleted") if new_team.deleted?
context.fail!(error: "New team already be deleted") if new_team.deleted?

end

def new_team_already_collaborating?(investigation)
investigation.collaboration_accesses.find_by(collaborator_id: new_team.id).present?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
investigation.collaboration_accesses.find_by(collaborator_id: new_team.id).present?
investigation.collaboration_accesses.where(collaborator: new_team).exists?

namespace :team do
desc "Marks the given team as deleted, assigning their cases and users to another team"
task delete: :environment do
team = Team.find(ENV.fetch("ID", nil))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
team = Team.find(ENV.fetch("ID", nil))
team = Team.find(ENV["ID"])

doc/tasks.md Outdated
cf run-task psd-web "export \$(./env/get-env-from-vcap.sh) && ID=<id> NEW_TEAM_ID=<id> EMAIL=<email address> rake team:delete" --name <task name>
```

Case collaborations and users which belong to the team identified by `ID` will be migrated to another team identified by `NEW_TEAM`. The user identified by `EMAIL` will be attributed to the change on all relevant audit activity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Case collaborations and users which belong to the team identified by `ID` will be migrated to another team identified by `NEW_TEAM`. The user identified by `EMAIL` will be attributed to the change on all relevant audit activity.
Case collaborations and users which belong to the team identified by `ID` will be migrated to another team identified by `NEW_TEAM_ID`. The user identified by `EMAIL` will be attributed to the change on all relevant audit activity.

expect(page).to have_css("#created_by_someone_else_id option[value=\"#{another_active_user.id}\"]")
expect(page).not_to have_css("#created_by_someone_else_id option[value=\"#{another_inactive_user.id}\"]")
expect(page).not_to have_css("#created_by_someone_else_id option[value=\"#{other_deleted_team.id}\"]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as @frankieroberto comment above, you could use the have_select(team.name), that would also test that the value in the drop down is the one team name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored the other existing expectations as well, looks a lot better.

spec/services/delete_team_spec.rb Show resolved Hide resolved
spec/services/delete_team_spec.rb Show resolved Hide resolved
spec/services/delete_team_spec.rb Show resolved Hide resolved

context "when the new team is not already a collaborator on the case" do
it "adds the new team to the case with the same access level as the old team" do
expect { delete_team }.to change { other_team_case.reload.teams_with_read_only_access }.from([team]).to([new_team])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect { delete_team }.to change { other_team_case.reload.teams_with_read_only_access }.from([team]).to([new_team])
expect { delete_team }.to change(other_team_case, :teams_with_read_only_access).from([team]).to([new_team])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't work. Need reload.

@opss-bot opss-bot temporarily deployed to review-app-877 September 22, 2020 14:15 Inactive
@opss-bot opss-bot temporarily deployed to review-app-877 September 23, 2020 09:14 Inactive
@slorek slorek merged commit 90367f7 into master Sep 23, 2020
@slorek slorek deleted the delete_team branch September 23, 2020 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants