Commit 1f5815f5 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'ee/master' into 2302-environment-specific-variables

* ee/master:
  Port of mk-add-project-moved-errors-for-git to EE
  Find first td within the table before assertion
  Add namespace license checks for Service Desk (EEP)
  set ENV['prometheus_multiproc_dir'] in config/boot.rb instead of config.ru
  Use `destroy` instead of `delete` so callbacks are triggered.
  Rename "Slash commands" to "Quick actions"
  Don't track historical data when there isn't a license or it's trial
parents c3c9032b 45efbff1
......@@ -34,7 +34,7 @@ class GfmAutoComplete {
const $input = $(input);
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
// This triggers at.js again
// Needed for slash commands with suffixes (ex: /label ~)
// Needed for quick actions with suffixes (ex: /label ~)
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
$input.on('clear-commands-cache.atwho', () => this.clearCache());
});
......@@ -48,8 +48,8 @@ class GfmAutoComplete {
if (this.enableMap.mergeRequests) this.setupMergeRequests($input);
if (this.enableMap.labels) this.setupLabels($input);
// We don't instantiate the slash commands autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-slash-commands="true"]').atwho({
// We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-quick-actions="true"]').atwho({
at: '/',
alias: 'commands',
searchKey: 'search',
......
......@@ -41,7 +41,7 @@
<textarea
id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
data-supports-slash-commands="false"
data-supports-quick-actionss="false"
aria-label="Description"
v-model="formState.description"
ref="textarea"
......
......@@ -32,7 +32,7 @@ const normalizeNewlines = function(str) {
(function() {
this.Notes = (function() {
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_SLASH_COMMANDS = /^\/\w+.*$/gm;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
Notes.interval = null;
......@@ -280,7 +280,7 @@ const normalizeNewlines = function(str) {
return this.initRefresh();
};
Notes.prototype.handleSlashCommands = function(noteEntity) {
Notes.prototype.handleQuickActions = function(noteEntity) {
var votesBlock;
if (noteEntity.commands_changes) {
if ('merge' in noteEntity.commands_changes) {
......@@ -1198,27 +1198,27 @@ const normalizeNewlines = function(str) {
};
/**
* Identify if comment has any slash commands
* Identify if comment has any quick actions
*/
Notes.prototype.hasSlashCommands = function(formContent) {
return REGEX_SLASH_COMMANDS.test(formContent);
Notes.prototype.hasQuickActions = function(formContent) {
return REGEX_QUICK_ACTIONS.test(formContent);
};
/**
* Remove slash commands and leave comment with pure message
* Remove quick actions and leave comment with pure message
*/
Notes.prototype.stripSlashCommands = function(formContent) {
return formContent.replace(REGEX_SLASH_COMMANDS, '').trim();
Notes.prototype.stripQuickActions = function(formContent) {
return formContent.replace(REGEX_QUICK_ACTIONS, '').trim();
};
/**
* Gets appropriate description from slash commands found in provided `formContent`
* Gets appropriate description from quick actions found in provided `formContent`
*/
Notes.prototype.getSlashCommandDescription = function (formContent, availableSlashCommands = []) {
Notes.prototype.getQuickActionDescription = function (formContent, availableQuickActions = []) {
let tempFormContent;
// Identify executed slash commands from `formContent`
const executedCommands = availableSlashCommands.filter((command, index) => {
// Identify executed quick actions from `formContent`
const executedCommands = availableQuickActions.filter((command, index) => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(formContent);
});
......@@ -1276,7 +1276,7 @@ const normalizeNewlines = function(str) {
};
/**
* Create Placeholder System Note DOM element populated with slash command description
* Create Placeholder System Note DOM element populated with quick action description
*/
Notes.prototype.createPlaceholderSystemNote = function ({ formContent, uniqueId }) {
const $tempNote = $(
......@@ -1325,7 +1325,7 @@ const normalizeNewlines = function(str) {
const { formData, formContent, formAction } = this.getFormData($form);
let noteUniqueId;
let systemNoteUniqueId;
let hasSlashCommands = false;
let hasQuickActions = false;
let $notesContainer;
let tempFormContent;
......@@ -1344,9 +1344,9 @@ const normalizeNewlines = function(str) {
}
tempFormContent = formContent;
if (this.hasSlashCommands(formContent)) {
tempFormContent = this.stripSlashCommands(formContent);
hasSlashCommands = true;
if (this.hasQuickActions(formContent)) {
tempFormContent = this.stripQuickActions(formContent);
hasQuickActions = true;
}
// Show placeholder note
......@@ -1363,10 +1363,10 @@ const normalizeNewlines = function(str) {
}
// Show placeholder system note
if (hasSlashCommands) {
if (hasQuickActions) {
systemNoteUniqueId = _.uniqueId('tempSystemNote_');
$notesContainer.append(this.createPlaceholderSystemNote({
formContent: this.getSlashCommandDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
uniqueId: systemNoteUniqueId,
}));
}
......@@ -1388,7 +1388,7 @@ const normalizeNewlines = function(str) {
$notesContainer.find(`#${noteUniqueId}`).remove();
// Reset cached commands list when command is applied
if (hasSlashCommands) {
if (hasQuickActions) {
$form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
}
......@@ -1422,7 +1422,7 @@ const normalizeNewlines = function(str) {
}
if (note.commands_changes) {
this.handleSlashCommands(note);
this.handleQuickActions(note);
}
$form.trigger('ajax:success', [note]);
......@@ -1430,7 +1430,7 @@ const normalizeNewlines = function(str) {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
if (hasSlashCommands) {
if (hasQuickActions) {
$notesContainer.find(`#${systemNoteUniqueId}`).remove();
}
......
......@@ -3,7 +3,7 @@
// MarkdownPreview
//
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview
// (including the explanation of slash commands), and showing a warning when
// (including the explanation of quick actions), and showing a warning when
// more than `x` users are referenced.
//
(function () {
......
......@@ -15,10 +15,10 @@ export default {
<div class="time-tracking-help-state">
<div class="time-tracking-info">
<h4>
Track time with slash commands
Track time with quick actions
</h4>
<p>
Slash commands can be used in the issues description and comment boxes.
Quick actions can be used in the issues description and comment boxes.
</p>
<p>
<code>
......
......@@ -16,10 +16,10 @@ export default {
'issuable-time-tracker': timeTracker,
},
methods: {
listenForSlashCommands() {
$(document).on('ajax:success', '.gfm-form', this.slashCommandListened);
listenForQuickActions() {
$(document).on('ajax:success', '.gfm-form', this.quickActionListened);
},
slashCommandListened(e, data) {
quickActionListened(e, data) {
const subscribedCommands = ['spend_time', 'time_estimate'];
let changedCommands;
if (data !== undefined) {
......@@ -35,7 +35,7 @@ export default {
},
},
mounted() {
this.listenForSlashCommands();
this.listenForQuickActions();
},
template: `
<div class="block">
......
......@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
attr_reader :authentication_result
attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
......@@ -14,7 +14,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
before_action :ensure_project_found!
private
......@@ -68,38 +67,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
def ensure_project_found!
render_not_found if project.blank?
end
def project
return @project if defined?(@project)
project_id, _ = project_id_with_suffix
@project =
if project_id.blank?
nil
else
Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
end
end
parse_repo_path unless defined?(@project)
# This method returns two values so that we can parse
# params[:project_id] (untrusted input!) in exactly one place.
def project_id_with_suffix
id = params[:project_id] || ''
%w[.wiki.git .git].each do |suffix|
if id.end_with?(suffix)
# Be careful to only remove the suffix from the end of 'id'.
# Accidentally removing it from the middle is how security
# vulnerabilities happen!
return [id.slice(0, id.length - suffix.length), suffix]
end
end
@project
end
# Something is wrong with params[:project_id]; do not pass it on.
[nil, nil]
def parse_repo_path
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
def render_missing_personal_token
......@@ -114,14 +89,9 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def wiki?
return @wiki if defined?(@wiki)
_, suffix = project_id_with_suffix
@wiki = suffix == '.wiki.git'
end
parse_repo_path unless defined?(@wiki)
def render_not_found
render plain: 'Not Found', status: :not_found
@wiki
end
def handle_basic_authentication(login, password)
......
......@@ -56,7 +56,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access
@access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities)
@access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path)
end
def access_actor
......
......@@ -10,8 +10,8 @@ module NotesHelper
Ability.can_edit_note?(current_user, note)
end
def note_supports_slash_commands?(note)
Notes::SlashCommandsService.supported?(note, current_user)
def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note, current_user)
end
def noteable_json(noteable)
......
......@@ -55,8 +55,13 @@ module EE
end
end
def service_desk_enabled
::EE::Gitlab::ServiceDesk.enabled?(project: self) && super
end
alias_method :service_desk_enabled?, :service_desk_enabled
def service_desk_address
return nil unless service_desk_available?
return nil unless service_desk_enabled?
config = ::Gitlab.config.incoming_email
wildcard = ::Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER
......@@ -118,7 +123,7 @@ module EE
private
def licensed_feature_available?(feature)
globally_available = License.current&.feature_available?(feature)
globally_available = License.feature_available?(feature)
if current_application_settings.should_check_namespace_plan?
globally_available &&
......@@ -131,11 +136,5 @@ module EE
def destroy_mirror_data
mirror_data.destroy
end
def service_desk_available?
return @service_desk_available if defined?(@service_desk_available)
@service_desk_available = EE::Gitlab::ServiceDesk.enabled? && service_desk_enabled?
end
end
end
......@@ -206,6 +206,10 @@ class License < ActiveRecord::Base
restricted_attr(:trueup_to)].all?(&:present?)
end
def trial?
restricted_attr(:trial)
end
private
def restricted_attr(name, default = nil)
......
......@@ -959,7 +959,7 @@ class MergeRequest < ActiveRecord::Base
!has_commits?
end
def mergeable_with_slash_command?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
def mergeable_with_quick_action?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
return false unless can_be_merged_by?(current_user)
return true if autocomplete_precheck
......
......@@ -33,7 +33,7 @@ class Note < ActiveRecord::Base
# Banzai::ObjectRenderer
attr_accessor :user_visible_reference_count
# Attribute used to store the attributes that have ben changed by slash commands.
# Attribute used to store the attributes that have ben changed by quick actions.
attr_accessor :commands_changes
default_value_for :system, false
......
class MattermostSlashCommandsService < ChatSlashCommandsService
class MattermostSlashCommandsService < SlashCommandsService
include TriggersHelper
prop_accessor :token
......
class SlackSlashCommandsService < ChatSlashCommandsService
class SlackSlashCommandsService < SlashCommandsService
include TriggersHelper
def title
......
# Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from.
class ChatSlashCommandsService < Service
class SlashCommandsService < Service
default_value_for :category, 'chat'
prop_accessor :token
......@@ -33,10 +33,10 @@ class ChatSlashCommandsService < Service
user = find_chat_user(params)
if user
Gitlab::ChatCommands::Command.new(project, user, params).execute
Gitlab::SlashCommands::Command.new(project, user, params).execute
else
url = authorize_chat_name_url(params)
Gitlab::ChatCommands::Presenters::Access.new(url).authorize
Gitlab::SlashCommands::Presenters::Access.new(url).authorize
end
end
......
......@@ -10,7 +10,7 @@ class GlobalPolicy < BasePolicy
can! :access_api
can! :access_git
can! :receive_notifications
can! :use_slash_commands
can! :use_quick_actions
end
end
end
......@@ -142,9 +142,9 @@ class IssuableBaseService < BaseService
LabelsFinder.new(current_user, project_id: @project.id).execute
end
def merge_slash_commands_into_params!(issuable)
def merge_quick_actions_into_params!(issuable)
description, command_params =
SlashCommands::InterpretService.new(project, current_user).
QuickActions::InterpretService.new(project, current_user).
execute(params[:description], issuable)
# Avoid a description already set on an issuable to be overwritten by a nil
......@@ -162,7 +162,7 @@ class IssuableBaseService < BaseService
end
def create(issuable)
merge_slash_commands_into_params!(issuable)
merge_quick_actions_into_params!(issuable)
filter_params(issuable)
params.delete(:state_event)
......
......@@ -7,7 +7,7 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
merge_from_slash_command(merge_request) if params[:merge]
merge_from_quick_action(merge_request) if params[:merge]
if merge_request.closed_without_fork?
params.except!(:target_branch, :force_remove_source_branch)
......@@ -86,9 +86,9 @@ module MergeRequests
end
end
def merge_from_slash_command(merge_request)
def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge)
return unless merge_request.mergeable_with_slash_command?(current_user, last_diff_sha: last_diff_sha)
return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
merge_request.update(merge_error: nil)
......
......@@ -9,11 +9,11 @@ module Notes
# We execute commands (extracted from `params[:note]`) on the noteable
# **before** we save the note because if the note consists of commands
# only, there is no need be create a note!
slash_commands_service = SlashCommandsService.new(project, current_user)
quick_actions_service = QuickActionsService.new(project, current_user)
if slash_commands_service.supported?(note)
if quick_actions_service.supported?(note)
options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
content, command_params = slash_commands_service.extract_commands(note, options)
content, command_params = quick_actions_service.extract_commands(note, options)
only_commands = content.empty?
......@@ -30,7 +30,7 @@ module Notes
end
if command_params.present?
slash_commands_service.execute(command_params, note)
quick_actions_service.execute(command_params, note)
# We must add the error after we call #save because errors are reset
# when #save is called
......
module Notes
class SlashCommandsService < BaseService
class QuickActionsService < BaseService
UPDATE_SERVICES = {
'Issue' => Issues::UpdateService,
'MergeRequest' => MergeRequests::UpdateService
......@@ -22,7 +22,7 @@ module Notes
def extract_commands(note, options = {})
return [note.note, {}] unless supported?(note)
SlashCommands::InterpretService.new(project, current_user, options).
QuickActions::InterpretService.new(project, current_user, options).
execute(note.note, note.noteable)
end
......
class PreviewMarkdownService < BaseService
def execute
text, commands = explain_slash_commands(params[:text])
text, commands = explain_quick_actions(params[:text])
users = find_user_references(text)
success(
......@@ -12,11 +12,11 @@ class PreviewMarkdownService < BaseService
private
def explain_slash_commands(text)
def explain_quick_actions(text)
return text, [] unless %w(Issue MergeRequest).include?(commands_target_type)
slash_commands_service = SlashCommands::InterpretService.new(project, current_user)
slash_commands_service.explain(text, find_commands_target)
quick_actions_service = QuickActions::InterpretService.new(project, current_user)
quick_actions_service.explain(text, find_commands_target)
end
def find_user_references(text)
......@@ -36,10 +36,10 @@ class PreviewMarkdownService < BaseService
end
def commands_target_type
params[:slash_commands_target_type]
params[:quick_actions_target_type]
end
def commands_target_id
params[:slash_commands_target_id]
params[:quick_actions_target_id]
end
end
......@@ -32,7 +32,7 @@ module Projects
issuable: noteable,
current_user: current_user
}
SlashCommands::InterpretService.command_definitions.map do |definition|
QuickActions::InterpretService.command_definitions.map do |definition|
next unless definition.available?(opts)
definition.to_h(opts)
......
module SlashCommands
module QuickActions
class InterpretService < BaseService
include Gitlab::SlashCommands::Dsl
include Gitlab::QuickActions::Dsl
attr_reader :issuable
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
def execute(content, issuable)
return [content, {}] unless current_user.can?(:use_slash_commands)
return [content, {}] unless current_user.can?(:use_quick_actions)
@issuable = issuable
@updates = {}
......@@ -20,7 +20,7 @@ module SlashCommands
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and array of changes explained.
def explain(content, issuable)
return [content, []] unless current_user.can?(:use_slash_commands)
return [content, []] unless current_user.can?(:use_quick_actions)
@issuable = issuable
......@@ -32,7 +32,7 @@ module SlashCommands
private
def extractor
Gitlab::SlashCommands::Extractor.new(self.class.command_definitions)
Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
end
desc do
......@@ -71,7 +71,7 @@ module SlashCommands
last_diff_sha = params && params[:merge_request_diff_head_sha]
issuable.is_a?(MergeRequest) &&
issuable.persisted? &&
issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
end
command :merge do
@updates[:merge] = params[:merge_request_diff_head_sha]
......
......@@ -28,7 +28,7 @@
placeholder: admin_namespace_dropdown_label('Search groups'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', all_available: true} })
- if @events.present?
%table.table
%table#events-table.table
%thead
%tr
%th Author
......
- @gfm_form = true
- current_text ||= nil
- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false)
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f
= f.text_area attr, class: classes, placeholder: placeholder, data: { supports_slash_commands: supports_slash_commands }
= f.text_area attr, class: classes, placeholder: placeholder, data: { supports_quick_actions: supports_quick_actions }
- else
= text_area_tag attr, current_text, class: classes, placeholder: placeholder
%a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
......
......@@ -130,7 +130,7 @@
= render 'merge_request_settings', form: f
- if EE::Gitlab::ServiceDesk.enabled?
- if EE::Gitlab::ServiceDesk.enabled?(project: @project)
%hr
%fieldset.js-service-desk-setting-wrapper.features.append-bottom-default
%h5.prepend-top-0
......
......@@ -20,7 +20,7 @@
%p
The subject will be used as the title of the new issue, and the message will be the description.
= link_to 'Slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1
= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
and styling with
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
are supported.
......
......@@ -2,10 +2,10 @@
- model = local_assigns.fetch(:model)
- form = local_assigns.fetch(:form)
- supports_slash_commands = model.new_record?
- supports_quick_actions = model.new_record?
- if supports_slash_commands
- preview_url = preview_markdown_path(project, slash_commands_target_type: model.class.name)
- if supports_quick_actions
- preview_url = preview_markdown_path(project, quick_actions_target_type: model.class.name)
- else
- preview_url = preview_markdown_path(project)
......@@ -17,7 +17,7 @@
= render 'projects/zen', f: form, attr: :description,
classes: 'note-textarea',
placeholder: "Write a comment or drag your files here...",
supports_slash_commands: supports_slash_commands
= render 'shared/notes/hints', supports_slash_commands: supports_slash_commands
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
.clearfix
.error-alert
- supports_slash_commands = note_supports_slash_commands?(@note)
- if supports_slash_commands
- preview_url = preview_markdown_path(@project, slash_commands_target_type: @note.noteable_type, slash_commands_target_id: @note.noteable_id)
- supports_quick_actions = note_supports_quick_actions?(@note)
- if supports_quick_actions
- preview_url = preview_markdown_path(@project, quick_actions_target_type: @note.noteable_type, quick_actions_target_id: @note.noteable_id)
- else
- preview_url = preview_markdown_path(@project)
......@@ -27,8 +27,8 @@
attr: :note,
classes: 'note-textarea js-note-text',
placeholder: "Write a comment or drag your files here...",
supports_slash_commands: supports_slash_commands
= render 'shared/notes/hints', supports_slash_commands: supports_slash_commands
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
.error-alert
.note-form-actions.clearfix
......
- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false)
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
.comment-toolbar.clearfix
.toolbar-text
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
- if supports_slash_commands
- if supports_quick_actions
and
= link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1
= link_to 'quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
are
- else
is
......
......@@ -4,6 +4,8 @@ class HistoricalDataWorker
def perform
return if Gitlab::Geo.secondary?
return if License.current.nil? || License.current&.trial?
HistoricalData.track!
end
end
---
title: 'Add namespace license checks for Service Desk (EEP)'
merge_request: 2109
author:
---
title: Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor
of "slash commands"
merge_request:
author:
......@@ -15,9 +15,6 @@ if defined?(Unicorn)
end
end
# set default directory for multiproces metrics gathering
ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
require ::File.expand_path('../config/environment', __FILE__)
map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do
......
......@@ -5,6 +5,9 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
# set default directory for multiproces metrics gathering
ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
# Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage
require 'bootsnap'
Bootsnap.setup(
......
......@@ -28,7 +28,7 @@ Shortcuts to GitLab's most visited docs:
- [GitLab Workflow](workflow/README.md): Enhance your workflow with the best of GitLab Workflow.
- See also [GitLab Workflow - an overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
- [GitLab Markdown](user/markdown.md): GitLab's advanced formatting system (GitLab Flavored Markdown).
- [GitLab Slash Commands](user/project/slash_commands.md): Textual shortcuts for common actions on issues or merge requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
- [GitLab Quick Actions](user/project/quick_actions.md): Textual shortcuts for common actions on issues or merge requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
### User account
......
......@@ -166,8 +166,8 @@ For instance this kind of thing:
= render 'projects/zen', f: form, attr: :description,
classes: 'note-textarea',
placeholder: "Write a comment or drag your files here...",
supports_slash_commands: !issuable.persisted?
= render 'projects/notes/hints', supports_slash_commands: !issuable.persisted?
supports_quick_actions: !issuable.persisted?
= render 'projects/notes/hints', supports_quick_actions: !issuable.persisted?
.clearfix
.error-alert
- if issuable.is_a?(Issue)
......
# Chat Commands
Chat commands in Mattermost and Slack (also called Slack slash commands) allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it.
Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are:
| Command | Effect |
| ------- | ------ |
| `/project-name help` | Shows all available chat commands |
| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
| `/project-name issue show <id>` | Shows the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
\ No newline at end of file
This document was moved to [integration/slash_commands.md](slash_commands.md).
# Slash Commands
Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it.
Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are:
| Command | Effect |
| ------- | ------ |
| `/project-name help` | Shows all available slash commands |
| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
| `/project-name issue show <id>` | Shows the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
......@@ -10,7 +10,7 @@ You can leave a comment in the following places:
- commits
- commit diffs
The comment area supports [Markdown] and [slash commands]. One can edit their
The comment area supports [Markdown] and [quick actions]. One can edit their
own comment at any time, and anyone with [Master access level][permissions] or
higher can also edit a comment made by someone else.
......@@ -146,5 +146,5 @@ comments in greater detail.
[discussion-view]: img/discussion_view.png
[discussions-resolved]: img/discussions_resolved.png
[markdown]: ../markdown.md
[slash commands]: ../project/slash_commands.md
[quick actions]: ../project/quick_actions.md
[permissions]: ../permissions.md
......@@ -2,7 +2,7 @@
> Introduced in GitLab 8.15
Slack slash commands (also known as chat commmands) allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab.
Slack slash commands allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab.
> Note: GitLab can also send events (e.g. issue created) to Slack as notifications. This is the separately configured [Slack Notifications Service](slack.md).
......@@ -20,4 +20,4 @@ Slack slash commands (also known as chat commmands) allow you to control GitLab
## Usage
You can now use the [Slack slash commands](../../../integration/chat_commands.md).
\ No newline at end of file
You can now use the [Slack slash commands](../../../integration/slash_commands.md).
......@@ -68,7 +68,7 @@ This feature is available only in [GitLab Enterprise Edition](https://about.gitl
- Spend: add the time spent on the implementation of that issue
> **Note:**
both estimate and spend times are set via [GitLab Slash Commands](../slash_commands.md).
both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md).
Learn more on the [Time Tracking documentation](../../../workflow/time_tracking.md).
......@@ -147,7 +147,7 @@ or in the issue thread.
#### 15. Award emoji
- Award an emoji to that issue.
- Award an emoji to that issue.
> **Tip:**
Posting "+1" as comments in threads spam all
......
# GitLab quick actions
Quick actions are textual shortcuts for common actions on issues or merge
requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, and
in comments. Each command should be on a separate line in order to be properly
detected and executed. The commands are removed from the issue, merge request or
comment body before it is saved and will not be visible to anyone else.
Below is a list of all of the available commands and descriptions about what they
do.
| Command | Action |
|:---------------------------|:-------------|
| `/close` | Close the issue or merge request |
| `/reopen` | Reopen the issue or merge request |
| `/merge` | Merge (when pipeline succeeds) |
| `/title <New title>` | Change title |
| `/assign @user1 @user2 ` | Add assignee(s) |
| `/reassign @user1 @user2 ` | Change assignee(s) |
| `/unassign @user1 @user2` | Remove all or specific assignee(s) |
| `/milestone %milestone` | Set milestone |
| `/remove_milestone` | Remove milestone |
| `/label ~foo ~"bar baz"` | Add label(s) |
| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) |
| `/relabel ~foo ~"bar baz"` | Replace all label(s) |
| `/todo` | Add a todo |
| `/done` | Mark todo as done |
| `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
| `/remove_due_date` | Remove due date |
| `/wip` | Toggle the Work In Progress status |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate |
| `/remove_estimate` | Remove estimated time |
| <code>/spend &lt;1h 30m &#124; -1h 5m&gt;</code> | Add or subtract spent time |
| `/remove_time_spent` | Remove time spent |
| `/target_branch <Branch Name>` | Set target branch for current merge request |
| `/award :emoji:` | Toggle award for :emoji: |
| `/weight <1-9>` | Set the weight of the issue |
| `/clear_weight` | Clears the issue weight |
| `/board_move ~column` | Move issue to column on the board |
Note: In GitLab EES every issue can have more than one assignee, so commands `/assign`, `/unassign` and `/reassign`
support multiple assignees.
# GitLab slash commands
Slash commands are textual shortcuts for common actions on issues or merge
requests that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, and
in comments. Each command should be on a separate line in order to be properly
detected and executed. The commands are removed from the issue, merge request or
comment body before it is saved and will not be visible to anyone else.
Below is a list of all of the available commands and descriptions about what they
do.
| Command | Action |
|:---------------------------|:-------------|
| `/close` | Close the issue or merge request |
| `/reopen` | Reopen the issue or merge request |
| `/merge` | Merge (when pipeline succeeds) |
| `/title <New title>` | Change title |
| `/assign @user1 @user2 ` | Add assignee(s) |
| `/reassign @user1 @user2 ` | Change assignee(s) |
| `/unassign @user1 @user2` | Remove all or specific assignee(s) |
| `/milestone %milestone` | Set milestone |
| `/remove_milestone` | Remove milestone |
| `/label ~foo ~"bar baz"` | Add label(s) |
| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) |
| `/relabel ~foo ~"bar baz"` | Replace all label(s) |
| `/todo` | Add a todo |
| `/done` | Mark todo as done |
| `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
| `/remove_due_date` | Remove due date |
| `/wip` | Toggle the Work In Progress status |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate |
| `/remove_estimate` | Remove estimated time |
| <code>/spend &lt;1h 30m &#124; -1h 5m&gt;</code> | Add or subtract spent time |
| `/remove_time_spent` | Remove time spent |
| `/target_branch <Branch Name>` | Set target branch for current merge request |
| `/award :emoji:` | Toggle award for :emoji: |
| `/weight <1-9>` | Set the weight of the issue |
| `/clear_weight` | Clears the issue weight |
| `/board_move ~column` | Move issue to column on the board |
Note: In GitLab EES every issue can have more than one assignee, so commands `/assign`, `/unassign` and `/reassign`
support multiple assignees.
This document was moved to [user/project/quick_actions.md](quick_actions.md).
......@@ -24,7 +24,7 @@
- [Project users](add-user/add-user.md)
- [Protected branches](../user/project/protected_branches.md)
- [Protected tags](../user/project/protected_tags.md)
- [Slash commands](../user/project/slash_commands.md)
- [Quick Actions](../user/project/quick_actions.md)
- [Sharing a project with a group](share_with_group.md)
- [Share projects with other groups](share_projects_with_other_groups.md)
- [Time tracking](time_tracking.md)
......
......@@ -21,13 +21,13 @@ below.
## How to enter data
Time Tracking uses two slash [commands] that are available with this new
Time Tracking uses two [quick actions] that GitLab introduced with this new
feature: `/spend` and `/estimate`.
Slash commands can be used in the body of an issue or a merge request, but also
Quick actions can be used in the body of an issue or a merge request, but also
in a comment in both an issue or a merge request.
Below is an example of how you can use those new slash commands inside a comment.
Below is an example of how you can use those new quick actions inside a comment.
![Time tracking example in a comment](time-tracking/time-tracking-example.png)
......@@ -77,4 +77,4 @@ Other interesting links:
- [Time Tracking landing page on about.gitlab.com][landing]
[landing]: https://about.gitlab.com/features/time-tracking
[commands]: ../user/project/slash_commands.md
[quick actions]: ../user/project/quick_actions.md
......@@ -10,6 +10,10 @@ module API
set_project unless defined?(@project)
@project
end
def redirected_path
@redirected_path
end
def ssh_authentication_abilities
[
......@@ -38,8 +42,9 @@ module API
def set_project
if params[:gl_repository]
@project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository])
@redirected_path = nil
else
@project, @wiki = Gitlab::RepoPath.parse(params[:project])
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project])
end
end
......
......@@ -34,7 +34,7 @@ module API
access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
access_checker = access_checker_klass
.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path)
begin
access_checker.check(params[:action], params[:changes])
......
......@@ -733,7 +733,7 @@ module API
trigger_services.each do |service_slug, settings|
helpers do
def chat_command_service(project, service_slug, params)
def slash_command_service(project, service_slug, params)
project.services.active.where(template: false).find do |service|
service.try(:token) == params[:token] && service.to_param == service_slug.underscore
end
......@@ -758,7 +758,7 @@ module API
# This is not accurate, but done to prevent leakage of the project names
not_found!('Service') unless project
service = chat_command_service(project, service_slug, params)
service = slash_command_service(project, service_slug, params)
result = service.try(:trigger, params)
if result
......
......@@ -654,7 +654,7 @@ module API
trigger_services.each do |service_slug, settings|
helpers do
def chat_command_service(project, service_slug, params)
def slash_command_service(project, service_slug, params)
project.services.active.where(template: false).find do |service|
service.try(:token) == params[:token] && service.to_param == service_slug.underscore
end
......@@ -679,7 +679,7 @@ module API
# This is not accurate, but done to prevent leakage of the project names
not_found!('Service') unless project
service = chat_command_service(project, service_slug, params)
service = slash_command_service(project, service_slug, params)
result = service.try(:trigger, params)
if result
......
......@@ -145,7 +145,7 @@ module EE
elsif group.last_owner?(user)
warn_cannot_remove_last_owner(user, group)
else
group.users.delete(user)
group.users.destroy(user)
end
end
end
......
module EE
module Gitlab
module ServiceDesk
def self.enabled?
::License.current&.feature_available?(:service_desk) &&
::Gitlab::IncomingEmail.enabled? &&
::Gitlab::IncomingEmail.supports_wildcard?
# Check whether a project or GitLab instance can support the Service Desk
# feature. Use `project.service_desk_enabled?` to check whether it is
# enabled for a particular project.
def self.enabled?(project: nil)
return unless ::Gitlab::IncomingEmail.enabled? && ::Gitlab::IncomingEmail.supports_wildcard?
(project || ::License).feature_available?(:service_desk)
end
end
end
......
......@@ -31,9 +31,11 @@ module Gitlab
def project
return @project if instance_variable_defined?(:@project)
@project =
found_project =
Project.where(service_desk_enabled: true)
.find_by_full_path(service_desk_key)
@project = found_project&.service_desk_enabled? ? found_project : nil
end
def create_issue!
......
......@@ -25,12 +25,13 @@ module Gitlab
PUSH_COMMANDS = %w{ git-receive-pack }.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
attr_reader :actor, :project, :protocol, :authentication_abilities
attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path
def initialize(actor, project, protocol, authentication_abilities:)
def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil)
@actor = actor
@project = project
@protocol = protocol
@redirected_path = redirected_path
@authentication_abilities = authentication_abilities
end
......@@ -38,6 +39,7 @@ module Gitlab
check_protocol!
check_active_user!
check_project_accessibility!
check_project_moved!
check_command_disabled!(cmd)
check_command_existence!(cmd)
check_repository_existence!
......@@ -92,6 +94,21 @@ module Gitlab
end
end
def check_project_moved!
if redirected_path
url = protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo
message = <<-MESSAGE.strip_heredoc
Project '#{redirected_path}' was moved to '#{project.full_path}'.
Please update your Git remote and try again:
git remote set-url origin #{url}
MESSAGE
raise NotFoundError, message
end
end
def check_command_disabled!(cmd)
if upload_pack?(cmd)
check_upload_pack_disabled!
......
module Gitlab
module SlashCommands
module QuickActions
class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params,
:condition_block, :parse_params_block, :action_block
......
module Gitlab
module SlashCommands
module QuickActions
module Dsl
extend ActiveSupport::Concern
......@@ -14,7 +14,7 @@ module Gitlab
end
class_methods do
# Allows to give a description to the next slash command.
# Allows to give a description to the next quick action.
# This description is shown in the autocomplete menu.
# It accepts a block that will be evaluated with the context given to
# `CommandDefintion#to_h`.
......@@ -31,7 +31,7 @@ module Gitlab
@description = block_given? ? block : text
end
# Allows to define params for the next slash command.
# Allows to define params for the next quick action.
# These params are shown in the autocomplete menu.
#
# Example:
......
module Gitlab
module SlashCommands
module QuickActions
# This class takes an array of commands that should be extracted from a
# given text.
#
# ```
# extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels])
# extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels])
# ```
class Extractor
attr_reader :command_definitions
......@@ -24,7 +24,7 @@ module Gitlab
#
# Usage:
# ```
# extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels])
# extractor = Gitlab::QuickActions::Extractor.new([:open, :assign, :labels])
# msg = %(hello\n/labels ~foo ~"bar baz"\nworld)
# commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
# msg #=> "hello\nworld"
......
......@@ -3,16 +3,18 @@ module Gitlab
NotFoundError = Class.new(StandardError)
def self.parse(repo_path)
wiki = false
project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false)
project = Project.find_by_full_path(project_path)
if project_path.end_with?('.wiki') && !project
project = Project.find_by_full_path(project_path.chomp('.wiki'))
project, was_redirected = find_project(project_path)
if project_path.end_with?('.wiki') && project.nil?
project, was_redirected = find_project(project_path.chomp('.wiki'))
wiki = true
else
wiki = false
end
[project, wiki]
redirected_path = project_path if was_redirected
[project, wiki, redirected_path]
end
def self.strip_storage_path(repo_path, fail_on_not_found: true)
......@@ -30,5 +32,12 @@ module Gitlab
result.sub(/\A\/*/, '')
end
def self.find_project(project_path)
project = Project.find_by_full_path(project_path, follow_redirects: true)
was_redirected = project && project.full_path.casecmp(project_path) != 0
[project, was_redirected]
end
end
end
module Gitlab
module ChatCommands
module SlashCommands
class BaseCommand
QUERY_LIMIT = 5
......
module Gitlab
module ChatCommands
module SlashCommands
class Command < BaseCommand
COMMANDS = [
Gitlab::ChatCommands::IssueShow,
Gitlab::ChatCommands::IssueNew,
Gitlab::ChatCommands::IssueSearch,
Gitlab::ChatCommands::Deploy
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::Deploy
].freeze
def execute
......@@ -15,10 +15,10 @@ module Gitlab
if command.allowed?(project, current_user)
command.new(project, current_user, params).execute(match)
else
Gitlab::ChatCommands::Presenters::Access.new.access_denied
Gitlab::SlashCommands::Presenters::Access.new.access_denied
end
else
Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
Gitlab::SlashCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
end
end
......
module Gitlab
module ChatCommands
module SlashCommands
class Deploy < BaseCommand
def self.match(text)
/\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
......@@ -24,12 +24,12 @@ module Gitlab
actions = find_actions(from, to)
if actions.none?
Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
Gitlab::SlashCommands::Presenters::Deploy.new(nil).no_actions
elsif actions.one?
action = play!(from, to, actions.first)
Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
Gitlab::SlashCommands::Presenters::Deploy.new(action).present(from, to)
else
Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
Gitlab::SlashCommands::Presenters::Deploy.new(actions).too_many_actions
end
end
......
module Gitlab
module ChatCommands
module SlashCommands
class Help < BaseCommand
# This class has to be used last, as it always matches. It has to match
# because other commands were not triggered and we want to show the help
......@@ -17,7 +17,7 @@ module Gitlab
end
def execute(commands, text)
Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text)
Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, text)
end
def trigger
......
module Gitlab
module ChatCommands
module SlashCommands
class IssueCommand < BaseCommand
def self.available?(project)
project.issues_enabled? && project.default_issues_tracker?
......
module Gitlab
module ChatCommands
module SlashCommands
class IssueNew < IssueCommand
def self.match(text)
# we can not match \n with the dot by passing the m modifier as than
......@@ -35,7 +35,7 @@ module Gitlab
end
def presenter(issue)
Gitlab::ChatCommands::Presenters::IssueNew.new(issue)
Gitlab::SlashCommands::Presenters::IssueNew.new(issue)
end
end
end
......
module Gitlab
module ChatCommands
module SlashCommands
class IssueSearch < IssueCommand
def self.match(text)
/\Aissue\s+search\s+(?<query>.*)/.match(text)
......
module Gitlab
module ChatCommands
module SlashCommands
class IssueShow < IssueCommand
def self.match(text)
/\Aissue\s+show\s+#{Issue.reference_prefix}?(?<iid>\d+)/.match(text)
......@@ -13,9 +13,9 @@ module Gitlab
issue = find_by_iid(match[:iid])
if issue
Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present
Gitlab::SlashCommands::Presenters::IssueShow.new(issue).present
else
Gitlab::ChatCommands::Presenters::Access.new.not_found
Gitlab::SlashCommands::Presenters::Access.new.not_found
end
end
end
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class Access < Presenters::Base
def access_denied
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class Base
include Gitlab::Routing.url_helpers
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class Deploy < Presenters::Base
def present(from, to)
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class Help < Presenters::Base
def present(trigger, text)
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
module IssueBase
def color(issuable)
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class IssueNew < Presenters::Base
include Presenters::IssueBase
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class IssueSearch < Presenters::Base
include Presenters::IssueBase
......
module Gitlab
module ChatCommands
module SlashCommands
module Presenters
class IssueShow < Presenters::Base
include Presenters::IssueBase
......
module Gitlab
module ChatCommands
module SlashCommands
Result = Struct.new(:type, :message)
end
end
......@@ -55,7 +55,7 @@ module Gitlab
end
def service_desk_counts
return {} unless ::License.current&.feature_available?(:service_desk)
return {} unless ::License.feature_available?(:service_desk)
projects_with_service_desk = Project.where(service_desk_enabled: true)
......
......@@ -701,7 +701,7 @@ describe Projects::IssuesController do
end
end
context 'when description has slash commands' do
context 'when description has quick actions' do
before do
sign_in(user)
end
......
require 'spec_helper'
describe Projects::ServiceDeskController do
let(:project) { create(:project_empty_repo, :private) }
let(:project) { create(:project_empty_repo, :private, service_desk_enabled: true) }
let(:user) { create(:user) }
before do
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(License).to receive(:feature_available?).and_call_original
allow(License).to receive(:feature_available?).with(:service_desk) { true }
allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
project.update(service_desk_enabled: true)
project.add_master(user)
sign_in(user)
end
......@@ -41,7 +41,7 @@ describe Projects::ServiceDeskController do
describe 'PUT service desk properties' do
it 'toggles services desk incoming email' do
project.update(service_desk_enabled: false)
project.update!(service_desk_enabled: false)
put :update, namespace_id: project.namespace.to_param, project_id: project, service_desk_enabled: true, format: :json
......
......@@ -15,9 +15,19 @@ FactoryGirl.define do
end
notify_users_at { |l| l.expires_at }
notify_admins_at { |l| l.expires_at }
trait :trial do
restrictions do
{ trial: true }
end
end
end
factory :license do
data { build(:gitlab_license).export }
end
factory :trial_license, class: License do
data { build(:gitlab_license, :trial).export }
end
end
......@@ -51,6 +51,8 @@ describe 'Admin::AuditLogs', feature: true, js: true do
wait_for_requests
find('.select2-results').click
find('#events-table td', match: :first)
expect(page).to have_content('Added user access as Owner')
end
end
......@@ -73,6 +75,8 @@ describe 'Admin::AuditLogs', feature: true, js: true do
wait_for_requests
find('.select2-results').click
find('#events-table td', match: :first)
expect(page).to have_content('Removed user access')
end
end
......
......@@ -81,13 +81,13 @@ describe 'Awards Emoji', feature: true do
end
end
context 'execute /award slash command' do
context 'execute /award quick action' do
it 'toggles the emoji award on noteable', js: true do
execute_slash_command('/award :100:')
execute_quick_action('/award :100:')
expect(find(noteable_award_counter)).to have_text("1")
execute_slash_command('/award :100:')
execute_quick_action('/award :100:')
expect(page).not_to have_selector(noteable_award_counter)
end
......@@ -105,7 +105,7 @@ describe 'Awards Emoji', feature: true do
end
end
def execute_slash_command(cmd)
def execute_quick_action(cmd)
within('.js-main-target-form') do
fill_in 'note[note]', with: cmd
click_button 'Comment'
......
......@@ -208,7 +208,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).not_to have_selector('.atwho-view')
end
it 'triggers autocomplete after selecting a slash command' do
it 'triggers autocomplete after selecting a quick action' do
note = find('#note_note')
page.within '.timeline-content-form' do
note.native.send_keys('')
......
require 'rails_helper'
feature 'Issues > User uses slash commands', feature: true, js: true do
include SlashCommandsHelpers
feature 'Issues > User uses quick actions', feature: true, js: true do
include QuickActionsHelpers
it_behaves_like 'issuable record that supports slash commands in its description and notes', :issue do
it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do
let(:issuable) { create(:issue, project: project) }
end
......
require 'rails_helper'
feature 'Merge Requests > User uses slash commands', feature: true, js: true do
include SlashCommandsHelpers
feature 'Merge Requests > User uses quick actions', feature: true, js: true do
include QuickActionsHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
it_behaves_like 'issuable record that supports slash commands in its description and notes', :merge_request do
it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do
let(:issuable) { create(:merge_request, source_project: project) }
let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
end
......
......@@ -7,8 +7,8 @@ describe 'Service Desk Setting', js: true, feature: true do
before do
project.add_master(user)
login_as(user)
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(true)
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
......
......@@ -533,46 +533,46 @@ import '~/notes';
});
});
describe('hasSlashCommands', () => {
describe('hasQuickActions', () => {
beforeEach(() => {
this.notes = new Notes('', []);
});
it('should return true when comment begins with a slash command', () => {
it('should return true when comment begins with a quick action', () => {
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasSlashCommands).toBeTruthy();
expect(hasQuickActions).toBeTruthy();
});
it('should return false when comment does NOT begin with a slash command', () => {
it('should return false when comment does NOT begin with a quick action', () => {
const sampleComment = 'Hey, /unassign Merging this';
const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasSlashCommands).toBeFalsy();
expect(hasQuickActions).toBeFalsy();
});
it('should return false when comment does NOT have any slash commands', () => {
it('should return false when comment does NOT have any quick actions', () => {
const sampleComment = 'Looking good, Awesome!';
const hasSlashCommands = this.notes.hasSlashCommands(sampleComment);
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasSlashCommands).toBeFalsy();
expect(hasQuickActions).toBeFalsy();
});
});
describe('stripSlashCommands', () => {
it('should strip slash commands from the comment which begins with a slash command', () => {
describe('stripQuickActions', () => {
it('should strip quick actions from the comment which begins with a quick action', () => {
this.notes = new Notes();
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const stripedComment = this.notes.stripSlashCommands(sampleComment);
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('');
});
it('should strip slash commands from the comment but leaves plain comment if it is present', () => {
it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
this.notes = new Notes();
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
const stripedComment = this.notes.stripSlashCommands(sampleComment);
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('Merging this');
});
......@@ -580,14 +580,14 @@ import '~/notes';
it('should NOT strip string that has slashes within', () => {
this.notes = new Notes();
const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
const stripedComment = this.notes.stripSlashCommands(sampleComment);
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe(sampleComment);
});
});
describe('getSlashCommandDescription', () => {
const availableSlashCommands = [
describe('getQuickActionDescription', () => {
const availableQuickActions = [
{ name: 'close', description: 'Close this issue', params: [] },
{ name: 'title', description: 'Change title', params: [{}] },
{ name: 'estimate', description: 'Set time estimate', params: [{}] }
......@@ -597,19 +597,19 @@ import '~/notes';
this.notes = new Notes();
});
it('should return executing slash command description when note has single slash command', () => {
it('should return executing quick action description when note has single quick action', () => {
const sampleComment = '/close';
expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying command to close this issue');
expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying command to close this issue');
});
it('should return generic multiple slash command description when note has multiple slash commands', () => {
it('should return generic multiple quick action description when note has multiple quick actions', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying multiple commands');
expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying multiple commands');
});
it('should return generic slash command description when available slash commands list is not populated', () => {
it('should return generic quick action description when available quick actions list is not populated', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
expect(this.notes.getSlashCommandDescription(sampleComment)).toBe('Applying command');
expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
});
});
......
......@@ -235,6 +235,15 @@ describe EE::Gitlab::LDAP::Sync::Group, lib: true do
expect(group.members.find_by(user_id: user.id).access_level)
.to eq(::Gitlab::Access::OWNER)
end
it 'updates projects authorizations' do
project = create(:empty_project, namespace: group)
group.add_user(user, Gitlab::Access::MASTER)
sync_group.update_permissions
expect(project.authorized_users.find_by(id: user.id)).to be_nil
end
end
context 'when the user is the last owner' do
......
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe EE::Gitlab::ServiceDesk, lib: true do
before do
stub_feature(:service_desk)
stub_feature(:service_desk, true)
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
......@@ -13,7 +13,7 @@ describe EE::Gitlab::ServiceDesk, lib: true do
context 'when license does not support service desk' do
before do
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { false }
stub_feature(:service_desk, false)
end
it { is_expected.to be_falsy }
......
......@@ -91,7 +91,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
end
end
context 'when the note contains slash commands' do
context 'when the note contains quick actions' do
let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") }
context 'and current user cannot update noteable' do
......
......@@ -10,19 +10,16 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
let(:email_raw) { fixture_file('emails/service_desk.eml') }
let(:namespace) { create(:namespace, name: "email") }
let(:project) { create(:project, :public, namespace: namespace, path: "test") }
context 'when service desk is enabled' do
before do
project.update(service_desk_enabled: true)
context 'service desk is enabled for the project' do
let(:project) { create(:empty_project, :public, namespace: namespace, path: 'test', service_desk_enabled: true) }
before do
allow(Notify).to receive(:service_desk_thank_you_email)
.with(kind_of(Integer)).and_return(double(deliver_later!: true))
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(true)
end
it 'sends thank you the email and creates issue' do
......@@ -56,8 +53,7 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
context 'when license does not support service desk' do
before do
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { false }
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
end
it 'does not create an issue or send email' do
......@@ -88,16 +84,14 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
end
end
context 'when service desk is not enabled' do
before do
project.update_attributes(service_desk_enabled: false)
end
context 'service desk is disabled for the project' do
let(:project) { create(:empty_project, :public, namespace: namespace, path: 'test') }
it 'bounces the email' do
expect { receiver.execute }.to raise_error(Gitlab::Email::ProcessingError)
end
it 'doesn\'t create an issue' do
it "doesn't create an issue" do
expect { receiver.execute rescue nil }.not_to change { Issue.count }
end
end
......
......@@ -13,15 +13,15 @@ describe Gitlab::Email::Handler, lib: true do
context 'a Service Desk email' do
it 'uses the Service Desk handler when Service Desk is enabled' do
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(true)
allow(License).to receive(:feature_available?).and_call_original
allow(License).to receive(:feature_available?).with(:service_desk).and_return(true)
expect(handler_for('emails/service_desk.eml', 'some/project')).to be_instance_of(Gitlab::Email::Handler::EE::ServiceDeskHandler)
end
it 'uses no handler when Service Desk is disabled' do
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(false)
allow(License).to receive(:feature_available?).and_call_original
allow(License).to receive(:feature_available?).with(:service_desk).and_return(false)
expect(handler_for('emails/service_desk.eml', 'some/project')).to be_nil
end
......@@ -31,15 +31,15 @@ describe Gitlab::Email::Handler, lib: true do
let!(:user) { create(:user, email: 'jake@adventuretime.ooo', incoming_email_token: 'auth_token') }
it 'uses the create issue handler when Service Desk is enabled' do
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(true)
allow(License).to receive(:feature_available?).and_call_original
allow(License).to receive(:feature_available?).with(:service_desk).and_return(true)
expect(handler_for('emails/valid_new_issue.eml', 'some/project+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
end
it 'uses the create issue handler when Service Desk is disabled' do
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(false)
allow(License).to receive(:feature_available?).and_call_original
allow(License).to receive(:feature_available?).with(:service_desk).and_return(false)
expect(handler_for('emails/valid_new_issue.eml', 'some/project+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
end
......
......@@ -3,12 +3,13 @@ require 'spec_helper'
describe Gitlab::GitAccess, lib: true do
let(:pull_access_check) { access.check('git-upload-pack', '_any') }
let(:push_access_check) { access.check('git-receive-pack', '_any') }
let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) }
let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:actor) { user }
let(:protocol) { 'ssh' }
let(:redirected_path) { nil }
let(:authentication_abilities) do
[
:read_project,
......@@ -163,6 +164,46 @@ describe Gitlab::GitAccess, lib: true do
end
end
describe '#check_project_moved!' do
before do
project.team << [user, :master]
end
context 'when a redirect was not followed to find the project' do
context 'pull code' do
it { expect { pull_access_check }.not_to raise_error }
end
context 'push code' do
it { expect { push_access_check }.not_to raise_error }
end
end
context 'when a redirect was followed to find the project' do
let(:redirected_path) { 'some/other-path' }
context 'pull code' do
it { expect { pull_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) }
it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) }
context 'http protocol' do
let(:protocol) { 'http' }
it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) }
end
end
context 'push code' do
it { expect { push_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) }
it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) }
context 'http protocol' do
let(:protocol) { 'http' }
it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) }
end
end
end
end
describe '#check_command_disabled!' do
before do
project.team << [user, :master]
......
require 'spec_helper'
describe Gitlab::GitAccessWiki, lib: true do
let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let!(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
let(:redirected_path) { nil }
let(:authentication_abilities) do
[
:read_project,
......
require 'spec_helper'
describe Gitlab::SlashCommands::CommandDefinition do
describe Gitlab::QuickActions::CommandDefinition do
subject { described_class.new(:command) }
describe "#all_names" do
......
require 'spec_helper'
describe Gitlab::SlashCommands::Dsl do
describe Gitlab::QuickActions::Dsl do
before :all do
DummyClass = Struct.new(:project) do
include Gitlab::SlashCommands::Dsl # rubocop:disable RSpec/DescribedClass
include Gitlab::QuickActions::Dsl # rubocop:disable RSpec/DescribedClass
desc 'A command with no args'
command :no_args, :none do
......
require 'spec_helper'
describe Gitlab::SlashCommands::Extractor do
describe Gitlab::QuickActions::Extractor do
let(:definitions) do
Class.new do
include Gitlab::SlashCommands::Dsl
include Gitlab::QuickActions::Dsl
command(:reopen, :open) { }
command(:assign) { }
......
......@@ -4,24 +4,44 @@ describe ::Gitlab::RepoPath do
describe '.parse' do
set(:project) { create(:project) }
it 'parses a full repository path' do
expect(described_class.parse(project.repository.path)).to eq([project, false])
end
context 'a repository storage path' do
it 'parses a full repository path' do
expect(described_class.parse(project.repository.path)).to eq([project, false, nil])
end
it 'parses a full wiki path' do
expect(described_class.parse(project.wiki.repository.path)).to eq([project, true])
it 'parses a full wiki path' do
expect(described_class.parse(project.wiki.repository.path)).to eq([project, true, nil])
end
end
it 'parses a relative repository path' do
expect(described_class.parse(project.full_path + '.git')).to eq([project, false])
end
context 'a relative path' do
it 'parses a relative repository path' do
expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil])
end
it 'parses a relative wiki path' do
expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true])
end
it 'parses a relative wiki path' do
expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil])
end
it 'parses a relative path starting with /' do
expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil])
end
context 'of a redirected project' do
let(:redirect) { project.route.create_redirect('foo/bar') }
it 'parses a relative repository path' do
expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar'])
end
it 'parses a relative wiki path' do
expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki'])
end
it 'parses a relative path starting with /' do
expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false])
it 'parses a relative path starting with /' do
expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar'])
end
end
end
end
......@@ -43,4 +63,33 @@ describe ::Gitlab::RepoPath do
)
end
end
describe '.find_project' do
let(:project) { create(:empty_project) }
let(:redirect) { project.route.create_redirect('foo/bar/baz') }
context 'when finding a project by its canonical path' do
context 'when the cases match' do
it 'returns the project and false' do
expect(described_class.find_project(project.full_path)).to eq([project, false])
end
end
context 'when the cases do not match' do
# This is slightly different than web behavior because on the web it is
# easy and safe to redirect someone to the correctly-cased URL. For git
# requests, we should accept wrongly-cased URLs because it is a pain to
# block people's git operations and force them to update remote URLs.
it 'returns the project and false' do
expect(described_class.find_project(project.full_path.upcase)).to eq([project, false])
end
end
end
context 'when finding a project via a redirect' do
it 'returns the project and true' do
expect(described_class.find_project(redirect.path)).to eq([project, true])
end
end
end
end
require 'spec_helper'
describe Gitlab::ChatCommands::Command, service: true do
describe Gitlab::SlashCommands::Command, service: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
......@@ -93,19 +93,19 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'IssueShow is triggered' do
let(:params) { { text: 'issue show 123' } }
it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) }
it { is_expected.to eq(Gitlab::SlashCommands::IssueShow) }
end
context 'IssueCreate is triggered' do
let(:params) { { text: 'issue create my title' } }
it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) }
it { is_expected.to eq(Gitlab::SlashCommands::IssueNew) }
end
context 'IssueSearch is triggered' do
let(:params) { { text: 'issue search my query' } }
it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) }
it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) }
end
end
end
require 'spec_helper'
describe Gitlab::ChatCommands::Deploy, service: true do
describe Gitlab::SlashCommands::Deploy, service: true do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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