summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-10-31 13:31:33 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-10-31 13:31:33 +0000
commitbf82d23a0a1c2ffe9f3da123775df55a3eb141e5 (patch)
tree27e3662451849824d611175134c4ba30607fe728
parent8f5b6137ab20d6baae86e3a03b8e54ed61cc2146 (diff)
parentc598283a16951ba9c951023adbfb29f56ee73311 (diff)
downloadgitlab-ce-bf82d23a0a1c2ffe9f3da123775df55a3eb141e5.tar.gz
Merge branch 'master' into 38869-labels
* master: Enable eslint Make `#hashed_storage?` require feature argument Adds callback function to inital cluster request Code Style changes and `hashed_storage?` now receives optional feature Fix broken test Remove issue and issue status select from global namespace Fix namespacing for MergeWhenPipelineSucceedsService in MR API Changelog Document existing storable objects and their status regarding Hashed storage Use the Hashed Storage compatible layer to store Attachments Moved renaming operations to storage layer abstraction Add Packagist project service
-rw-r--r--app/assets/javascripts/clusters.js22
-rw-r--r--app/assets/javascripts/header.js14
-rw-r--r--app/assets/javascripts/init_legacy_filters.js4
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js5
-rw-r--r--app/assets/javascripts/issue.js4
-rw-r--r--app/assets/javascripts/issue_status_select.js57
-rw-r--r--app/assets/javascripts/main.js9
-rw-r--r--app/models/project.rb44
-rw-r--r--app/models/project_services/packagist_service.rb65
-rw-r--r--app/models/service.rb1
-rw-r--r--app/services/projects/hashed_storage_migration_service.rb2
-rw-r--r--app/uploaders/file_uploader.rb2
-rw-r--r--changelogs/unreleased/3674-hashed-storage-attachments.yml5
-rw-r--r--changelogs/unreleased/39619-cancel-merge-when-pipeline-succeeds-from-the-api-fails.yml5
-rw-r--r--changelogs/unreleased/39639-clusters-poll.yml5
-rw-r--r--changelogs/unreleased/add-packagist-project-service.yml5
-rw-r--r--doc/administration/repository_storage_types.md25
-rw-r--r--doc/api/services.md34
-rw-r--r--doc/user/project/integrations/project_services.md1
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/services.rb21
-rw-r--r--lib/api/v3/services.rb20
-rw-r--r--spec/features/projects/services/user_activates_packagist_spec.rb24
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb1
-rw-r--r--spec/javascripts/header_spec.js3
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/project_services/packagist_service_spec.rb46
-rw-r--r--spec/models/project_spec.rb68
-rw-r--r--spec/requests/api/merge_requests_spec.rb24
-rw-r--r--spec/services/projects/hashed_storage_migration_service_spec.rb2
-rw-r--r--spec/uploaders/file_uploader_spec.rb52
31 files changed, 475 insertions, 98 deletions
diff --git a/app/assets/javascripts/clusters.js b/app/assets/javascripts/clusters.js
index 180aa30e98c..661870c226c 100644
--- a/app/assets/javascripts/clusters.js
+++ b/app/assets/javascripts/clusters.js
@@ -64,19 +64,16 @@ export default class Clusters {
this.poll = new Poll({
resource: this.service,
method: 'fetchData',
- successCallback: (data) => {
- const { status, status_reason } = data.data;
- this.updateContainer(status, status_reason);
- },
- errorCallback: () => {
- Flash(s__('ClusterIntegration|Something went wrong on our end.'));
- },
+ successCallback: data => this.handleSuccess(data),
+ errorCallback: () => Clusters.handleError(),
});
if (!Visibility.hidden()) {
this.poll.makeRequest();
} else {
- this.service.fetchData();
+ this.service.fetchData()
+ .then(data => this.handleSuccess(data))
+ .catch(() => Clusters.handleError());
}
Visibility.change(() => {
@@ -88,6 +85,15 @@ export default class Clusters {
});
}
+ static handleError() {
+ Flash(s__('ClusterIntegration|Something went wrong on our end.'));
+ }
+
+ handleSuccess(data) {
+ const { status, status_reason } = data.data;
+ this.updateContainer(status, status_reason);
+ }
+
hideAll() {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index ea2e2205077..33a352e158a 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -7,10 +7,12 @@ import { highCountTrim } from '~/lib/utils/text_utility';
* @param {jQuery.Event} e
* @param {String} count
*/
-$(document).on('todo:toggle', (e, count) => {
- const parsedCount = parseInt(count, 10);
- const $todoPendingCount = $('.todos-count');
+export default function initTodoToggle() {
+ $(document).on('todo:toggle', (e, count) => {
+ const parsedCount = parseInt(count, 10);
+ const $todoPendingCount = $('.todos-count');
- $todoPendingCount.text(highCountTrim(parsedCount));
- $todoPendingCount.toggleClass('hidden', parsedCount === 0);
-});
+ $todoPendingCount.text(highCountTrim(parsedCount));
+ $todoPendingCount.toggleClass('hidden', parsedCount === 0);
+ });
+}
diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js
index 56fe7a5b648..1b265721581 100644
--- a/app/assets/javascripts/init_legacy_filters.js
+++ b/app/assets/javascripts/init_legacy_filters.js
@@ -1,15 +1,15 @@
/* eslint-disable no-new */
import LabelsSelect from './labels_select';
/* global MilestoneSelect */
-/* global IssueStatusSelect */
/* global SubscriptionSelect */
import UsersSelect from './users_select';
+import issueStatusSelect from './issue_status_select';
export default () => {
new UsersSelect();
new LabelsSelect();
new MilestoneSelect();
- new IssueStatusSelect();
+ issueStatusSelect();
new SubscriptionSelect();
};
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 14dc1b5e9ec..af6358953cf 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -1,11 +1,10 @@
/* eslint-disable class-methods-use-this, no-new */
/* global MilestoneSelect */
-/* global IssueStatusSelect */
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import './milestone_select';
-import './issue_status_select';
+import issueStatusSelect from './issue_status_select';
import './subscription_select';
import LabelsSelect from './labels_select';
@@ -48,7 +47,7 @@ export default class IssuableBulkUpdateSidebar {
initDropdowns() {
new LabelsSelect();
new MilestoneSelect();
- new IssueStatusSelect();
+ issueStatusSelect();
new SubscriptionSelect();
}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 3fc29f9a661..acd5730cf3c 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -6,7 +6,7 @@ import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper';
-class Issue {
+export default class Issue {
constructor() {
if ($('a.btn-close').length) {
this.taskList = new TaskList({
@@ -147,5 +147,3 @@ class Issue {
});
}
}
-
-export default Issue;
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
index 56cb536dcde..03546f61d1f 100644
--- a/app/assets/javascripts/issue_status_select.js
+++ b/app/assets/javascripts/issue_status_select.js
@@ -1,34 +1,23 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
-(function() {
- this.IssueStatusSelect = (function() {
- function IssueStatusSelect() {
- $('.js-issue-status').each(function(i, el) {
- var fieldName;
- fieldName = $(el).data("field-name");
- return $(el).glDropdown({
- selectable: true,
- fieldName: fieldName,
- toggleLabel: (function(_this) {
- return function(selected, el, instance) {
- var $item, label;
- label = 'Author';
- $item = instance.dropdown.find('.is-active');
- if ($item.length) {
- label = $item.text();
- }
- return label;
- };
- })(this),
- clicked: function(options) {
- return options.e.preventDefault();
- },
- id: function(obj, el) {
- return $(el).data("id");
- }
- });
- });
- }
-
- return IssueStatusSelect;
- })();
-}).call(window);
+export default function issueStatusSelect() {
+ $('.js-issue-status').each((i, el) => {
+ const fieldName = $(el).data('field-name');
+ return $(el).glDropdown({
+ selectable: true,
+ fieldName,
+ toggleLabel(selected, element, instance) {
+ let label = 'Author';
+ const $item = instance.dropdown.find('.is-active');
+ if ($item.length) {
+ label = $item.text();
+ }
+ return label;
+ },
+ clicked(options) {
+ return options.e.preventDefault();
+ },
+ id(obj, element) {
+ return $(element).data('id');
+ },
+ });
+ });
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 659804e2e7c..e1a025e1e4e 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -54,11 +54,8 @@ import './gl_dropdown';
import './gl_field_error';
import './gl_field_errors';
import './gl_form';
-import './header';
+import initTodoToggle from './header';
import initImporterStatus from './importer_status';
-import './issuable_form';
-import './issue';
-import './issue_status_select';
import './layout_nav';
import LazyLoader from './lazy_loader';
import './line_highlighter';
@@ -136,7 +133,11 @@ $(function () {
initBreadcrumbs();
initImporterStatus();
+<<<<<<< HEAD
initLogoAnimation();
+=======
+ initTodoToggle();
+>>>>>>> master
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
diff --git a/app/models/project.rb b/app/models/project.rb
index 7185b4d44fc..a494193c2d7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -26,7 +26,15 @@ class Project < ActiveRecord::Base
NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
- LATEST_STORAGE_VERSION = 1
+ # Hashed Storage versions handle rolling out new storage to project and dependents models:
+ # nil: legacy
+ # 1: repository
+ # 2: attachments
+ LATEST_STORAGE_VERSION = 2
+ HASHED_STORAGE_FEATURES = {
+ repository: 1,
+ attachments: 2
+ }.freeze
cache_markdown_field :description, pipeline: :description
@@ -120,6 +128,7 @@ class Project < ActiveRecord::Base
has_one :mock_deployment_service
has_one :mock_monitoring_service
has_one :microsoft_teams_service
+ has_one :packagist_service
# TODO: replace these relations with the fork network versions
has_one :forked_project_link, foreign_key: "forked_to_project_id"
@@ -1394,6 +1403,19 @@ class Project < ActiveRecord::Base
end
end
+ def after_rename_repo
+ path_before_change = previous_changes['path'].first
+
+ # We need to check if project had been rolled out to move resource to hashed storage or not and decide
+ # if we need execute any take action or no-op.
+
+ unless hashed_storage?(:attachments)
+ Gitlab::UploadsTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
+ end
+
+ Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
+ end
+
def rename_repo_notify!
send_move_instructions(full_path_was)
expires_full_path_cache
@@ -1404,13 +1426,6 @@ class Project < ActiveRecord::Base
reload_repository!
end
- def after_rename_repo
- path_before_change = previous_changes['path'].first
-
- Gitlab::UploadsTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
- Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
- end
-
def running_or_pending_build_count(force: false)
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
builds.running_or_pending.count(:all)
@@ -1600,8 +1615,13 @@ class Project < ActiveRecord::Base
[nil, 0].include?(self.storage_version)
end
- def hashed_storage?
- self.storage_version && self.storage_version >= 1
+ # Check if Hashed Storage is enabled for the project with at least informed feature rolled out
+ #
+ # @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments)
+ def hashed_storage?(feature)
+ raise ArgumentError, "Invalid feature" unless HASHED_STORAGE_FEATURES.include?(feature)
+
+ self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
end
def renamed?
@@ -1637,7 +1657,7 @@ class Project < ActiveRecord::Base
end
def migrate_to_hashed_storage!
- return if hashed_storage?
+ return if hashed_storage?(:repository)
update!(repository_read_only: true)
@@ -1662,7 +1682,7 @@ class Project < ActiveRecord::Base
def storage
@storage ||=
- if hashed_storage?
+ if hashed_storage?(:repository)
Storage::HashedProject.new(self)
else
Storage::LegacyProject.new(self)
diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb
new file mode 100644
index 00000000000..f68a0c1a3c3
--- /dev/null
+++ b/app/models/project_services/packagist_service.rb
@@ -0,0 +1,65 @@
+class PackagistService < Service
+ include HTTParty
+
+ prop_accessor :username, :token, :server
+
+ validates :username, presence: true, if: :activated?
+ validates :token, presence: true, if: :activated?
+
+ default_value_for :push_events, true
+ default_value_for :tag_push_events, true
+
+ after_save :compose_service_hook, if: :activated?
+
+ def title
+ 'Packagist'
+ end
+
+ def description
+ 'Update your project on Packagist, the main Composer repository'
+ end
+
+ def self.to_param
+ 'packagist'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'username', placeholder: '', required: true },
+ { type: 'text', name: 'token', placeholder: '', required: true },
+ { type: 'text', name: 'server', placeholder: 'https://packagist.org', required: false }
+ ]
+ end
+
+ def self.supported_events
+ %w(push merge_request tag_push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ service_hook.execute(data)
+ end
+
+ def test(data)
+ begin
+ result = execute(data)
+ return { success: false, result: result[:message] } if result[:http_status] != 202
+ rescue StandardError => error
+ return { success: false, result: error }
+ end
+
+ { success: true, result: result[:message] }
+ end
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.url = hook_url
+ hook.save
+ end
+
+ def hook_url
+ base_url = server.present? ? server : 'https://packagist.org'
+ "#{base_url}/api/update-package?username=#{username}&apiToken=#{token}"
+ end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index 6b64079215f..fdd2605e3e3 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -238,6 +238,7 @@ class Service < ActiveRecord::Base
kubernetes
mattermost_slash_commands
mattermost
+ packagist
pipelines_email
pivotaltracker
prometheus
diff --git a/app/services/projects/hashed_storage_migration_service.rb b/app/services/projects/hashed_storage_migration_service.rb
index 41259de3a16..f5945f3b87f 100644
--- a/app/services/projects/hashed_storage_migration_service.rb
+++ b/app/services/projects/hashed_storage_migration_service.rb
@@ -10,7 +10,7 @@ module Projects
end
def execute
- return if project.hashed_storage?
+ return if project.hashed_storage?(:repository)
@old_disk_path = project.disk_path
has_wiki = project.wiki.repository_exists?
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 7027ac4b5db..d4ba3a028be 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -30,7 +30,7 @@ class FileUploader < GitlabUploader
#
# Returns a String without a trailing slash
def self.dynamic_path_segment(model)
- File.join(CarrierWave.root, base_dir, model.full_path)
+ File.join(CarrierWave.root, base_dir, model.disk_path)
end
attr_accessor :model
diff --git a/changelogs/unreleased/3674-hashed-storage-attachments.yml b/changelogs/unreleased/3674-hashed-storage-attachments.yml
new file mode 100644
index 00000000000..41bdb5fa568
--- /dev/null
+++ b/changelogs/unreleased/3674-hashed-storage-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Hashed Storage support for Attachments
+merge_request: 15068
+author:
+type: added
diff --git a/changelogs/unreleased/39619-cancel-merge-when-pipeline-succeeds-from-the-api-fails.yml b/changelogs/unreleased/39619-cancel-merge-when-pipeline-succeeds-from-the-api-fails.yml
new file mode 100644
index 00000000000..95251b46ecc
--- /dev/null
+++ b/changelogs/unreleased/39619-cancel-merge-when-pipeline-succeeds-from-the-api-fails.yml
@@ -0,0 +1,5 @@
+---
+title: Fix namespacing for MergeWhenPipelineSucceedsService in MR API
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/39639-clusters-poll.yml b/changelogs/unreleased/39639-clusters-poll.yml
new file mode 100644
index 00000000000..f0a82f58b19
--- /dev/null
+++ b/changelogs/unreleased/39639-clusters-poll.yml
@@ -0,0 +1,5 @@
+---
+title: Adds callback functions for initial request in clusters page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-packagist-project-service.yml b/changelogs/unreleased/add-packagist-project-service.yml
new file mode 100644
index 00000000000..a13d00e91f7
--- /dev/null
+++ b/changelogs/unreleased/add-packagist-project-service.yml
@@ -0,0 +1,5 @@
+---
+title: Add Packagist project service
+merge_request: 14493
+author: Matt Coleman
+type: added
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index fa882bbe28a..0cb2648cc1e 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -25,7 +25,10 @@ Any change in the URL will need to be reflected on disk (when groups / users or
of load in big installations, and can be even worst if they are using any type of network based filesystem.
Last, for GitLab Geo, this storage type means we have to synchronize the disk state, replicate renames in the correct
-order or we may end-up with wrong repository or missing data temporarily.
+order or we may end-up with wrong repository or missing data temporarily.
+
+This pattern also exists in other objects stored in GitLab, like issue Attachments, GitLab Pages artifacts,
+Docker Containers for the integrated Registry, etc.
## Hashed Storage
@@ -67,3 +70,23 @@ To migrate your existing projects to the new storage type, check the specific [r
[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
[rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
[storage-paths]: repository_storage_types.md
+
+### Hashed Storage coverage
+
+We are incrementally moving every storable object in GitLab to the Hashed Storage pattern. You can check the current
+coverage status below.
+
+Not that things stored in S3 compatible endpoint, will not have the downsides mentioned earlier, if they are not
+prefixed with `#{namespace}/#{project_name}`, which is true for CI Cache and LFS Objects.
+
+| Storable Object | Legacy Storage | Hashed Storage | S3 Compatible | GitLab Version |
+| ----------------| -------------- | -------------- | ------------- | -------------- |
+| Repository | Yes | Yes | - | 10.0 |
+| Attachments | Yes | Yes | - | 10.2 |
+| Avatars | Yes | No | - | - |
+| Pages | Yes | No | - | - |
+| Docker Registry | Yes | No | - | - |
+| CI Build Logs | No | No | - | - |
+| CI Artifacts | No | No | - | - |
+| CI Cache | No | No | Yes | - |
+| LFS Objects | Yes | No | Yes (EEP) | - |
diff --git a/doc/api/services.md b/doc/api/services.md
index 6c8f196fd5c..e642ec964de 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -582,6 +582,40 @@ Delete Mattermost slash command service for a project.
DELETE /projects/:id/services/mattermost-slash-commands
```
+## Packagist
+
+Update your project on Packagist, the main Composer repository, when commits or tags are pushed to GitLab.
+
+### Create/Edit Packagist service
+
+Set Packagist service for a project.
+
+```
+PUT /projects/:id/services/packagist
+```
+
+Parameters:
+
+- `username` (**required**)
+- `token` (**required**)
+- `server` (optional)
+
+### Delete Packagist service
+
+Delete Packagist service for a project.
+
+```
+DELETE /projects/:id/services/packagist
+```
+
+### Get Packagist service settings
+
+Get Packagist service settings for a project.
+
+```
+GET /projects/:id/services/packagist
+```
+
## Pipeline-Emails
Get emails for GitLab CI pipelines.
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index 51989ccaaea..a0405161495 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -43,6 +43,7 @@ Click on the service links to see further configuration instructions and details
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
| [Microsoft teams](microsoft_teams.md) | Receive notifications for actions that happen on GitLab into a room on Microsoft Teams using Office 365 Connectors |
+| Packagist | Update your project on Packagist, the main Composer repository |
| Pipelines emails | Email the pipeline status to a list of recipients |
| [Slack Notifications](slack.md) | Send GitLab events (e.g. issue created) to Slack as notifications |
| [Slack slash commands](slack_slash_commands.md) | Use slash commands in Slack to control GitLab |
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index be843ec8251..726f09e3669 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -295,7 +295,7 @@ module API
unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
- ::MergeRequest::MergeWhenPipelineSucceedsService
+ ::MergeRequests::MergeWhenPipelineSucceedsService
.new(merge_request.target_project, current_user)
.cancel(merge_request)
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 1e4f7c29633..6454e475036 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -374,6 +374,26 @@ module API
desc: 'The Slack token'
}
],
+ 'packagist' => [
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'The username'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Packagist API token'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'The server'
+ }
+ ],
'pipelines-email' => [
{
required: true,
@@ -551,6 +571,7 @@ module API
KubernetesService,
MattermostSlashCommandsService,
SlackSlashCommandsService,
+ PackagistService,
PipelinesEmailService,
PivotaltrackerService,
PrometheusService,
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index 2d13d6fabfd..44ed94d2869 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -395,6 +395,26 @@ module API
desc: 'The Slack token'
}
],
+ 'packagist' => [
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'The username'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Packagist API token'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'The server'
+ }
+ ],
'pipelines-email' => [
{
required: true,
diff --git a/spec/features/projects/services/user_activates_packagist_spec.rb b/spec/features/projects/services/user_activates_packagist_spec.rb
new file mode 100644
index 00000000000..b0cc818f093
--- /dev/null
+++ b/spec/features/projects/services/user_activates_packagist_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe 'User activates Packagist' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(project_settings_integrations_path(project))
+
+ click_link('Packagist')
+ end
+
+ it 'activates service' do
+ check('Active')
+ fill_in('Username', with: 'theUser')
+ fill_in('Token', with: 'verySecret')
+ click_button('Save')
+
+ expect(page).to have_content('Packagist activated.')
+ end
+end
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index f86591c2633..5c5e8b66642 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -21,5 +21,6 @@ describe 'User views services' do
expect(page).to have_content('JetBrains TeamCity')
expect(page).to have_content('Asana')
expect(page).to have_content('Irker (IRC gateway)')
+ expect(page).to have_content('Packagist')
end
end
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 4751eb868a4..2443ffd48f3 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -1,4 +1,4 @@
-import '~/header';
+import initTodoToggle from '~/header';
describe('Header', function () {
const todosPendingCount = '.todos-count';
@@ -14,6 +14,7 @@ describe('Header', function () {
preloadFixtures(fixtureTemplate);
beforeEach(() => {
+ initTodoToggle();
loadFixtures(fixtureTemplate);
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 29baa70d5ae..3824bb0757c 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -195,6 +195,7 @@ project:
- mattermost_slash_commands_service
- slack_slash_commands_service
- irker_service
+- packagist_service
- pivotaltracker_service
- prometheus_service
- hipchat_service
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
new file mode 100644
index 00000000000..6acee311700
--- /dev/null
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe PackagistService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ let(:project) { create(:project) }
+
+ let(:packagist_server) { 'https://packagist.example.com' }
+ let(:packagist_username) { 'theUser' }
+ let(:packagist_token) { 'verySecret' }
+ let(:packagist_hook_url) do
+ "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}"
+ end
+
+ let(:packagist_params) do
+ {
+ active: true,
+ project: project,
+ properties: {
+ username: packagist_username,
+ token: packagist_token,
+ server: packagist_server
+ }
+ }
+ end
+
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+ let(:packagist_service) { described_class.create(packagist_params) }
+
+ before do
+ stub_request(:post, packagist_hook_url)
+ end
+
+ it 'calls Packagist API' do
+ packagist_service.execute(push_sample_data)
+
+ expect(a_request(:post, packagist_hook_url)).to have_been_made.once
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 74eba7e33f6..ed6e42d476e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -24,6 +24,7 @@ describe Project do
it { is_expected.to have_one(:slack_service) }
it { is_expected.to have_one(:microsoft_teams_service) }
it { is_expected.to have_one(:mattermost_service) }
+ it { is_expected.to have_one(:packagist_service) }
it { is_expected.to have_one(:pushover_service) }
it { is_expected.to have_one(:asana_service) }
it { is_expected.to have_many(:boards) }
@@ -2452,6 +2453,7 @@ describe Project do
context 'legacy storage' do
let(:project) { create(:project, :repository) }
let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project_storage) { project.send(:storage) }
before do
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
@@ -2493,7 +2495,7 @@ describe Project do
describe '#hashed_storage?' do
it 'returns false' do
- expect(project.hashed_storage?).to be_falsey
+ expect(project.hashed_storage?(:repository)).to be_falsey
end
end
@@ -2546,6 +2548,30 @@ describe Project do
it { expect { subject }.to raise_error(StandardError) }
end
+
+ context 'gitlab pages' do
+ before do
+ expect(project_storage).to receive(:rename_repo) { true }
+ end
+
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
+
+ project.rename_repo
+ end
+ end
+
+ context 'attachments' do
+ before do
+ expect(project_storage).to receive(:rename_repo) { true }
+ end
+
+ it 'moves uploads folder to new location' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
+
+ project.rename_repo
+ end
+ end
end
describe '#pages_path' do
@@ -2605,8 +2631,14 @@ describe Project do
end
describe '#hashed_storage?' do
- it 'returns true' do
- expect(project.hashed_storage?).to be_truthy
+ it 'returns true if rolled out' do
+ expect(project.hashed_storage?(:attachments)).to be_truthy
+ end
+
+ it 'returns false when not rolled out yet' do
+ project.storage_version = 1
+
+ expect(project.hashed_storage?(:attachments)).to be_falsey
end
end
@@ -2649,10 +2681,6 @@ describe Project do
.to receive(:execute_hooks_for)
.with(project, :rename)
- expect_any_instance_of(Gitlab::UploadsTransfer)
- .to receive(:rename_project)
- .with('foo', project.path, project.namespace.full_path)
-
expect(project).to receive(:expire_caches_before_rename)
expect(project).to receive(:expires_full_path_cache)
@@ -2673,6 +2701,32 @@ describe Project do
it { expect { subject }.to raise_error(StandardError) }
end
+
+ context 'gitlab pages' do
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
+
+ project.rename_repo
+ end
+ end
+
+ context 'attachments' do
+ it 'keeps uploads folder location unchanged' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
+
+ project.rename_repo
+ end
+
+ context 'when not rolled out' do
+ let(:project) { create(:project, :repository, storage_version: 1) }
+
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
+
+ project.rename_repo
+ end
+ end
+ end
end
describe '#pages_path' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 28b1404a4f7..024cfe8b372 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1061,6 +1061,30 @@ describe API::MergeRequests do
end
end
+ describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
+ before do
+ ::MergeRequests::MergeWhenPipelineSucceedsService.new(merge_request.target_project, user).execute(merge_request)
+ end
+
+ it 'removes the merge_when_pipeline_succeeds status' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/cancel_merge_when_pipeline_succeeds", user)
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'returns 404 if the merge request is not found' do
+ post api("/projects/#{project.id}/merge_requests/123/merge_when_pipeline_succeeds", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 if the merge request id is used instead of iid' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge_when_pipeline_succeeds", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
describe 'Time tracking' do
let(:issuable) { merge_request }
diff --git a/spec/services/projects/hashed_storage_migration_service_spec.rb b/spec/services/projects/hashed_storage_migration_service_spec.rb
index aa1988d29d6..b71b47c59b6 100644
--- a/spec/services/projects/hashed_storage_migration_service_spec.rb
+++ b/spec/services/projects/hashed_storage_migration_service_spec.rb
@@ -23,7 +23,7 @@ describe Projects::HashedStorageMigrationService do
it 'updates project to be hashed and not read-only' do
service.execute
- expect(project.hashed_storage?).to be_truthy
+ expect(project.hashed_storage?(:repository)).to be_truthy
expect(project.repository_read_only).to be_falsey
end
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 2492d56a5cf..f52b2bab05b 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -3,25 +3,51 @@ require 'spec_helper'
describe FileUploader do
let(:uploader) { described_class.new(build_stubbed(:project)) }
- describe '.absolute_path' do
- it 'returns the correct absolute path by building it dynamically' do
- project = build_stubbed(:project)
- upload = double(model: project, path: 'secret/foo.jpg')
+ context 'legacy storage' do
+ let(:project) { build_stubbed(:project) }
- dynamic_segment = project.path_with_namespace
+ describe '.absolute_path' do
+ it 'returns the correct absolute path by building it dynamically' do
+ upload = double(model: project, path: 'secret/foo.jpg')
- expect(described_class.absolute_path(upload))
- .to end_with("#{dynamic_segment}/secret/foo.jpg")
+ dynamic_segment = project.full_path
+
+ expect(described_class.absolute_path(upload))
+ .to end_with("#{dynamic_segment}/secret/foo.jpg")
+ end
+ end
+
+ describe "#store_dir" do
+ it "stores in the namespace path" do
+ uploader = described_class.new(project)
+
+ expect(uploader.store_dir).to include(project.full_path)
+ expect(uploader.store_dir).not_to include("system")
+ end
end
end
- describe "#store_dir" do
- it "stores in the namespace path" do
- project = build_stubbed(:project)
- uploader = described_class.new(project)
+ context 'hashed storage' do
+ let(:project) { build_stubbed(:project, :hashed) }
+
+ describe '.absolute_path' do
+ it 'returns the correct absolute path by building it dynamically' do
+ upload = double(model: project, path: 'secret/foo.jpg')
+
+ dynamic_segment = project.disk_path
+
+ expect(described_class.absolute_path(upload))
+ .to end_with("#{dynamic_segment}/secret/foo.jpg")
+ end
+ end
+
+ describe "#store_dir" do
+ it "stores in the namespace path" do
+ uploader = described_class.new(project)
- expect(uploader.store_dir).to include(project.path_with_namespace)
- expect(uploader.store_dir).not_to include("system")
+ expect(uploader.store_dir).to include(project.disk_path)
+ expect(uploader.store_dir).not_to include("system")
+ end
end
end