summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-06 09:06:23 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-06 09:06:23 +0000
commitd15180e00b209d0fbe3d8ce61b3af269aecdf7f5 (patch)
treee24bcc044a3e471811b91ade8a23120a27210c3f
parent505c40d537244b35807129ade0c577f752e9d564 (diff)
downloadgitlab-ce-d15180e00b209d0fbe3d8ce61b3af269aecdf7f5.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue3
-rw-r--r--app/assets/javascripts/ide/stores/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/clientside/actions.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/clientside/index.js6
-rw-r--r--app/controllers/projects/usage_ping_controller.rb13
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb8
-rw-r--r--changelogs/unreleased/32398-geo-rake-gitlab-geo-check-on-the-primary-is-cluttered.yml5
-rw-r--r--changelogs/unreleased/add-slack-slash-command-issue-comment.yml5
-rw-r--r--changelogs/unreleased/fj-24837-codesanbox-usage-ping.yml5
-rw-r--r--changelogs/unreleased/jc-dont-try-to-movedirs-unless-legacy.yml5
-rw-r--r--config/routes/project.rb4
-rw-r--r--doc/integration/slash_commands.md1
-rw-r--r--lib/gitlab/slash_commands/command.rb1
-rw-r--r--lib/gitlab/slash_commands/issue_comment.rb55
-rw-r--r--lib/gitlab/slash_commands/presenters/access.rb4
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_comment.rb43
-rw-r--r--lib/gitlab/slash_commands/presenters/note_base.rb48
-rw-r--r--lib/gitlab/usage_data.rb3
-rw-r--r--lib/gitlab/usage_data_counters/web_ide_counter.rb14
-rw-r--r--spec/controllers/projects/usage_ping_controller_spec.rb64
-rw-r--r--spec/frontend/ide/components/preview/clientside_spec.js16
-rw-r--r--spec/frontend/ide/stores/modules/clientside/actions_spec.js39
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb5
-rw-r--r--spec/lib/gitlab/slash_commands/issue_comment_spec.rb117
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb10
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb37
-rw-r--r--spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb34
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb3
-rw-r--r--spec/models/namespace_spec.rb38
-rw-r--r--spec/routing/project_routing_spec.rb6
30 files changed, 599 insertions, 7 deletions
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index 6999746f115..beb179d0411 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -92,6 +92,7 @@ export default {
},
methods: {
...mapActions(['getFileData', 'getRawFileData']),
+ ...mapActions('clientside', ['pingUsage']),
loadFileContent(path) {
return this.getFileData({ path, makeFileActive: false }).then(() =>
this.getRawFileData({ path }),
@@ -100,6 +101,8 @@ export default {
initPreview() {
if (!this.mainEntry) return null;
+ this.pingUsage();
+
return this.loadFileContent(this.mainEntry)
.then(() => this.$nextTick())
.then(() => {
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index f1f544b52b2..85550578e94 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -10,6 +10,7 @@ import mergeRequests from './modules/merge_requests';
import branches from './modules/branches';
import fileTemplates from './modules/file_templates';
import paneModule from './modules/pane';
+import clientsideModule from './modules/clientside';
Vue.use(Vuex);
@@ -26,6 +27,7 @@ export const createStore = () =>
branches,
fileTemplates: fileTemplates(),
rightPane: paneModule(),
+ clientside: clientsideModule(),
},
});
diff --git a/app/assets/javascripts/ide/stores/modules/clientside/actions.js b/app/assets/javascripts/ide/stores/modules/clientside/actions.js
new file mode 100644
index 00000000000..eb3bcdff2ae
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/clientside/actions.js
@@ -0,0 +1,12 @@
+import axios from '~/lib/utils/axios_utils';
+
+export const pingUsage = ({ rootGetters }) => {
+ const { web_url: projectUrl } = rootGetters.currentProject;
+
+ const url = `${projectUrl}/usage_ping/web_ide_clientside_preview`;
+
+ return axios.post(url);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/clientside/index.js b/app/assets/javascripts/ide/stores/modules/clientside/index.js
new file mode 100644
index 00000000000..b28f7b935a8
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/clientside/index.js
@@ -0,0 +1,6 @@
+import * as actions from './actions';
+
+export default () => ({
+ namespaced: true,
+ actions,
+});
diff --git a/app/controllers/projects/usage_ping_controller.rb b/app/controllers/projects/usage_ping_controller.rb
new file mode 100644
index 00000000000..ebdf28bd59c
--- /dev/null
+++ b/app/controllers/projects/usage_ping_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Projects::UsagePingController < Projects::ApplicationController
+ before_action :authenticate_user!
+
+ def web_ide_clientside_preview
+ return render_404 unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
+
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_previews_count
+
+ head(200)
+ end
+end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 78544405c49..ce98a3ad3d6 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -55,7 +55,7 @@ module Storage
def move_repositories
# Move the namespace directory in all storages used by member projects
- repository_storages.each do |repository_storage|
+ repository_storages(legacy_only: true).each do |repository_storage|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
@@ -77,12 +77,14 @@ module Storage
@old_repository_storage_paths ||= repository_storages
end
- def repository_storages
+ def repository_storages(legacy_only: false)
# We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT.
Project.unscoped do
- all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage)
+ namespace_projects = all_projects
+ namespace_projects = namespace_projects.without_storage_feature(:repository) if legacy_only
+ namespace_projects.pluck(Arel.sql('distinct(repository_storage)'))
end
end
diff --git a/changelogs/unreleased/32398-geo-rake-gitlab-geo-check-on-the-primary-is-cluttered.yml b/changelogs/unreleased/32398-geo-rake-gitlab-geo-check-on-the-primary-is-cluttered.yml
new file mode 100644
index 00000000000..e8a8828b929
--- /dev/null
+++ b/changelogs/unreleased/32398-geo-rake-gitlab-geo-check-on-the-primary-is-cluttered.yml
@@ -0,0 +1,5 @@
+---
+title: "[Geo] Fix: rake gitlab:geo:check on the primary is cluttered"
+merge_request: 19460
+author:
+type: changed
diff --git a/changelogs/unreleased/add-slack-slash-command-issue-comment.yml b/changelogs/unreleased/add-slack-slash-command-issue-comment.yml
new file mode 100644
index 00000000000..ccd82830303
--- /dev/null
+++ b/changelogs/unreleased/add-slack-slash-command-issue-comment.yml
@@ -0,0 +1,5 @@
+---
+title: Add a Slack slash command to add a comment to an issue
+merge_request: 18946
+author:
+type: added
diff --git a/changelogs/unreleased/fj-24837-codesanbox-usage-ping.yml b/changelogs/unreleased/fj-24837-codesanbox-usage-ping.yml
new file mode 100644
index 00000000000..480fdbd51f4
--- /dev/null
+++ b/changelogs/unreleased/fj-24837-codesanbox-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add Codesandbox metrics to usage ping
+merge_request: 19075
+author:
+type: other
diff --git a/changelogs/unreleased/jc-dont-try-to-movedirs-unless-legacy.yml b/changelogs/unreleased/jc-dont-try-to-movedirs-unless-legacy.yml
new file mode 100644
index 00000000000..b1fd8a29a4f
--- /dev/null
+++ b/changelogs/unreleased/jc-dont-try-to-movedirs-unless-legacy.yml
@@ -0,0 +1,5 @@
+---
+title: Only move repos for legacy project storage
+merge_request: 19410
+author:
+type: fixed
diff --git a/config/routes/project.rb b/config/routes/project.rb
index d628e1ea650..d49ba20ce84 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -617,6 +617,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ scope :usage_ping, controller: :usage_ping do
+ post :web_ide_clientside_preview
+ end
+
# Since both wiki and repository routing contains wildcard characters
# its preferable to keep it below all other project routes
draw :wiki
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index b8842ef3a43..bc2f190920c 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -18,6 +18,7 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name issue close <id>` | Closes the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` |
+| `/project-name issue comment <id> <shift+return> <comment>` | Adds a new comment to an issue with id `<id>` and comment body `<comment>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
| `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` |
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 079b5916566..239479f99d2 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -10,6 +10,7 @@ module Gitlab
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::IssueClose,
+ Gitlab::SlashCommands::IssueComment,
Gitlab::SlashCommands::Deploy,
Gitlab::SlashCommands::Run
]
diff --git a/lib/gitlab/slash_commands/issue_comment.rb b/lib/gitlab/slash_commands/issue_comment.rb
new file mode 100644
index 00000000000..cbb9c41aab0
--- /dev/null
+++ b/lib/gitlab/slash_commands/issue_comment.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ class IssueComment < IssueCommand
+ def self.match(text)
+ /\Aissue\s+comment\s+#{Issue.reference_prefix}?(?<iid>\d+)\n*(?<note_body>(.|\n)*)/.match(text)
+ end
+
+ def self.help_message
+ 'issue comment <id> *`⇧ Shift`*+*`↵ Enter`* <comment>'
+ end
+
+ def execute(match)
+ note_body = match[:note_body].to_s.strip
+ issue = find_by_iid(match[:iid])
+
+ return not_found unless issue
+ return access_denied unless can_create_note?(issue)
+
+ note = create_note(issue: issue, note: note_body)
+
+ if note.persisted?
+ presenter(note).present
+ else
+ presenter(note).display_errors
+ end
+ end
+
+ private
+
+ def can_create_note?(issue)
+ Ability.allowed?(current_user, :create_note, issue)
+ end
+
+ def not_found
+ Gitlab::SlashCommands::Presenters::Access.new.not_found
+ end
+
+ def access_denied
+ Gitlab::SlashCommands::Presenters::Access.new.generic_access_denied
+ end
+
+ def create_note(issue:, note:)
+ note_params = { noteable: issue, note: note }
+
+ Notes::CreateService.new(project, current_user, note_params).execute
+ end
+
+ def presenter(note)
+ Gitlab::SlashCommands::Presenters::IssueComment.new(note)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/access.rb b/lib/gitlab/slash_commands/presenters/access.rb
index 9ce1bcfb37c..fbc3cf2e049 100644
--- a/lib/gitlab/slash_commands/presenters/access.rb
+++ b/lib/gitlab/slash_commands/presenters/access.rb
@@ -15,6 +15,10 @@ module Gitlab
MESSAGE
end
+ def generic_access_denied
+ ephemeral_response(text: 'You are not allowed to perform the given chatops command.')
+ end
+
def deactivated
ephemeral_response(text: <<~MESSAGE)
You are not allowed to perform the given chatops command since
diff --git a/lib/gitlab/slash_commands/presenters/issue_comment.rb b/lib/gitlab/slash_commands/presenters/issue_comment.rb
new file mode 100644
index 00000000000..cce71e23b21
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/issue_comment.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class IssueComment < Presenters::Base
+ include Presenters::NoteBase
+
+ def present
+ ephemeral_response(new_note)
+ end
+
+ private
+
+ def new_note
+ {
+ attachments: [
+ {
+ title: "#{issue.title} · #{issue.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "New comment on #{issue.to_reference}: #{issue.title}",
+ pretext: pretext,
+ color: color,
+ fields: fields,
+ mrkdwn_in: [
+ :title,
+ :pretext,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def pretext
+ "I commented on an issue on #{author_profile_link}'s behalf: *#{issue.to_reference}* in #{project_link}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/note_base.rb b/lib/gitlab/slash_commands/presenters/note_base.rb
new file mode 100644
index 00000000000..7758fc740de
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/note_base.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ module NoteBase
+ GREEN = '#38ae67'
+
+ def color
+ GREEN
+ end
+
+ def issue
+ resource.noteable
+ end
+
+ def project
+ issue.project
+ end
+
+ def project_link
+ "[#{project.full_name}](#{project.web_url})"
+ end
+
+ def author
+ resource.author
+ end
+
+ def author_profile_link
+ "[#{author.to_reference}](#{url_for(author)})"
+ end
+
+ def fields
+ [
+ {
+ title: 'Comment',
+ value: resource.note
+ }
+ ]
+ end
+
+ private
+
+ attr_reader :resource
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 30c9855507d..5dbdc8b2cdd 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -131,7 +131,8 @@ module Gitlab
omniauth_enabled: Gitlab::Auth.omniauth_enabled?,
prometheus_metrics_enabled: Gitlab::Metrics.prometheus_metrics_enabled?,
reply_by_email_enabled: Gitlab::IncomingEmail.enabled?,
- signup_enabled: Gitlab::CurrentSettings.allow_signup?
+ signup_enabled: Gitlab::CurrentSettings.allow_signup?,
+ web_ide_clientside_preview_enabled: Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
}
end
diff --git a/lib/gitlab/usage_data_counters/web_ide_counter.rb b/lib/gitlab/usage_data_counters/web_ide_counter.rb
index 0718c1dd761..c012a6c96df 100644
--- a/lib/gitlab/usage_data_counters/web_ide_counter.rb
+++ b/lib/gitlab/usage_data_counters/web_ide_counter.rb
@@ -8,6 +8,7 @@ module Gitlab
COMMITS_COUNT_KEY = 'WEB_IDE_COMMITS_COUNT'
MERGE_REQUEST_COUNT_KEY = 'WEB_IDE_MERGE_REQUESTS_COUNT'
VIEWS_COUNT_KEY = 'WEB_IDE_VIEWS_COUNT'
+ PREVIEW_COUNT_KEY = 'WEB_IDE_PREVIEWS_COUNT'
class << self
def increment_commits_count
@@ -34,11 +35,22 @@ module Gitlab
total_count(VIEWS_COUNT_KEY)
end
+ def increment_previews_count
+ return unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
+
+ increment(PREVIEW_COUNT_KEY)
+ end
+
+ def total_previews_count
+ total_count(PREVIEW_COUNT_KEY)
+ end
+
def totals
{
web_ide_commits: total_commits_count,
web_ide_views: total_views_count,
- web_ide_merge_requests: total_merge_requests_count
+ web_ide_merge_requests: total_merge_requests_count,
+ web_ide_previews: total_previews_count
}
end
end
diff --git a/spec/controllers/projects/usage_ping_controller_spec.rb b/spec/controllers/projects/usage_ping_controller_spec.rb
new file mode 100644
index 00000000000..a9abbff160d
--- /dev/null
+++ b/spec/controllers/projects/usage_ping_controller_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::UsagePingController do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ describe 'POST #web_ide_clientside_preview' do
+ subject { post :web_ide_clientside_preview, params: { namespace_id: project.namespace, project_id: project } }
+
+ before do
+ sign_in(user) if user
+ end
+
+ context 'when web ide clientside preview is enabled' do
+ before do
+ stub_application_setting(web_ide_clientside_preview_enabled: true)
+ end
+
+ context 'when the user is not authenticated' do
+ let(:user) { nil }
+
+ it 'returns 302' do
+ subject
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when the user has access to the project' do
+ let(:user) { project.owner }
+
+ it 'increments the counter' do
+ expect do
+ subject
+ end.to change { Gitlab::UsageDataCounters::WebIdeCounter.total_previews_count }.by(1)
+ end
+ end
+ end
+
+ context 'when web ide clientside preview is not enabled' do
+ let(:user) { project.owner }
+
+ before do
+ stub_application_setting(web_ide_clientside_preview_enabled: false)
+ end
+
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/frontend/ide/components/preview/clientside_spec.js b/spec/frontend/ide/components/preview/clientside_spec.js
index a7c74dc992a..6a33f4998c5 100644
--- a/spec/frontend/ide/components/preview/clientside_spec.js
+++ b/spec/frontend/ide/components/preview/clientside_spec.js
@@ -24,6 +24,9 @@ describe('IDE clientside preview', () => {
getFileData: jest.fn().mockReturnValue(Promise.resolve({})),
getRawFileData: jest.fn().mockReturnValue(Promise.resolve('')),
};
+ const storeClientsideActions = {
+ pingUsage: jest.fn().mockReturnValue(Promise.resolve({})),
+ };
const waitForCalls = () => new Promise(setImmediate);
@@ -42,6 +45,12 @@ describe('IDE clientside preview', () => {
...getters,
},
actions: storeActions,
+ modules: {
+ clientside: {
+ namespaced: true,
+ actions: storeClientsideActions,
+ },
+ },
});
wrapper = shallowMount(Clientside, {
@@ -76,7 +85,8 @@ describe('IDE clientside preview', () => {
describe('with main entry', () => {
beforeEach(() => {
createComponent({ getters: { packageJson: dummyPackageJson } });
- return wrapper.vm.initPreview();
+
+ return waitForCalls();
});
it('creates sandpack manager', () => {
@@ -95,6 +105,10 @@ describe('IDE clientside preview', () => {
},
);
});
+
+ it('pings usage', () => {
+ expect(storeClientsideActions.pingUsage).toHaveBeenCalledTimes(1);
+ });
});
describe('computed', () => {
diff --git a/spec/frontend/ide/stores/modules/clientside/actions_spec.js b/spec/frontend/ide/stores/modules/clientside/actions_spec.js
new file mode 100644
index 00000000000..a47bc0bd711
--- /dev/null
+++ b/spec/frontend/ide/stores/modules/clientside/actions_spec.js
@@ -0,0 +1,39 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import { TEST_HOST } from 'helpers/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import * as actions from '~/ide/stores/modules/clientside/actions';
+
+const TEST_PROJECT_URL = `${TEST_HOST}/lorem/ipsum`;
+const TEST_USAGE_URL = `${TEST_PROJECT_URL}/usage_ping/web_ide_clientside_preview`;
+
+describe('IDE store module clientside actions', () => {
+ let rootGetters;
+ let mock;
+
+ beforeEach(() => {
+ rootGetters = {
+ currentProject: {
+ web_url: TEST_PROJECT_URL,
+ },
+ };
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('pingUsage', () => {
+ it('posts to usage endpoint', done => {
+ const usageSpy = jest.fn(() => [200]);
+
+ mock.onPost(TEST_USAGE_URL).reply(() => usageSpy());
+
+ testAction(actions.pingUsage, null, rootGetters, [], [], () => {
+ expect(usageSpy).toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index dc412c80e68..5a8c721a634 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -115,5 +115,10 @@ describe Gitlab::SlashCommands::Command do
let(:params) { { text: 'issue move #78291 to gitlab/gitlab-ci' } }
it { is_expected.to eq(Gitlab::SlashCommands::IssueMove) }
end
+
+ context 'IssueComment is triggered' do
+ let(:params) { { text: "issue comment #503\ncomment body" } }
+ it { is_expected.to eq(Gitlab::SlashCommands::IssueComment) }
+ end
end
end
diff --git a/spec/lib/gitlab/slash_commands/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
new file mode 100644
index 00000000000..c6f56d10d1f
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::IssueComment do
+ describe '#execute' do
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+ let(:user) { issue.author }
+ let(:chat_name) { double(:chat_name, user: user) }
+ let(:regex_match) { described_class.match("issue comment #{issue.iid}\nComment body") }
+
+ subject { described_class.new(project, chat_name).execute(regex_match) }
+
+ context 'when the issue exists' do
+ context 'when project is private' do
+ let(:project) { create(:project) }
+
+ context 'when the user is not a member of the project' do
+ let(:chat_name) { double(:chat_name, user: create(:user)) }
+
+ it 'does not allow the user to comment' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match('not found')
+ expect(issue.reload.notes.count).to be_zero
+ end
+ end
+ end
+
+ context 'when the user is not a member of the project' do
+ let(:chat_name) { double(:chat_name, user: create(:user)) }
+
+ context 'when the discussion is locked in the issue' do
+ before do
+ issue.update!(discussion_locked: true)
+ end
+
+ it 'does not allow the user to comment' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match('You are not allowed')
+ expect(issue.reload.notes.count).to be_zero
+ end
+ end
+ end
+
+ context 'when the user can comment on the issue' do
+ context 'when comment body exists' do
+ it 'creates a new comment' do
+ expect { subject }.to change { issue.notes.count }.by(1)
+ end
+
+ it 'a new comment has a correct body' do
+ subject
+
+ expect(issue.notes.last.note).to eq('Comment body')
+ end
+ end
+
+ context 'when comment body does not exist' do
+ let(:regex_match) { described_class.match("issue comment #{issue.iid}") }
+
+ it 'does not create a new comment' do
+ expect { subject }.not_to change { issue.notes.count }
+ end
+
+ it 'displays the errors' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("- Note can't be blank")
+ end
+ end
+ end
+ end
+
+ context 'when the issue does not exist' do
+ let(:regex_match) { described_class.match("issue comment 2343242\nComment body") }
+
+ it 'returns not found' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match('not found')
+ end
+ end
+ end
+
+ describe '.match' do
+ subject(:match) { described_class.match(command) }
+
+ context 'when a command has an issue ID' do
+ context 'when command has a comment body' do
+ let(:command) { "issue comment 503\nComment body" }
+
+ it 'matches an issue ID' do
+ expect(match[:iid]).to eq('503')
+ end
+
+ it 'matches an note body' do
+ expect(match[:note_body]).to eq('Comment body')
+ end
+ end
+ end
+
+ context 'when a command has a reference prefix for issue ID' do
+ let(:command) { "issue comment #503\nComment body" }
+
+ it 'matches an issue ID' do
+ expect(match[:iid]).to eq('503')
+ end
+ end
+
+ context 'when a command does not have an issue ID' do
+ let(:command) { 'issue comment' }
+
+ it 'does not match' do
+ is_expected.to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index c7b83467660..804184a7173 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -22,6 +22,16 @@ describe Gitlab::SlashCommands::Presenters::Access do
end
end
+ describe '#generic_access_denied' do
+ subject { described_class.new.generic_access_denied }
+
+ it { is_expected.to be_a(Hash) }
+
+ it_behaves_like 'displays an error message' do
+ let(:error_message) { 'You are not allowed to perform the given chatops command.' }
+ end
+ end
+
describe '#deactivated' do
subject { described_class.new.deactivated }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
new file mode 100644
index 00000000000..b5ef417cb93
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::IssueComment do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:note) { create(:note, project: project, noteable: issue) }
+ let(:author) { note.author }
+
+ describe '#present' do
+ let(:attachment) { subject[:attachments].first }
+ subject { described_class.new(note).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'sets ephemeral response type' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+
+ it 'sets the title' do
+ expect(attachment[:title]).to eq("#{issue.title} · #{issue.to_reference}")
+ end
+
+ it 'sets the fallback text' do
+ expect(attachment[:fallback]).to eq("New comment on #{issue.to_reference}: #{issue.title}")
+ end
+
+ it 'sets the fields' do
+ expect(attachment[:fields]).to eq([{ title: 'Comment', value: note.note }])
+ end
+
+ it 'sets the color' do
+ expect(attachment[:color]).to eq('#38ae67')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
index 7a01f7d1de8..96ebeb8ff76 100644
--- a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
@@ -34,22 +34,54 @@ describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_st
it_behaves_like 'counter examples'
end
+ describe 'previews counter' do
+ let(:setting_enabled) { true }
+
+ before do
+ stub_application_setting(web_ide_clientside_preview_enabled: setting_enabled)
+ end
+
+ context 'when web ide clientside preview is enabled' do
+ let(:increment_counter_method) { :increment_previews_count }
+ let(:total_counter_method) { :total_previews_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ context 'when web ide clientside preview is not enabled' do
+ let(:setting_enabled) { false }
+
+ it 'does not increment the counter' do
+ expect(described_class.total_previews_count).to eq(0)
+
+ 2.times { described_class.increment_previews_count }
+
+ expect(described_class.total_previews_count).to eq(0)
+ end
+ end
+ end
+
describe '.totals' do
commits = 5
merge_requests = 3
views = 2
+ previews = 4
before do
+ stub_application_setting(web_ide_clientside_preview_enabled: true)
+
commits.times { described_class.increment_commits_count }
merge_requests.times { described_class.increment_merge_requests_count }
views.times { described_class.increment_views_count }
+ previews.times { described_class.increment_previews_count }
end
it 'can report all totals' do
expect(described_class.totals).to include(
web_ide_commits: commits,
web_ide_views: views,
- web_ide_merge_requests: merge_requests
+ web_ide_merge_requests: merge_requests,
+ web_ide_previews: previews
)
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b0886963a04..0ad196bd050 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -76,6 +76,7 @@ describe Gitlab::UsageData do
avg_cycle_analytics
influxdb_metrics_enabled
prometheus_metrics_enabled
+ web_ide_clientside_preview_enabled
))
end
@@ -93,6 +94,7 @@ describe Gitlab::UsageData do
web_ide_views
web_ide_commits
web_ide_merge_requests
+ web_ide_previews
navbar_searches
cycle_analytics_views
productivity_analytics_views
@@ -252,6 +254,7 @@ describe Gitlab::UsageData do
expect(subject[:container_registry_enabled]).to eq(Gitlab.config.registry.enabled)
expect(subject[:dependency_proxy_enabled]).to eq(Gitlab.config.dependency_proxy.enabled)
expect(subject[:gitlab_shared_runners_enabled]).to eq(Gitlab.config.gitlab_ci.shared_runners_enabled)
+ expect(subject[:web_ide_clientside_preview_enabled]).to eq(Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?)
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 1e06d0fd7b9..c93e6aafd75 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -281,6 +281,44 @@ describe Namespace do
end
end
+ shared_examples 'move_dir without repository storage feature' do |storage_version|
+ let(:namespace) { create(:namespace) }
+ let(:gitlab_shell) { namespace.gitlab_shell }
+ let!(:project) { create(:project_empty_repo, namespace: namespace, storage_version: storage_version) }
+
+ it 'calls namespace service' do
+ expect(gitlab_shell).to receive(:add_namespace).and_return(true)
+ expect(gitlab_shell).to receive(:mv_namespace).and_return(true)
+
+ namespace.move_dir
+ end
+ end
+
+ shared_examples 'move_dir with repository storage feature' do |storage_version|
+ let(:namespace) { create(:namespace) }
+ let(:gitlab_shell) { namespace.gitlab_shell }
+ let!(:project) { create(:project_empty_repo, namespace: namespace, storage_version: storage_version) }
+
+ it 'does not call namespace service' do
+ expect(gitlab_shell).not_to receive(:add_namespace)
+ expect(gitlab_shell).not_to receive(:mv_namespace)
+
+ namespace.move_dir
+ end
+ end
+
+ context 'project is without repository storage feature' do
+ [nil, 0].each do |storage_version|
+ it_behaves_like 'move_dir without repository storage feature', storage_version
+ end
+ end
+
+ context 'project has repository storage feature' do
+ [1, 2].each do |storage_version|
+ it_behaves_like 'move_dir with repository storage feature', storage_version
+ end
+ end
+
context 'with subgroups' do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 28778bf26d4..561c2b572ec 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -788,4 +788,10 @@ describe 'project routing' do
expect(put("/gitlab/gitlabhq/-/deploy_tokens/1/revoke")).to route_to("projects/deploy_tokens#revoke", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
end
+
+ describe Projects::UsagePingController, 'routing' do
+ it 'routes to usage_ping#web_ide_clientside_preview' do
+ expect(post('/gitlab/gitlabhq/usage_ping/web_ide_clientside_preview')).to route_to('projects/usage_ping#web_ide_clientside_preview', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+ end
end