Commit 4178123f authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '31103-quick-actions-to-add-remove-zoom-meetings-on-issues' into 'master'

Resolve "Quick actions to add/remove Zoom meetings on issues"

See merge request gitlab-org/gitlab!16609
parents 301ea762 aa739c9f
# frozen_string_literal: true
module Issues
class ZoomLinkService < Issues::BaseService
def initialize(issue, user)
super(issue.project, user)
@issue = issue
end
def add_link(link)
if can_add_link? && (link = parse_link(link))
success(_('Zoom meeting added'), append_to_description(link))
else
error(_('Failed to add a Zoom meeting'))
end
end
def can_add_link?
available? && !link_in_issue_description?
end
def remove_link
if can_remove_link?
success(_('Zoom meeting removed'), remove_from_description)
else
error(_('Failed to remove a Zoom meeting'))
end
end
def can_remove_link?
available? && link_in_issue_description?
end
def parse_link(link)
Gitlab::ZoomLinkExtractor.new(link).links.last
end
private
attr_reader :issue
def issue_description
issue.description || ''
end
def success(message, description)
ServiceResponse
.success(message: message, payload: { description: description })
end
def error(message)
ServiceResponse.error(message: message)
end
def append_to_description(link)
"#{issue_description}\n\n#{link}"
end
def remove_from_description
link = parse_link(issue_description)
return issue_description unless link
issue_description.delete_suffix(link).rstrip
end
def link_in_issue_description?
link = extract_link_from_issue_description
return unless link
Gitlab::ZoomLinkExtractor.new(link).match?
end
def extract_link_from_issue_description
issue_description[/(\S+)\z/, 1]
end
def available?
feature_enabled? && can?
end
def feature_enabled?
Feature.enabled?(:issue_zoom_integration, project)
end
def can?
current_user.can?(:update_issue, project)
end
end
end
...@@ -64,6 +64,8 @@ The following quick actions are applicable to descriptions, discussions and thre ...@@ -64,6 +64,8 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue | | `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue |
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related **(STARTER)** | | `/relate #issue1 #issue2` | ✓ | | | Mark issues as related **(STARTER)** |
| `/move <path/to/project>` | ✓ | | | Move this issue to another project | | `/move <path/to/project>` | ✓ | | | Move this issue to another project |
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/target_branch <local branch name>` | | ✓ | | Set target branch | | `/target_branch <local branch name>` | | ✓ | | Set target branch |
| `/wip` | | ✓ | | Toggle the Work In Progress status | | `/wip` | | ✓ | | Toggle the Work In Progress status |
| `/approve` | | ✓ | | Approve the merge request | | `/approve` | | ✓ | | Approve the merge request |
......
---
title: Implement `/zoom` and `/remove_zoom` quick actions
merge_request: 16609
author:
type: added
...@@ -167,6 +167,49 @@ module Gitlab ...@@ -167,6 +167,49 @@ module Gitlab
issue_iid: quick_action_target.iid issue_iid: quick_action_target.iid
} }
end end
desc _('Add Zoom meeting')
explanation _('Adds a Zoom meeting')
params '<Zoom URL>'
types Issue
condition do
zoom_link_service.can_add_link?
end
parse_params do |link|
zoom_link_service.parse_link(link)
end
command :zoom do |link|
result = zoom_link_service.add_link(link)
if result.success?
@updates[:description] = result.payload[:description]
end
@execution_message[:zoom] = result.message
end
desc _('Remove Zoom meeting')
explanation _('Remove Zoom meeting')
execution_message _('Zoom meeting removed')
types Issue
condition do
zoom_link_service.can_remove_link?
end
command :remove_zoom do
result = zoom_link_service.remove_link
if result.success?
@updates[:description] = result.payload[:description]
end
@execution_message[:remove_zoom] = result.message
end
private
def zoom_link_service
Issues::ZoomLinkService.new(quick_action_target, current_user)
end
end end
end end
end end
......
...@@ -836,6 +836,9 @@ msgstr "" ...@@ -836,6 +836,9 @@ msgstr ""
msgid "Add README" msgid "Add README"
msgstr "" msgstr ""
msgid "Add Zoom meeting"
msgstr ""
msgid "Add a %{type} token" msgid "Add a %{type} token"
msgstr "" msgstr ""
...@@ -1007,6 +1010,9 @@ msgstr "" ...@@ -1007,6 +1010,9 @@ msgstr ""
msgid "Adds a To Do." msgid "Adds a To Do."
msgstr "" msgstr ""
msgid "Adds a Zoom meeting"
msgstr ""
msgid "Adds an issue to an epic." msgid "Adds an issue to an epic."
msgstr "" msgstr ""
...@@ -6268,6 +6274,9 @@ msgstr "" ...@@ -6268,6 +6274,9 @@ msgstr ""
msgid "Failed create wiki" msgid "Failed create wiki"
msgstr "" msgstr ""
msgid "Failed to add a Zoom meeting"
msgstr ""
msgid "Failed to apply commands." msgid "Failed to apply commands."
msgstr "" msgstr ""
...@@ -6340,6 +6349,9 @@ msgstr "" ...@@ -6340,6 +6349,9 @@ msgstr ""
msgid "Failed to protect the environment" msgid "Failed to protect the environment"
msgstr "" msgstr ""
msgid "Failed to remove a Zoom meeting"
msgstr ""
msgid "Failed to remove issue from board, please try again." msgid "Failed to remove issue from board, please try again."
msgstr "" msgstr ""
...@@ -12672,6 +12684,9 @@ msgstr "" ...@@ -12672,6 +12684,9 @@ msgstr ""
msgid "Remove Runner" msgid "Remove Runner"
msgstr "" msgstr ""
msgid "Remove Zoom meeting"
msgstr ""
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch" msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
msgstr "" msgstr ""
...@@ -18118,6 +18133,12 @@ msgstr "" ...@@ -18118,6 +18133,12 @@ msgstr ""
msgid "Your request for access has been queued for review." msgid "Your request for access has been queued for review."
msgstr "" msgstr ""
msgid "Zoom meeting added"
msgstr ""
msgid "Zoom meeting removed"
msgstr ""
msgid "a deleted user" msgid "a deleted user"
msgstr "" msgstr ""
......
...@@ -42,5 +42,6 @@ describe 'Issues > User uses quick actions', :js do ...@@ -42,5 +42,6 @@ describe 'Issues > User uses quick actions', :js do
it_behaves_like 'create_merge_request quick action' it_behaves_like 'create_merge_request quick action'
it_behaves_like 'move quick action' it_behaves_like 'move quick action'
it_behaves_like 'zoom quick actions'
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Issues::ZoomLinkService do
set(:user) { create(:user) }
set(:issue) { create(:issue) }
let(:project) { issue.project }
let(:service) { described_class.new(issue, user) }
let(:zoom_link) { 'https://zoom.us/j/123456789' }
before do
project.add_reporter(user)
end
shared_context 'with Zoom link' do
before do
issue.update!(description: "Description\n\n#{zoom_link}")
end
end
shared_context 'with Zoom link not at the end' do
before do
issue.update!(description: "Description with #{zoom_link} some where")
end
end
shared_context 'without Zoom link' do
before do
issue.update!(description: "Description\n\nhttp://example.com")
end
end
shared_context 'without issue description' do
before do
issue.update!(description: nil)
end
end
shared_context 'feature flag disabled' do
before do
stub_feature_flags(issue_zoom_integration: false)
end
end
shared_context 'insufficient permissions' do
before do
project.add_guest(user)
end
end
describe '#add_link' do
shared_examples 'can add link' do
it 'appends the link to issue description' do
expect(result).to be_success
expect(result.payload[:description])
.to eq("#{issue.description}\n\n#{zoom_link}")
end
end
shared_examples 'cannot add link' do
it 'cannot add the link' do
expect(result).to be_error
expect(result.message).to eq('Failed to add a Zoom meeting')
end
end
subject(:result) { service.add_link(zoom_link) }
context 'without Zoom link in the issue description' do
include_context 'without Zoom link'
include_examples 'can add link'
context 'with invalid Zoom link' do
let(:zoom_link) { 'https://not-zoom.link' }
include_examples 'cannot add link'
end
context 'when feature flag is disabled' do
include_context 'feature flag disabled'
include_examples 'cannot add link'
end
context 'with insufficient permissions' do
include_context 'insufficient permissions'
include_examples 'cannot add link'
end
end
context 'with Zoom link in the issue description' do
include_context 'with Zoom link'
include_examples 'cannot add link'
context 'but not at the end' do
include_context 'with Zoom link not at the end'
include_examples 'can add link'
end
end
context 'without issue description' do
include_context 'without issue description'
include_examples 'can add link'
end
end
describe '#can_add_link?' do
subject { service.can_add_link? }
context 'without Zoom link in the issue description' do
include_context 'without Zoom link'
it { is_expected.to eq(true) }
context 'when feature flag is disabled' do
include_context 'feature flag disabled'
it { is_expected.to eq(false) }
end
context 'with insufficient permissions' do
include_context 'insufficient permissions'
it { is_expected.to eq(false) }
end
end
context 'with Zoom link in the issue description' do
include_context 'with Zoom link'
it { is_expected.to eq(false) }
end
end
describe '#remove_link' do
shared_examples 'cannot remove link' do
it 'cannot remove the link' do
expect(result).to be_error
expect(result.message).to eq('Failed to remove a Zoom meeting')
end
end
subject(:result) { service.remove_link }
context 'with Zoom link in the issue description' do
include_context 'with Zoom link'
it 'removes the link from the issue description' do
expect(result).to be_success
expect(result.payload[:description])
.to eq(issue.description.delete_suffix("\n\n#{zoom_link}"))
end
context 'when feature flag is disabled' do
include_context 'feature flag disabled'
include_examples 'cannot remove link'
end
context 'with insufficient permissions' do
include_context 'insufficient permissions'
include_examples 'cannot remove link'
end
context 'but not at the end' do
include_context 'with Zoom link not at the end'
include_examples 'cannot remove link'
end
end
context 'without Zoom link in the issue description' do
include_context 'without Zoom link'
include_examples 'cannot remove link'
end
context 'without issue description' do
include_context 'without issue description'
include_examples 'cannot remove link'
end
end
describe '#can_remove_link?' do
subject { service.can_remove_link? }
context 'with Zoom link in the issue description' do
include_context 'with Zoom link'
it { is_expected.to eq(true) }
context 'when feature flag is disabled' do
include_context 'feature flag disabled'
it { is_expected.to eq(false) }
end
context 'with insufficient permissions' do
include_context 'insufficient permissions'
it { is_expected.to eq(false) }
end
end
context 'without Zoom link in the issue description' do
include_context 'without Zoom link'
it { is_expected.to eq(false) }
end
end
describe '#parse_link' do
subject { service.parse_link(description) }
context 'with valid Zoom links' do
where(:description) do
[
'Some text https://zoom.us/j/123456789 more text',
'Mixed https://zoom.us/j/123456789 http://example.com',
'Multiple link https://zoom.us/my/name https://zoom.us/j/123456789'
]
end
with_them do
it { is_expected.to eq('https://zoom.us/j/123456789') }
end
end
context 'with invalid Zoom links' do
where(:description) do
[
nil,
'',
'Text only',
'Non-Zoom http://example.com',
'Almost Zoom http://zoom.us'
]
end
with_them do
it { is_expected.to eq(nil) }
end
end
end
end
# frozen_string_literal: true
shared_examples 'zoom quick actions' do
let(:zoom_link) { 'https://zoom.us/j/123456789' }
let(:invalid_zoom_link) { 'https://invalid-zoom' }
before do
issue.update!(description: description)
end
describe '/zoom' do
shared_examples 'skip silently' do
it 'skip addition silently' do
add_note("/zoom #{zoom_link}")
wait_for_requests
expect(page).not_to have_content('Zoom meeting added')
expect(page).not_to have_content('Failed to add a Zoom meeting')
expect(issue.reload.description).to eq(description)
end
end
shared_examples 'success' do
it 'adds a Zoom link' do
add_note("/zoom #{zoom_link}")
wait_for_requests
expect(page).to have_content('Zoom meeting added')
expect(issue.reload.description).to end_with(zoom_link)
end
end
context 'without issue description' do
let(:description) { nil }
include_examples 'success'
it 'cannot add invalid zoom link' do
add_note("/zoom #{invalid_zoom_link}")
wait_for_requests
expect(page).to have_content('Failed to add a Zoom meeting')
expect(page).not_to have_content(zoom_link)
end
context 'when feature flag disabled' do
before do
stub_feature_flags(issue_zoom_integration: false)
end
include_examples 'skip silently'
end
end
context 'with Zoom link not at the end of the issue description' do
let(:description) { "A link #{zoom_link} not at the end" }
include_examples 'success'
end
context 'with Zoom link at end of the issue description' do
let(:description) { "Text\n#{zoom_link}" }
include_examples 'skip silently'
end
end
describe '/remove_zoom' do
shared_examples 'skip silently' do
it 'skip removal silently' do
add_note('/remove_zoom')
wait_for_requests
expect(page).not_to have_content('Zoom meeting removed')
expect(page).not_to have_content('Failed to remove a Zoom meeting')
expect(issue.reload.description).to eq(description)
end
end
context 'with Zoom link in the description' do
let(:description) { "Text with #{zoom_link}\n\n\n#{zoom_link}" }
it 'removes last Zoom link' do
add_note('/remove_zoom')
wait_for_requests
expect(page).to have_content('Zoom meeting removed')
expect(issue.reload.description).to eq("Text with #{zoom_link}")
end
context 'when feature flag disabled' do
before do
stub_feature_flags(issue_zoom_integration: false)
end
include_examples 'skip silently'
end
end
context 'with a Zoom link not at the end of the description' do
let(:description) { "A link #{zoom_link} not at the end" }
include_examples 'skip silently'
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment