summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.rubocop.yml4
-rw-r--r--.rubocop_todo.yml6
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile.lock16
-rw-r--r--PROCESS.md84
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js8
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/button.vue11
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue31
-rw-r--r--app/assets/javascripts/ide/ide_router.js104
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js68
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js32
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/actions.js3
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue63
-rw-r--r--app/assets/javascripts/labels_select.js9
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js94
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue4
-rw-r--r--app/assets/javascripts/reports/components/report_issues.vue4
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss1
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/buttons.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss4
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss20
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss16
-rw-r--r--app/assets/stylesheets/pages/editor.scss25
-rw-r--r--app/assets/stylesheets/pages/groups.scss3
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/assets/stylesheets/pages/settings.scss14
-rw-r--r--app/helpers/mirror_helper.rb5
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/programming_language.rb2
-rw-r--r--app/models/project.rb22
-rw-r--r--app/models/project_services/asana_service.rb2
-rw-r--r--app/models/project_services/assembla_service.rb2
-rw-r--r--app/models/project_services/bamboo_service.rb2
-rw-r--r--app/models/project_services/bugzilla_service.rb2
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/builds_email_service.rb2
-rw-r--r--app/models/project_services/campfire_service.rb6
-rw-r--r--app/models/project_services/chat_message/base_message.rb2
-rw-r--r--app/models/project_services/chat_message/issue_message.rb2
-rw-r--r--app/models/project_services/chat_message/merge_message.rb2
-rw-r--r--app/models/project_services/chat_message/note_message.rb2
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb2
-rw-r--r--app/models/project_services/chat_message/push_message.rb2
-rw-r--r--app/models/project_services/chat_message/wiki_page_message.rb2
-rw-r--r--app/models/project_services/chat_notification_service.rb2
-rw-r--r--app/models/project_services/ci_service.rb2
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/deployment_service.rb2
-rw-r--r--app/models/project_services/drone_ci_service.rb2
-rw-r--r--app/models/project_services/emails_on_push_service.rb2
-rw-r--r--app/models/project_services/external_wiki_service.rb2
-rw-r--r--app/models/project_services/flowdock_service.rb2
-rw-r--r--app/models/project_services/gemnasium_service.rb2
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/hangouts_chat_service.rb2
-rw-r--r--app/models/project_services/hipchat_service.rb22
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/kubernetes_service.rb2
-rw-r--r--app/models/project_services/mattermost_service.rb2
-rw-r--r--app/models/project_services/mattermost_slash_commands_service.rb2
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/mock_ci_service.rb2
-rw-r--r--app/models/project_services/mock_deployment_service.rb2
-rw-r--r--app/models/project_services/mock_monitoring_service.rb2
-rw-r--r--app/models/project_services/monitoring_service.rb2
-rw-r--r--app/models/project_services/packagist_service.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb2
-rw-r--r--app/models/project_services/pivotaltracker_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb2
-rw-r--r--app/models/project_services/pushover_service.rb4
-rw-r--r--app/models/project_services/redmine_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb2
-rw-r--r--app/models/project_services/slack_slash_commands_service.rb2
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb2
-rw-r--r--app/models/protected_branch/merge_access_level.rb2
-rw-r--r--app/models/protected_branch/push_access_level.rb2
-rw-r--r--app/models/protected_tag/create_access_level.rb2
-rw-r--r--app/models/repository_language.rb2
-rw-r--r--app/models/site_statistic.rb2
-rw-r--r--app/models/storage/hashed_project.rb2
-rw-r--r--app/models/storage/legacy_project.rb2
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/serializers/environment_entity.rb11
-rw-r--r--app/serializers/project_mirror_serializer.rb3
-rw-r--r--app/services/ci/enqueue_build_service.rb8
-rw-r--r--app/services/ci/process_pipeline_service.rb6
-rw-r--r--app/views/admin/users/show.html.haml12
-rw-r--r--app/views/projects/blob/_editor.html.haml8
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml15
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml63
-rw-r--r--app/views/projects/mirrors/_mirror_repos_form.html.haml18
-rw-r--r--app/views/projects/mirrors/_push.html.haml50
-rw-r--r--app/views/projects/mirrors/_show.html.haml4
-rw-r--r--app/views/projects/pages/_use.html.haml4
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml19
-rw-r--r--app/views/shared/milestones/_issuables.html.haml4
-rw-r--r--app/views/shared/plugins/_index.html.haml4
-rw-r--r--changelogs/unreleased/2747-protected-environments-backend-ce.yml5
-rw-r--r--changelogs/unreleased/28930-add-project-reference-filter.yml5
-rw-r--r--changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml5
-rw-r--r--changelogs/unreleased/48320-cancel-a-created-job.yml5
-rw-r--r--changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml5
-rw-r--r--changelogs/unreleased/50101-stuck-component.yml5
-rw-r--r--changelogs/unreleased/50126-blocked-user-card.yml5
-rw-r--r--changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml5
-rw-r--r--changelogs/unreleased/ide-header-buttons-tooltip.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml5
-rw-r--r--db/fixtures/development/23_spam_logs.rb32
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/development/contributing/design.md19
-rw-r--r--doc/development/new_fe_guide/development/testing.md24
-rw-r--r--doc/user/markdown.md1
-rw-r--r--doc/user/project/integrations/webhooks.md1
-rw-r--r--lib/banzai/filter/project_reference_filter.rb117
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/reference_parser/project_parser.rb30
-rw-r--r--lib/gitlab/import_export/relation_factory.rb6
-rw-r--r--locale/gitlab.pot75
-rw-r--r--spec/features/projects/blobs/shortcuts_blob_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/features/projects/remote_mirror_spec.rb10
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb5
-rw-r--r--spec/javascripts/ide/components/new_dropdown/button_spec.js16
-rw-r--r--spec/javascripts/ide/components/preview/clientside_spec.js6
-rw-r--r--spec/javascripts/ide/stores/actions/merge_request_spec.js101
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js52
-rw-r--r--spec/javascripts/ide/stores/modules/branches/actions_spec.js16
-rw-r--r--spec/javascripts/jobs/stuck_block_spec.js81
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb85
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb85
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb50
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--spec/models/project_spec.rb9
-rw-r--r--spec/serializers/project_mirror_serializer_spec.rb7
-rw-r--r--spec/services/ci/enqueue_build_service_spec.rb16
-rw-r--r--spec/support/banzai/reference_filter_shared_examples.rb73
-rw-r--r--spec/support/import_export/configuration_helper.rb2
151 files changed, 1600 insertions, 439 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fcf47421b01..fd02d72b4c2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -453,6 +453,7 @@ danger-review:
- master
variables:
- $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
+ - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
script:
- git version
- danger --fail-on-errors=true
diff --git a/.rubocop.yml b/.rubocop.yml
index c8b1ce327e2..a586190319b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -31,6 +31,10 @@ Style/MutableConstant:
- 'ee/db/post_migrate/**/*'
- 'ee/db/geo/migrate/**/*'
+# TODO: Move this to gitlab-styles
+Style/SafeNavigation:
+ Enabled: false
+
Naming/FileName:
ExpectMatchingDefinition: true
Exclude:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 8a1ca6747a8..54e3b8217d8 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -706,12 +706,6 @@ Style/RescueModifier:
Style/RescueStandardError:
Enabled: false
-# Offense count: 92
-# Cop supports --auto-correct.
-# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
-Style/SafeNavigation:
- Enabled: false
-
# Offense count: 8
# Cop supports --auto-correct.
Style/SelfAssignment:
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 8104cabd36f..0e79152459e 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.1.0
+8.1.1
diff --git a/Gemfile.lock b/Gemfile.lock
index 1537cacaadd..ee8eed07ed7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -123,7 +123,7 @@ GEM
numerizer (~> 0.1.1)
chunky_png (1.3.5)
citrus (3.0.2)
- coderay (1.1.1)
+ coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
commonmarker (0.17.8)
@@ -494,7 +494,7 @@ GEM
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
- method_source (0.8.2)
+ method_source (0.9.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
@@ -636,12 +636,11 @@ GEM
unparser
procto (0.0.3)
prometheus-client-mmap (0.9.4)
- pry (0.10.4)
+ pry (0.11.3)
coderay (~> 1.1.0)
- method_source (~> 0.8.1)
- slop (~> 3.4)
- pry-byebug (3.4.2)
- byebug (~> 9.0)
+ method_source (~> 0.9.0)
+ pry-byebug (3.4.3)
+ byebug (>= 9.0, < 9.1)
pry (~> 0.10)
pry-rails (0.3.5)
pry (>= 0.9.10)
@@ -872,7 +871,6 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slack-notifier (1.5.1)
- slop (3.6.0)
spring (2.0.1)
activesupport (>= 4.2)
spring-commands-rspec (1.0.4)
@@ -1207,4 +1205,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.2
+ 1.16.3
diff --git a/PROCESS.md b/PROCESS.md
index a06ddb68b77..5f50d472bd7 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -15,8 +15,9 @@
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
- [On the 7th](#on-the-7th)
- [After the 7th](#after-the-7th)
-- [Regressions](#regressions)
- - [How to manage a regression](#how-to-manage-a-regression)
+- [Bugs](#bugs)
+ - [Regressions](#regressions)
+ - [Managing bugs](#managing-bugs)
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- [Retrospective](#retrospective)
- [Kickoff](#kickoff)
@@ -168,7 +169,7 @@ information, see
Once the stable branch is frozen, the only MRs that can be cherry-picked into
the stable branch are:
-* Fixes for [regressions](#regressions)
+* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section.
* Fixes for security issues
* Fixes or improvements to automated QA scenarios
* Documentation updates for changes in the same release
@@ -201,48 +202,59 @@ you can ask for an exception to be made.
Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one.
-## Regressions
+## Bugs
-A regression for a particular monthly release is a bug that exists in that
-release, but wasn't present in the release before. This includes bugs in
-features that were only added in that monthly release. Every regression **must**
-have the milestone of the release it was introduced in - if a regression doesn't
-have a milestone, it might be 'just' a bug!
+A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling the product requirements.
-For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
-then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
-reintroduces the bug, then this bug is still a regression in 10.5.
+The level of impact of a ~bug can vary from blocking a whole functionality
+or a feature usability bug. A bug should always be linked to a severity level.
+Refer to our [severity levels](../CONTRIBUTING.md#severity-labels)
-Because GitLab.com runs release candidates of new releases, a regression can be
-reported in a release before its 'official' release date on the 22nd of the
-month. When we say 'the most recent monthly release', this can refer to either
-the version currently running on GitLab.com, or the most recent version
-available in the package repositories.
+Whether the bug is also a regression or not, the triage process should start as soon as possible.
+Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed.
-### How to manage a regression
+### Regressions
-Regressions are very important, and they should be considered high priority
-issues that should be solved as soon as possible, especially if they affect
-users. Despite that, ~regression label itself does not imply when the issue
-will be scheduled.
+A ~regression implies that a previously **verified working functionality** no longer works.
+Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress.
+The label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule.
-When a regression is found:
-1. Create an issue describing the problem in the most detailed way possible
-1. If possible, provide links to real examples and how to reproduce the problem
+The regression label does not apply to ~bugs for new features for which functionality was **never verified as working**.
+These, by definition, are not regressions.
+
+A regression should always have the `regression:xx.x` label on it to designate when it was introduced.
+
+Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users.
+
+### Managing bugs
+
+**Prioritization:** We give higher priority to regressions on features that worked in the last recent monthly release and the current release candidates.
+The two scenarios below can [bypass the exception request in the release process](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md#after-the-7th), where the affected regression version matches the current monthly release version.
+* A regression which worked in the **Last monthly release**
+ * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label.
+ * *Note:* When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories.
+* A regression which worked in the **Current release candidates**
+ * **Example:** In 11.1-RC3 we shipped a new feature which has been verified as working. Then in 11.1-RC5 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label.
+ * *Note:* Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month.
+
+When a bug is found:
+1. Create an issue describing the problem in the most detailed way possible.
+1. If possible, provide links to real examples and how to reproduce the problem.
1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels),
the [subject label](../CONTRIBUTING.md#subject-labels)
and any other label that may apply in the specific case
-1. Add the ~bug and ~regression labels
-1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels).
-1. If the regression is either an ~S1, ~S2 or ~S3 severity, label the regression with the current milestone as it should be fixed in the current milestone.
- 1. If the regression was introduced in an RC of the current release, label with ~Deliverable
- 1. If the regression was introduced in the previous release, label with ~"Next Patch Release"
-1. If the regression is an ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager.
-
-When a new issue is found, the fix should start as soon as possible. You can
-ping the Engineering Manager or the Product Manager for the relative area to
-make them aware of the issue earlier. They will analyze the priority and change
-it if needed.
+1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels).
+The counterpart Product Manager is included to weigh-in on prioritization as needed.
+1. If the ~bug is **NOT** a regression:
+ 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied.
+1. If the bug is a ~regression:
+ 1. Determine the release that the regression affects and add the corresponding `regression:xx.x` label.
+ 1. If the affected release version can't be determined, add the generic ~regression label for the time being.
+ 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, it's recommended to schedule the fix for the current milestone.
+ 1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above.
+ 1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release**
+ 1. If the regression is an ~S1 severity, it's recommended to schedule the fix for the current milestone. We would like to fix the highest severity regression as soon as we can.
+ 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of the Engineering Manager and Product Manager.
## Release retrospective and kickoff
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index a9102743bf9..109e60cbde2 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -3,7 +3,7 @@
import $ from 'jquery';
import Vue from 'vue';
import Flash from '../../flash';
-import { __ } from '../../locale';
+import { sprintf, __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
@@ -55,8 +55,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
return this.issue.labels && this.issue.labels.length;
},
labelDropdownTitle() {
- return this.hasLabels ?
- `${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
+ return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
+ firstLabel: this.issue.labels[0].title,
+ labelCount: this.issue.labels.length - 1
+ }) : __('Label');
},
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
diff --git a/app/assets/javascripts/ide/components/new_dropdown/button.vue b/app/assets/javascripts/ide/components/new_dropdown/button.vue
index ff114e47741..aa5fce59dbf 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/button.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/button.vue
@@ -1,7 +1,11 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
export default {
+ directives: {
+ tooltip,
+ },
components: {
Icon,
},
@@ -26,6 +30,11 @@ export default {
default: true,
},
},
+ computed: {
+ tooltipTitle() {
+ return this.showLabel ? '' : this.label;
+ },
+ },
methods: {
clicked() {
this.$emit('click');
@@ -36,7 +45,9 @@ export default {
<template>
<button
+ v-tooltip
:aria-label="label"
+ :title="tooltipTitle"
type="button"
class="btn-blank"
@click.stop.prevent="clicked"
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index fef36eae7b1..39a1bd1f61b 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -2,6 +2,7 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { Manager } from 'smooshpack';
+import { listen } from 'codesandbox-api';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Navigator from './navigator.vue';
import { packageJsonPath } from '../../constants';
@@ -16,6 +17,7 @@ export default {
return {
manager: {},
loading: false,
+ sandpackReady: false,
};
},
computed: {
@@ -81,6 +83,10 @@ export default {
}
this.manager = {};
+ if (this.listener) {
+ this.listener();
+ }
+
clearTimeout(this.timeout);
this.timeout = null;
},
@@ -96,17 +102,29 @@ export default {
return this.loadFileContent(this.mainEntry)
.then(() => this.$nextTick())
- .then(() =>
+ .then(() => {
this.initManager('#ide-preview', this.sandboxOpts, {
fileResolver: {
isFile: p => Promise.resolve(!!this.entries[createPathWithExt(p)]),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
- }),
- );
+ });
+
+ this.listener = listen(e => {
+ switch (e.type) {
+ case 'done':
+ this.sandpackReady = true;
+ break;
+ default:
+ break;
+ }
+ });
+ });
},
update() {
- if (this.timeout) return;
+ if (!this.sandpackReady) return;
+
+ clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (_.isEmpty(this.manager)) {
@@ -116,10 +134,7 @@ export default {
}
this.manager.updatePreview(this.sandboxOpts);
-
- clearTimeout(this.timeout);
- this.timeout = null;
- }, 500);
+ }, 250);
},
initManager(el, opts, resolver) {
this.manager = new Manager(el, opts, resolver);
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index 82f6f981e7a..3f6101e58f4 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
import { join as joinPath } from 'path';
import flash from '~/flash';
import store from './stores';
-import { activityBarViews } from './constants';
Vue.use(VueRouter);
@@ -74,102 +73,23 @@ router.beforeEach((to, from, next) => {
projectId: to.params.project,
})
.then(() => {
- const fullProjectId = `${to.params.namespace}/${to.params.project}`;
-
+ const basePath = to.params[0] || '';
+ const projectId = `${to.params.namespace}/${to.params.project}`;
const branchId = to.params.branchid;
+ const mergeRequestId = to.params.mrid;
if (branchId) {
- const basePath = to.params[0] || '';
-
- store.dispatch('setCurrentBranchId', branchId);
-
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
+ store.dispatch('openBranch', {
+ projectId,
branchId,
+ basePath,
+ });
+ } else if (mergeRequestId) {
+ store.dispatch('openMergeRequest', {
+ projectId,
+ mergeRequestId,
+ targetProjectId: to.query.target_project,
});
-
- store
- .dispatch('getFiles', {
- projectId: fullProjectId,
- branchId,
- })
- .then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(store.state.entries).find(
- key => key === path && !store.state.entries[key].pending,
- );
- const treeEntry = store.state.entries[treeEntryKey];
-
- if (treeEntry) {
- store.dispatch('handleTreeEntryAction', treeEntry);
- }
- }
- })
- .catch(e => {
- throw e;
- });
- } else if (to.params.mrid) {
- store
- .dispatch('getMergeRequestData', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- })
- .then(mr => {
- store.dispatch('setCurrentBranchId', mr.source_branch);
-
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
- branchId: mr.source_branch,
- });
-
- return store.dispatch('getFiles', {
- projectId: fullProjectId,
- branchId: mr.source_branch,
- });
- })
- .then(() =>
- store.dispatch('getMergeRequestVersions', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- }),
- )
- .then(() =>
- store.dispatch('getMergeRequestChanges', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- }),
- )
- .then(mrChanges => {
- if (mrChanges.changes.length) {
- store.dispatch('updateActivityBarView', activityBarViews.review);
- }
-
- mrChanges.changes.forEach((change, ind) => {
- const changeTreeEntry = store.state.entries[change.new_path];
-
- if (changeTreeEntry) {
- store.dispatch('setFileMrChange', {
- file: changeTreeEntry,
- mrChange: change,
- });
-
- if (ind < 10) {
- store.dispatch('getFileData', {
- path: change.new_path,
- makeFileActive: ind === 0,
- });
- }
- }
- });
- })
- .catch(e => {
- flash('Error while loading the merge request. Please try again.');
- throw e;
- });
}
})
.catch(e => {
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 1887b77b00b..187f8c75d07 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -1,6 +1,8 @@
-import { __ } from '../../../locale';
+import flash from '~/flash';
+import { __ } from '~/locale';
import service from '../../services';
import * as types from '../mutation_types';
+import { activityBarViews } from '../../constants';
export const getMergeRequestData = (
{ commit, dispatch, state },
@@ -104,3 +106,67 @@ export const getMergeRequestVersions = (
resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
}
});
+
+export const openMergeRequest = (
+ { dispatch, state },
+ { projectId, targetProjectId, mergeRequestId } = {},
+) =>
+ dispatch('getMergeRequestData', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ })
+ .then(mr => {
+ dispatch('setCurrentBranchId', mr.source_branch);
+
+ dispatch('getBranchData', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+
+ return dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+ })
+ .then(() =>
+ dispatch('getMergeRequestVersions', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(() =>
+ dispatch('getMergeRequestChanges', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(mrChanges => {
+ if (mrChanges.changes.length) {
+ dispatch('updateActivityBarView', activityBarViews.review);
+ }
+
+ mrChanges.changes.forEach((change, ind) => {
+ const changeTreeEntry = state.entries[change.new_path];
+
+ if (changeTreeEntry) {
+ dispatch('setFileMrChange', {
+ file: changeTreeEntry,
+ mrChange: change,
+ });
+
+ if (ind < 10) {
+ dispatch('getFileData', {
+ path: change.new_path,
+ makeFileActive: ind === 0,
+ });
+ }
+ }
+ });
+ })
+ .catch(e => {
+ flash(__('Error while loading the merge request. Please try again.'));
+ throw e;
+ });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 501e25d452b..543dc6c0461 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -124,3 +124,35 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
actionPayload: branchId,
});
};
+
+export const openBranch = (
+ { dispatch, state },
+ { projectId, branchId, basePath },
+) => {
+ dispatch('setCurrentBranchId', branchId);
+
+ dispatch('getBranchData', {
+ projectId,
+ branchId,
+ });
+
+ return (
+ dispatch('getFiles', {
+ projectId,
+ branchId,
+ })
+ .then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
+
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
+ }
+ }
+ })
+ );
+};
diff --git a/app/assets/javascripts/ide/stores/modules/branches/actions.js b/app/assets/javascripts/ide/stores/modules/branches/actions.js
index 74aa98ef9f9..f90c2d77f2b 100644
--- a/app/assets/javascripts/ide/stores/modules/branches/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/branches/actions.js
@@ -33,7 +33,4 @@ export const fetchBranches = ({ dispatch, rootGetters }, { search = '' }) => {
export const resetBranches = ({ commit }) => commit(types.RESET_BRANCHES);
-export const openBranch = ({ rootState, dispatch }, id) =>
- dispatch('goToRoute', `/project/${rootState.currentProjectId}/edit/${id}`, { root: true });
-
export default () => {};
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
new file mode 100644
index 00000000000..18883fea950
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -0,0 +1,63 @@
+<script>
+/**
+ * Renders Stuck Runners block for job's view.
+ */
+export default {
+ props: {
+ hasNoRunnersForProject: {
+ type: Boolean,
+ required: true,
+ },
+ tags: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="bs-callout bs-callout-warning">
+ <p
+ v-if="hasNoRunnersForProject"
+ class="js-stuck-no-runners"
+ >
+ {{ s__(`Job|This job is stuck, because the project
+ doesn't have any runners online assigned to it.`) }}
+ </p>
+ <p
+ v-else-if="tags.length"
+ class="js-stuck-with-tags"
+ >
+ {{ s__(`This job is stuck, because you don't have
+ any active runners online with any of these tags assigned to them:`) }}
+ <span
+ v-for="(tag, index) in tags"
+ :key="index"
+ class="badge badge-primary"
+ >
+ {{ tag }}
+ </span>
+ </p>
+ <p
+ v-else
+ class="js-stuck-no-active-runner"
+ >
+ {{ s__(`This job is stuck, because you don't
+ have any active runners that can run this job.`) }}
+ </p>
+
+ {{ __("Go to") }}
+ <a
+ v-if="runnersPath"
+ :href="runnersPath"
+ class="js-runners-path"
+ >
+ {{ __("Runners page") }}
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index cb851ff6745..6499b919787 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,7 +4,7 @@
import $ from 'jquery';
import _ from 'underscore';
-import { __ } from './locale';
+import { sprintf, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
@@ -39,7 +39,7 @@ export default class LabelsSelect {
showNo = $dropdown.data('showNo');
showAny = $dropdown.data('showAny');
showMenuAbove = $dropdown.data('showMenuAbove');
- defaultLabel = $dropdown.data('defaultLabel') || 'Label';
+ defaultLabel = $dropdown.data('defaultLabel') || __('Label');
abilityName = $dropdown.data('abilityName');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
@@ -267,7 +267,10 @@ export default class LabelsSelect {
return selectedLabels;
}
else if (selectedLabels.length) {
- return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more";
+ return sprintf(__('%{firstLabel} +%{labelCount} more'), {
+ firstLabel: selectedLabels[0],
+ labelCount: selectedLabels.length - 1
+ });
}
else {
return defaultLabel;
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index ffc84dc106b..78cf5406e43 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,3 +1,9 @@
import initForm from '../form';
+import MirrorRepos from './mirror_repos';
-document.addEventListener('DOMContentLoaded', initForm);
+document.addEventListener('DOMContentLoaded', () => {
+ initForm();
+
+ const mirrorReposContainer = document.querySelector('.js-mirror-settings');
+ if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js
new file mode 100644
index 00000000000..4c56af20cc3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js
@@ -0,0 +1,94 @@
+import $ from 'jquery';
+import _ from 'underscore';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+
+export default class MirrorRepos {
+ constructor(container) {
+ this.$container = $(container);
+ this.$form = $('.js-mirror-form', this.$container);
+ this.$urlInput = $('.js-mirror-url', this.$form);
+ this.$protectedBranchesInput = $('.js-mirror-protected', this.$form);
+ this.$table = $('.js-mirrors-table-body', this.$container);
+ this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint');
+ }
+
+ init() {
+ this.initMirrorPush();
+ this.registerUpdateListeners();
+ }
+
+ initMirrorPush() {
+ this.$passwordGroup = $('.js-password-group', this.$container);
+ this.$password = $('.js-password', this.$passwordGroup);
+ this.$authMethod = $('.js-auth-method', this.$form);
+
+ this.$authMethod.on('change', () => this.togglePassword());
+ this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl());
+ }
+
+ updateUrl() {
+ let val = this.$urlInput.val();
+
+ if (this.$password) {
+ const password = this.$password.val();
+ if (password) val = val.replace('@', `:${password}@`);
+ }
+
+ $('.js-mirror-url-hidden', this.$form).val(val);
+ }
+
+ updateProtectedBranches() {
+ const val = this.$protectedBranchesInput.get(0).checked
+ ? this.$protectedBranchesInput.val()
+ : '0';
+ $('.js-mirror-protected-hidden', this.$form).val(val);
+ }
+
+ registerUpdateListeners() {
+ this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200);
+ this.$urlInput.on('input', () => this.debouncedUpdateUrl());
+ this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches());
+ this.$table.on('click', '.js-delete-mirror', event => this.deleteMirror(event));
+ }
+
+ togglePassword() {
+ const isPassword = this.$authMethod.val() === 'password';
+
+ if (!isPassword) {
+ this.$password.val('');
+ this.updateUrl();
+ }
+ this.$passwordGroup.collapse(isPassword ? 'show' : 'hide');
+ }
+
+ deleteMirror(event, existingPayload) {
+ const $target = $(event.currentTarget);
+ let payload = existingPayload;
+
+ if (!payload) {
+ payload = {
+ project: {
+ remote_mirrors_attributes: {
+ id: $target.data('mirrorId'),
+ enabled: 0,
+ },
+ },
+ };
+ }
+
+ return axios
+ .put(this.mirrorEndpoint, payload)
+ .then(() => this.removeRow($target))
+ .catch(() => Flash(__('Failed to remove mirror.')));
+ }
+
+ /* eslint-disable class-methods-use-this */
+ removeRow($target) {
+ const row = $target.closest('tr');
+ $('.js-delete-mirror', row).tooltip('hide');
+ row.remove();
+ }
+ /* eslint-enable class-methods-use-this */
+}
diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index dbb8848d1fa..df42201b5de 100644
--- a/app/assets/javascripts/reports/components/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -1,10 +1,10 @@
<script>
-import IssuesBlock from './report_issues.vue';
+import IssuesBlock from '~/reports/components/report_issues.vue';
import {
STATUS_SUCCESS,
STATUS_FAILED,
STATUS_NEUTRAL,
-} from '../constants';
+} from '~/reports/constants';
/**
* Renders block of issues
diff --git a/app/assets/javascripts/reports/components/report_issues.vue b/app/assets/javascripts/reports/components/report_issues.vue
index 884f55c8dec..c553a374f66 100644
--- a/app/assets/javascripts/reports/components/report_issues.vue
+++ b/app/assets/javascripts/reports/components/report_issues.vue
@@ -1,6 +1,6 @@
<script>
-import IssueStatusIcon from './issue_status_icon.vue';
-import { components, componentNames } from './issue_body';
+import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
+import { components, componentNames } from '~/reports/components/issue_body';
export default {
name: 'ReportIssues',
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c20738a20c3..056d4b7207a 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -14,7 +14,6 @@ $border-radius-base: 3px !default;
$modal-body-bg: $white-light;
$input-border: $border-color;
-$input-border-focus: $focus-border-color;
$padding-base-vertical: $gl-vert-padding;
$padding-base-horizontal: $gl-padding;
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c46b0b5db09..b1a20c06910 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,4 +1,5 @@
@import 'framework/variables';
+@import 'framework/variables_overrides';
@import 'framework/mixins';
@import 'bootstrap';
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 646cedd79ed..ea4798fcefd 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -350,7 +350,7 @@
&:focus {
cursor: text;
box-shadow: none;
- border-color: lighten($dropdown-input-focus-border, 20%);
+ border-color: lighten($blue-300, 20%);
color: $gray-darkest;
background-color: $gray-light;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index eebce8b9011..83bc3776178 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -615,7 +615,7 @@
&:focus {
color: $dropdown-link-color;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
~ .fa {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5d79610b21e..9b09ed0ed0a 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -205,7 +205,7 @@
&.focus,
&.focus:hover {
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: 0 0 4px $search-input-focus-shadow-color;
}
@@ -294,7 +294,7 @@
&:hover,
&:focus {
color: $gl-text-color;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
outline: none;
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index d7149d93622..437fcff5c62 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -201,7 +201,7 @@ label {
}
.gl-show-field-errors {
- .form-control {
+ .form-control:not(textarea) {
height: 34px;
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b40dcf93969..88d2f0aaf85 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -153,7 +153,7 @@
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
- border-color: $input-border-focus;
+ border-color: $blue-300;
}
&.select2-active {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4db9efff6ee..136a5612ddc 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -177,7 +177,6 @@ $border-gray-dark: darken($white-normal, $darken-border-factor);
* UI elements
*/
$border-color: #e5e5e5;
-$focus-border-color: $blue-300;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
$well-light-border: #f1f1f1;
@@ -392,8 +391,7 @@ $dropdown-divider-color: rgba(#000, 0.1);
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555;
$dropdown-input-fa-color: #c7c7c7;
-$dropdown-input-focus-border: $focus-border-color;
-$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4);
+$dropdown-input-focus-shadow: rgba($blue-300, 0.4);
$dropdown-loading-bg: rgba(#fff, 0.6);
$dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%);
@@ -834,19 +832,3 @@ Prometheus
$prometheus-table-row-highlight-color: $theme-gray-100;
$priority-label-empty-state-width: 114px;
-
-/*
- * Override Bootstrap 4 variables
- */
-
-$secondary: $gray-light;
-$input-disabled-bg: $gray-light;
-$input-border-color: $theme-gray-200;
-$input-color: $gl-text-color;
-$font-family-sans-serif: $regular-font;
-$font-family-monospace: $monospace-font;
-$input-line-height: 20px;
-$btn-line-height: 20px;
-$table-accent-bg: $gray-light;
-$card-border-color: $border-color;
-$card-cap-bg: $gray-light;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
new file mode 100644
index 00000000000..b9c343fa2e9
--- /dev/null
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -0,0 +1,16 @@
+/*
+ * This file is only for overriding Bootstrap 4 variables.
+ * Please add any new variables to variables.scss
+ */
+
+$secondary: $gray-light;
+$input-disabled-bg: $gray-light;
+$input-border-color: $theme-gray-200;
+$input-color: $gl-text-color;
+$font-family-sans-serif: $regular-font;
+$font-family-monospace: $monospace-font;
+$input-line-height: 20px;
+$btn-line-height: 20px;
+$table-accent-bg: $gray-light;
+$card-border-color: $border-color;
+$card-cap-bg: $gray-light;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index ddd1f8cc98a..892da152b5f 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -36,10 +36,7 @@
line-height: 35px;
padding-top: 7px;
padding-bottom: 7px;
-
- .float-right {
- height: 20px;
- }
+ display: flex;
}
.editor-ref {
@@ -60,20 +57,18 @@
.new-file-name {
display: inline-block;
- max-width: 450px;
+ max-width: 420px;
float: left;
@media(max-width: map-get($grid-breakpoints, lg)-1) {
- width: 280px;
- }
-
- @media(max-width: map-get($grid-breakpoints, md)-1) {
width: 180px;
}
}
.file-buttons {
- font-size: 0;
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
}
.select2 {
@@ -111,12 +106,10 @@
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.file-editor {
.file-title {
- .float-right {
- height: auto;
- }
+ display: block;
}
.new-file-name {
@@ -144,6 +137,10 @@
}
}
}
+
+ .editor-ref {
+ max-width: 250px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 1587aebfe1d..fa8a0f26b5d 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -419,6 +419,7 @@
.stats {
position: relative;
line-height: normal;
+ text-align: right;
flex-shrink: 0;
> span {
@@ -464,7 +465,7 @@
}
.last-updated {
- position: absolute;
+ position: relative;
right: 12px;
min-width: 250px;
text-align: right;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index dcf590e7331..8acd64ca1a1 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -69,7 +69,7 @@
.comment-toolbar,
.nav-links {
- border-color: $focus-border-color;
+ border-color: $blue-300;
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 60b280fd12e..c9405004c38 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -23,7 +23,7 @@ $search-avatar-size: 16px;
.search-text-input:hover,
.form-control:hover,
:not[readonly] {
- border-color: lighten($dropdown-input-focus-border, 20%);
+ border-color: lighten($blue-300, 20%);
box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
}
@@ -127,7 +127,7 @@ input[type='checkbox']:hover {
&.search-active {
form {
@extend .form-control:focus;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: none;
@include media-breakpoint-up(xl) {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 839ac5ba59b..fb03970f64f 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -301,3 +301,17 @@
margin-bottom: 0;
}
}
+
+.mirror-error-badge {
+ background-color: $error-bg;
+ border-radius: $border-radius-default;
+ color: $white-light;
+}
+
+.push-pull-table {
+ margin-top: 1em;
+
+ .mirror-action-buttons {
+ padding-right: 0;
+ }
+}
diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb
new file mode 100644
index 00000000000..93ed22513ac
--- /dev/null
+++ b/app/helpers/mirror_helper.rb
@@ -0,0 +1,5 @@
+module MirrorHelper
+ def mirrors_form_data_attributes
+ { project_mirror_endpoint: project_mirror_path(@project) }
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 9292929be98..3c69677baf0 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -226,7 +226,7 @@ module Ci
end
def cancelable?
- active?
+ active? || created?
end
def retryable?
diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb
index 400d6c407a7..0e667dac21e 100644
--- a/app/models/programming_language.rb
+++ b/app/models/programming_language.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProgrammingLanguage < ActiveRecord::Base
validates :name, presence: true
validates :color, allow_blank: false, color: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 36089995ed3..7735f23cb9e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -470,6 +470,24 @@ class Project < ActiveRecord::Base
}x
end
+ def reference_postfix
+ '>'
+ end
+
+ def reference_postfix_escaped
+ '&gt;'
+ end
+
+ # Pattern used to extract `namespace/project>` project references from text.
+ # '>' or its escaped form ('&gt;') are checked for because '>' is sometimes escaped
+ # when the reference comes from an external source.
+ def markdown_reference_pattern
+ %r{
+ #{reference_pattern}
+ (#{reference_postfix}|#{reference_postfix_escaped})
+ }x
+ end
+
def trending
joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
.reorder('trending_projects.id ASC')
@@ -908,6 +926,10 @@ class Project < ActiveRecord::Base
end
end
+ def to_reference_with_postfix
+ "#{to_reference(full: true)}#{self.class.reference_postfix}"
+ end
+
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from)
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 4f289e6e215..35c19049c04 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'asana'
class AsanaService < Service
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 4234b8044e5..60575e45a90 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AssemblaService < Service
prop_accessor :token, :subdomain
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index edc5c00d9c4..d502423726c 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BambooService < CiService
include ReactiveService
diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb
index e4e3a80976b..1a2bb6a171b 100644
--- a/app/models/project_services/bugzilla_service.rb
+++ b/app/models/project_services/bugzilla_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BugzillaService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 35884c4560c..43edfde851c 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "addressable/uri"
class BuildkiteService < CiService
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 0c526b53d72..f2295a95b60 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class is to be removed with 9.1
# We should also by then remove BuildsEmailService from database
class BuildsEmailService < Service
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index cb4af73807b..1d7877a1fb5 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CampfireService < Service
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
@@ -82,7 +84,7 @@ class CampfireService < Service
before = push[:before]
after = push[:after]
- message = ""
+ message = []
message << "[#{project.full_name}] "
message << "#{push[:user_name]} "
@@ -95,6 +97,6 @@ class CampfireService < Service
message << "#{project.web_url}/compare/#{before}...#{after}"
end
- message
+ message.join
end
end
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index f710fa85b5d..8c68ddc40f2 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'slack-notifier'
module ChatMessage
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index 3273f41dbd2..0cdcfcf0237 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class IssueMessage < BaseMessage
attr_reader :title
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index f412b6833d9..58631e09538 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class MergeMessage < BaseMessage
attr_reader :merge_request_iid
diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb
index 7f9486132e6..741474fb27b 100644
--- a/app/models/project_services/chat_message/note_message.rb
+++ b/app/models/project_services/chat_message/note_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class NoteMessage < BaseMessage
attr_reader :note
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 96fd23aede3..62aec4351db 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class PipelineMessage < BaseMessage
attr_reader :ref_type
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 8d599c5f116..82be33a12a1 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class PushMessage < BaseMessage
attr_reader :after
diff --git a/app/models/project_services/chat_message/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb
index d84b80f2de2..b605d289278 100644
--- a/app/models/project_services/chat_message/wiki_page_message.rb
+++ b/app/models/project_services/chat_message/wiki_page_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class WikiPageMessage < BaseMessage
attr_reader :title
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index a60b4c7fd0d..c10ee07ccf4 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from.
class ChatNotificationService < Service
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 82979c8bd34..f0ef2d925ab 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for CI services
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 456c7f5cee2..b8f8072869c 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CustomIssueTrackerService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb
index 5b8320158fc..6dae4f3a4a6 100644
--- a/app/models/project_services/deployment_service.rb
+++ b/app/models/project_services/deployment_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for deployment services
#
# These services integrate with a deployment solution like Kubernetes/OpenShift,
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index ab4e46da89f..158ae0bf255 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DroneCiService < CiService
include ReactiveService
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index b604d860a87..fb73d430fb1 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailsOnPushService < Service
boolean_accessor :send_from_committer_email
boolean_accessor :disable_diffs
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index a4b1ef09e93..d2835c6ac82 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ExternalWikiService < Service
prop_accessor :external_wiki_url
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index da01ac1b7cf..2545df06f6b 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "flowdock-git-hook"
# Flow dock depends on Grit to compute the number of commits between two given
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 8a6b0ed1a5f..67a92c441b1 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "gemnasium/gitlab_service"
class GemnasiumService < Service
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 16e32a4139e..fa9abf58e62 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Routing
diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb
index a8512c5f57c..272cd0f4e47 100644
--- a/app/models/project_services/hangouts_chat_service.rb
+++ b/app/models/project_services/hangouts_chat_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'hangouts_chat'
class HangoutsChatService < ChatNotificationService
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index dce878e485f..66012f0da99 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class HipchatService < Service
include ActionView::Helpers::SanitizeHelper
@@ -108,7 +110,7 @@ class HipchatService < Service
before = push[:before]
after = push[:after]
- message = ""
+ message = []
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
@@ -132,7 +134,7 @@ class HipchatService < Service
end
end
- message
+ message.join
end
def markdown(text, options = {})
@@ -165,11 +167,11 @@ class HipchatService < Service
description = obj_attr[:description]
issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
- message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"
+ message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
- message
+ message.join
end
def create_merge_request_message(data)
@@ -184,12 +186,11 @@ class HipchatService < Service
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
- message = "#{user_name} #{state} #{merge_request_link} in " \
- "#{project_link}: <b>#{title}</b>"
+ message = ["#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
-
- message
+ message.join
end
def format_title(title)
@@ -235,12 +236,11 @@ class HipchatService < Service
end
subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
- message = "#{user_name} commented on #{subject_html} in #{project_link}: "
+ message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
message << title
message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
-
- message
+ message.join
end
def create_pipeline_message(data)
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 27bdf708c80..a783a314071 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'uri'
class IrkerService < Service
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index df6dcd90985..c7520d766a8 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class IssueTrackerService < Service
validate :one_issue_tracker, if: :activated?, on: :manual_change
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 82d438d5378..cc98b3f5a41 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JiraService < IssueTrackerService
include Gitlab::Routing
include ApplicationHelper
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 722642f6da7..bda1f67b8ff 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# NOTE:
# We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic.
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 0362ed172c7..b8bc83b870e 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MattermostService < ChatNotificationService
def title
'Mattermost notifications'
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 227d430083d..ca324f68d2d 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MattermostSlashCommandsService < SlashCommandsService
include TriggersHelper
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index 99500caec0e..5b0e5fed092 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MicrosoftTeamsService < ChatNotificationService
def title
'Microsoft Teams Notification'
diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb
index b89dc07a73e..6883976f0c8 100644
--- a/app/models/project_services/mock_ci_service.rb
+++ b/app/models/project_services/mock_ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
class MockCiService < CiService
ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze
diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb
index 59a3811ce5d..7ab1687f8ba 100644
--- a/app/models/project_services/mock_deployment_service.rb
+++ b/app/models/project_services/mock_deployment_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MockDeploymentService < DeploymentService
def title
'Mock deployment'
diff --git a/app/models/project_services/mock_monitoring_service.rb b/app/models/project_services/mock_monitoring_service.rb
index ed0318c6b27..bcf8f1df5da 100644
--- a/app/models/project_services/mock_monitoring_service.rb
+++ b/app/models/project_services/mock_monitoring_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MockMonitoringService < MonitoringService
def title
'Mock monitoring'
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
index 9af68b4e821..1b530a8247b 100644
--- a/app/models/project_services/monitoring_service.rb
+++ b/app/models/project_services/monitoring_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for monitoring services
#
# These services integrate with a deployment solution like Prometheus
diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb
index ba62a5b7ac0..003884bb7ac 100644
--- a/app/models/project_services/packagist_service.rb
+++ b/app/models/project_services/packagist_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PackagistService < Service
prop_accessor :username, :token, :server
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 4cf149ac044..6f39a5e6e83 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelinesEmailService < Service
prop_accessor :recipients
boolean_accessor :notify_only_broken_pipelines
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 3476e7d2283..617e502b639 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PivotaltrackerService < Service
API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index df4254e0523..509e5b6089b 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PrometheusService < MonitoringService
include PrometheusAdapter
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 8777a44b72f..4e48c348b45 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PushoverService < Service
BASE_URI = 'https://api.pushover.net/1'.freeze
@@ -79,7 +81,7 @@ class PushoverService < Service
end
if data[:total_commits_count] > 0
- message << "\nTotal commits count: #{data[:total_commits_count]}"
+ message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n")
end
pushover_data = {
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index 3721093a6d1..a80be4b06da 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RedmineService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 71da0af75f6..482808255f9 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SlackService < ChatNotificationService
def title
'Slack notifications'
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 1c3892a3f75..6c82e088231 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SlackSlashCommandsService < SlashCommandsService
include TriggersHelper
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 37ea45109ae..e3ab60adefd 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from.
class SlashCommandsService < Service
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 802678147cf..eeeff5e802a 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TeamcityService < CiService
include ReactiveService
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index e8d35ac326f..b0d5c64e931 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index 7a2e9e5ec5d..b2a88229853 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
end
diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb
index 6b6ab3d8279..b06e55fb5dd 100644
--- a/app/models/protected_tag/create_access_level.rb
+++ b/app/models/protected_tag/create_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedTag::CreateAccessLevel < ActiveRecord::Base
include ProtectedTagAccess
diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb
index f467d4eafa3..b18142a2ac4 100644
--- a/app/models/repository_language.rb
+++ b/app/models/repository_language.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryLanguage < ActiveRecord::Base
belongs_to :project
belongs_to :programming_language
diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb
index 9c9c3172fe6..daac1c57db9 100644
--- a/app/models/site_statistic.rb
+++ b/app/models/site_statistic.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SiteStatistic < ActiveRecord::Base
# prevents the creation of multiple rows
default_value_for :id, 1
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index 26b4b78ac64..90710f73fd3 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
class HashedProject
attr_accessor :project
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index 27cb388c702..9f6f19acb41 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
class LegacyProject
attr_accessor :project
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f52a3bad77d..00c58f15013 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -251,6 +251,7 @@ class ProjectPolicy < BasePolicy
enable :update_pages
enable :read_cluster
enable :create_cluster
+ enable :create_environment_terminal
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index b18e9706db6..07a13c33b89 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -23,9 +23,8 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
- expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
- can?(request.current_user, :admin_environment, environment.project) &&
- terminal_project_environment_path(environment.project, environment)
+ expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment|
+ terminal_project_environment_path(environment.project, environment)
end
expose :folder_path do |environment|
@@ -40,7 +39,13 @@ class EnvironmentEntity < Grape::Entity
private
+ alias_method :environment, :object
+
def current_user
request.current_user
end
+
+ def can_access_terminal?
+ can?(request.current_user, :create_environment_terminal, environment)
+ end
end
diff --git a/app/serializers/project_mirror_serializer.rb b/app/serializers/project_mirror_serializer.rb
new file mode 100644
index 00000000000..7e9ba592252
--- /dev/null
+++ b/app/serializers/project_mirror_serializer.rb
@@ -0,0 +1,3 @@
+class ProjectMirrorSerializer < BaseSerializer
+ entity ProjectMirrorEntity
+end
diff --git a/app/services/ci/enqueue_build_service.rb b/app/services/ci/enqueue_build_service.rb
new file mode 100644
index 00000000000..8140651d980
--- /dev/null
+++ b/app/services/ci/enqueue_build_service.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Ci
+ class EnqueueBuildService < BaseService
+ def execute(build)
+ build.enqueue
+ end
+ end
+end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index cda9bbff3b4..cafee76a33c 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -37,7 +37,7 @@ module Ci
def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status)
- build.action? ? build.actionize : build.enqueue
+ build.action? ? build.actionize : enqueue_build(build)
true
else
build.skip
@@ -93,5 +93,9 @@ module Ci
.where.not(id: latest_statuses.map(&:first))
.update_all(retried: true) if latest_statuses.any?
end
+
+ def enqueue_build(build)
+ Ci::EnqueueBuildService.new(project, @user).execute(build)
+ end
end
end
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index f730fd05176..029efadd75d 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -128,8 +128,8 @@
.col-md-6
- unless @user == current_user
- unless @user.confirmed?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
Confirm user
.card-body
- if @user.unconfirmed_email.present?
@@ -138,8 +138,8 @@
%br
= link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- if @user.blocked?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
This user is blocked
.card-body
%p A blocked user cannot:
@@ -162,8 +162,8 @@
%br
= link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning"
- if @user.access_locked?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
This account has been locked
.card-body
%p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account.
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 8560b72fe85..24f256d083b 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -2,11 +2,11 @@
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
- .editor-ref
+ .editor-ref.block-truncated
= sprite_icon('fork', size: 12)
= ref
- %span.editor-file-name
- - if current_action?(:edit) || current_action?(:update)
+ - if current_action?(:edit) || current_action?(:update)
+ %span.editor-file-name
= text_field_tag 'file_path', (params[:file_path] || @path),
class: 'form-control new-file-path js-file-path-name-input'
@@ -16,7 +16,7 @@
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name js-file-path-name-input'
- .float-right.file-buttons
+ .file-buttons
= button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 64f0fde30cf..3d811be3fe3 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -1,10 +1,11 @@
.account-well.prepend-top-default.append-bottom-default
%ul
%li
- The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>.
- %li
- Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.
- %li
- The update action will time out after 10 minutes. For big repositories, use a clone/push combination.
- %li
- The Git LFS objects will <strong>not</strong> be synced.
+ = _('The repository must be accessible over <code>http://</code>,
+ <code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe
+ %li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
+ %li= _('The update action will time out after 15 minutes. For big repositories, use a clone/push combination.')
+ %li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
+ %li
+ = _('This user will be the author of all events in the activity feed that are the result of an update,
+ like new branches being created or new commits being pushed to existing branches.')
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
new file mode 100644
index 00000000000..53387b3a50c
--- /dev/null
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -0,0 +1,63 @@
+- expanded = Rails.env.test?
+- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
+
+%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= _('Mirroring repositories')
+ %button.btn.js-settings-toggle
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
+ = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
+
+ .settings-content
+ = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
+ .panel.panel-default
+ .panel-heading
+ %h3.panel-title= _('Mirror a repository')
+ .panel-body
+ %div= form_errors(@project)
+
+ .form-group.has-feedback
+ = label_tag :url, _('Git repository URL'), class: 'label-light'
+ = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
+
+ = render 'projects/mirrors/instructions'
+
+ = render 'projects/mirrors/mirror_repos_form', f: f
+
+ .form-check.append-bottom-10
+ = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
+ = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label'
+ = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
+
+ .panel-footer
+ = f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror
+
+ .panel.panel-default
+ .table-responsive
+ %table.table.push-pull-table
+ %thead
+ %tr
+ %th
+ = _('Mirrored repositories')
+ = render_if_exists 'projects/mirrors/mirrored_repositories_count'
+ %th= _('Direction')
+ %th= _('Last update')
+ %th
+ %th
+ %tbody.js-mirrors-table-body
+ = render_if_exists 'projects/mirrors/table_pull_row'
+ - @project.remote_mirrors.each_with_index do |mirror, index|
+ - if mirror.enabled
+ %tr
+ %td= mirror.safe_url
+ %td= _('Push')
+ %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
+ %td
+ - if mirror.last_error.present?
+ .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
+ %td.mirror-action-buttons
+ .btn-group.mirror-actions-group.pull-right{ role: 'group' }
+ = render 'shared/remote_mirror_update_button', remote_mirror: mirror
+ %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml
new file mode 100644
index 00000000000..93994cb30ac
--- /dev/null
+++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml
@@ -0,0 +1,18 @@
+- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
+
+.form-group
+ = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
+ = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true
+
+= f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f|
+ = rm_f.hidden_field :enabled, value: '1'
+ = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
+ = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
+
+.form-group
+ = label_tag :auth_method, _('Authentication method'), class: 'label-bold'
+ = select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method" }
+
+.form-group.js-password-group.collapse
+ = label_tag :password, _('Password'), class: 'label-bold'
+ = text_field_tag :password, '', class: 'form-control js-password'
diff --git a/app/views/projects/mirrors/_push.html.haml b/app/views/projects/mirrors/_push.html.haml
deleted file mode 100644
index 08375e09816..00000000000
--- a/app/views/projects/mirrors/_push.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-- expanded = Rails.env.test?
-%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Push to a remote repository
- %button.btn.js-settings-toggle
- = expanded ? 'Collapse' : 'Expand'
- %p
- Set up the remote repository that you want to update with the content of the current repository
- every time someone pushes to it.
- = link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank'
- .settings-content
- = form_for @project, url: project_mirror_path(@project) do |f|
- %div
- = form_errors(@project)
- = render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror
- - if @remote_mirror.last_error.present?
- .panel.panel-danger
- .panel-heading
- - if @remote_mirror.last_update_at
- The remote repository failed to update #{time_ago_with_tooltip(@remote_mirror.last_update_at)}.
- - else
- The remote repository failed to update.
-
- - if @remote_mirror.last_successful_update_at
- Last successful update #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
- .panel-body
- %pre
- :preserve
- #{h(@remote_mirror.last_error.strip)}
- = f.fields_for :remote_mirrors, @remote_mirror do |rm_form|
- .form-group
- = rm_form.check_box :enabled, class: "float-left"
- .prepend-left-20
- = rm_form.label :enabled, "Remote mirror repository", class: "label-bold append-bottom-0"
- %p.light.append-bottom-0
- Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it.
- .form-group.has-feedback
- = rm_form.label :url, "Git repository URL", class: "label-bold"
- = rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git'
-
- = render "projects/mirrors/instructions"
-
- .form-group
- = rm_form.check_box :only_protected_branches, class: 'float-left'
- .prepend-left-20
- = rm_form.label :only_protected_branches, class: 'label-bold'
- = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
-
- = f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
diff --git a/app/views/projects/mirrors/_show.html.haml b/app/views/projects/mirrors/_show.html.haml
index de77701a373..8318d5898a1 100644
--- a/app/views/projects/mirrors/_show.html.haml
+++ b/app/views/projects/mirrors/_show.html.haml
@@ -1,3 +1 @@
-- if can?(current_user, :admin_remote_mirror, @project)
- = render 'projects/mirrors/push'
-
+= render 'projects/mirrors/mirror_repos'
diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml
index cd9177c0f9e..988dabef3a0 100644
--- a/app/views/projects/pages/_use.html.haml
+++ b/app/views/projects/pages/_use.html.haml
@@ -1,6 +1,6 @@
- unless @project.pages_deployed?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
Configure pages
.card-body
%p
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index 34de1c0695f..f32cff18fa8 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -1,13 +1,6 @@
-- if @project.has_remote_mirror?
- .append-bottom-default
- - if remote_mirror.update_in_progress?
- %span.btn.disabled
- = icon("refresh spin")
- Updating&hellip;
- - else
- = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn" do
- = icon("refresh")
- Update Now
- - if @remote_mirror.last_successful_update_at
- %p.inline.prepend-left-10
- Successfully updated #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
+- if remote_mirror.update_in_progress?
+ %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
+ = icon("refresh spin")
+- else
+ = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
+ = icon("refresh")
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index ee6354b1c28..ee97f0172da 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -2,8 +2,8 @@
- primary = local_assigns.fetch(:primary, false)
- panel_class = primary ? 'bg-primary text-white' : ''
-.card{ class: panel_class }
- .card-header
+.card
+ .card-header{ class: panel_class }
.title
= title
- if show_counter
diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml
index 7bcc54e7459..9d230d12be2 100644
--- a/app/views/shared/plugins/_index.html.haml
+++ b/app/views/shared/plugins/_index.html.haml
@@ -19,5 +19,5 @@
.monospace
= File.basename(file)
- else
- %p.card.bg-light.text-center
- No plugins found.
+ .card.bg-light.text-center
+ .nothing-here-block No plugins found.
diff --git a/changelogs/unreleased/2747-protected-environments-backend-ce.yml b/changelogs/unreleased/2747-protected-environments-backend-ce.yml
new file mode 100644
index 00000000000..dcec74a33a7
--- /dev/null
+++ b/changelogs/unreleased/2747-protected-environments-backend-ce.yml
@@ -0,0 +1,5 @@
+---
+title: CE Port of Protected Environments backend
+merge_request: 20859
+author:
+type: other
diff --git a/changelogs/unreleased/28930-add-project-reference-filter.yml b/changelogs/unreleased/28930-add-project-reference-filter.yml
new file mode 100644
index 00000000000..c7679c5fe76
--- /dev/null
+++ b/changelogs/unreleased/28930-add-project-reference-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Add the ability to reference projects in comments and other markdown text.
+merge_request: 20285
+author: Reuben Pereira
+type: added
diff --git a/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml b/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml
new file mode 100644
index 00000000000..81ca632947d
--- /dev/null
+++ b/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml
@@ -0,0 +1,5 @@
+---
+title: Fix buttons on the new file page wrapping outside of the container
+merge_request: 21015
+author:
+type: fixed
diff --git a/changelogs/unreleased/48320-cancel-a-created-job.yml b/changelogs/unreleased/48320-cancel-a-created-job.yml
new file mode 100644
index 00000000000..3e7a9e9ae52
--- /dev/null
+++ b/changelogs/unreleased/48320-cancel-a-created-job.yml
@@ -0,0 +1,5 @@
+---
+title: Allows to cancel a Created job
+merge_request: 20635
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml b/changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml
new file mode 100644
index 00000000000..ca17a41d611
--- /dev/null
+++ b/changelogs/unreleased/50063-add-missing-i18n-strings-to-issue-boards.yml
@@ -0,0 +1,5 @@
+---
+title: Added missing i18n strings to issue boards lables dropdown
+merge_request: 21081
+author:
+type: other
diff --git a/changelogs/unreleased/50101-stuck-component.yml b/changelogs/unreleased/50101-stuck-component.yml
new file mode 100644
index 00000000000..bfe4009a2b3
--- /dev/null
+++ b/changelogs/unreleased/50101-stuck-component.yml
@@ -0,0 +1,5 @@
+---
+title: Creates Vvue component for warning block about stuck runners
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50126-blocked-user-card.yml b/changelogs/unreleased/50126-blocked-user-card.yml
new file mode 100644
index 00000000000..a42d62e5530
--- /dev/null
+++ b/changelogs/unreleased/50126-blocked-user-card.yml
@@ -0,0 +1,5 @@
+---
+title: Fix blocked user card style
+merge_request: 21095
+author:
+type: fixed
diff --git a/changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml b/changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml
new file mode 100644
index 00000000000..068681dfe19
--- /dev/null
+++ b/changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce differences between CE and EE code base in reports components
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml b/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml
new file mode 100644
index 00000000000..a77f3baeed3
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen string in rest of app/models/**/*.rb
+merge_request: gfyoung
+author:
+type: performance
diff --git a/changelogs/unreleased/ide-header-buttons-tooltip.yml b/changelogs/unreleased/ide-header-buttons-tooltip.yml
new file mode 100644
index 00000000000..4c8f6fd554f
--- /dev/null
+++ b/changelogs/unreleased/ide-header-buttons-tooltip.yml
@@ -0,0 +1,5 @@
+---
+title: Added tooltips to tree list header
+merge_request: 21138
+author:
+type: added
diff --git a/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml b/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml
new file mode 100644
index 00000000000..e31768773b1
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml
@@ -0,0 +1,5 @@
+---
+title: Rails5 fix specs duplicate key value violates unique constraint 'index_gpg_signatures_on_commit_sha'
+merge_request: 21119
+author: Jasper Maes
+type: fixed
diff --git a/db/fixtures/development/23_spam_logs.rb b/db/fixtures/development/23_spam_logs.rb
new file mode 100644
index 00000000000..81cc13e6b2d
--- /dev/null
+++ b/db/fixtures/development/23_spam_logs.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Db
+ module Fixtures
+ module Development
+ class SpamLog
+ def self.seed
+ Gitlab::Seeder.quiet do
+ (::SpamLog.default_per_page + 3).times do |i|
+ ::SpamLog.create(
+ user: self.random_user,
+ user_agent: FFaker::Lorem.sentence,
+ source_ip: FFaker::Internet.ip_v4_address,
+ title: FFaker::Lorem.sentence,
+ description: FFaker::Lorem.paragraph,
+ via_api: FFaker::Boolean.random,
+ submitted_as_ham: FFaker::Boolean.random,
+ recaptcha_verified: FFaker::Boolean.random)
+ print '.'
+ end
+ end
+ end
+
+ def self.random_user
+ User.find(User.pluck(:id).sample)
+ end
+ end
+ end
+ end
+end
+
+Db::Fixtures::Development::SpamLog.seed
diff --git a/doc/api/projects.md b/doc/api/projects.md
index f360b49c293..bda4164ee92 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -258,8 +258,7 @@ GET /projects?custom_attributes[key]=value&custom_attributes[other_key]=other_va
## List user projects
-Get a list of visible projects for the given user. When accessed without
-authentication, only public projects are returned.
+Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned.
```
GET /users/:user_id/projects
diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md
index d4cce79b067..57ae318e821 100644
--- a/doc/development/contributing/design.md
+++ b/doc/development/contributing/design.md
@@ -18,23 +18,16 @@ To better understand the priority by which UX tackles issues, see the [UX sectio
Once an issue has been worked on and is ready for development, a UXer removes the ~"UX" label and applies the ~"UX ready" label to that issue.
-The UX team has a special type label called ~"design artifact". This label indicates that the final output
-for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone.
-Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is
-needed until the solution has been decided.
+There is a special type label called ~"product discovery". It represents a discovery issue intended for UX, PM, FE, and BE to discuss the problem and potential solutions. The final output for this issue could be a doc of requirements, a design artifact, or even a prototype. The solution will be developed in a subsequent milestone.
-~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
+~"product discovery" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
-To prevent the misunderstanding that a feature will be be delivered in the
-assigned milestone, when only UX design is planned for that milestone, the
-Product Manager should create a separate issue for the ~"design artifact",
-assign the ~UX, ~"design artifact" and ~"Deliverable" labels, add a milestone
-and use a title that makes it clear that the scheduled issue is design only
-(e.g. `Design exploration for XYZ`).
+The initial issue should be about the problem we are solving. If a separate [product discovery issue](#product-discovery-issues) is needed for additional research and design work, it will be created by a PM or UX person. Assign the ~UX, ~"product discovery" and ~"Deliverable" labels, add a milestone and use a title that makes it clear that the scheduled issue is product discovery
+(e.g. `Product discovery for XYZ`).
-When the ~"design artifact" issue has been completed, the UXer removes the ~UX
+When the ~"product discovery" issue has been completed, the UXer removes the ~UX
label, adds the ~"UX ready" label and closes the issue. This indicates the
-design artifact is complete. The UXer will also copy the designs to related
+UX work for the issue is complete. The UXer will also copy any designs to related
issues for implementation in an upcoming milestone.
## Style guides
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index e1e13474b75..53dfe6774e9 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -133,3 +133,27 @@ afterEach(() => {
vm.$destroy();
});
```
+## Testing with older browsers
+
+Some regressions only affect a specific browser version. We can install and test in particular browsers with either Firefox or Browserstack using the following steps:
+
+
+### Browserstack
+
+[Browserstack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
+You can use it directly through the [live app](https://www.browserstack.com/live) or you can install the [chrome extension](https://chrome.google.com/webstore/detail/browserstack/nkihdmlheodkdfojglpcjjmioefjahjb) for easy access.
+You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
+
+### Firefox
+
+#### macOS
+You can download any older version of Firefox from the releases FTP server, https://ftp.mozilla.org/pub/firefox/releases/
+
+1. From the website, select a version, in this case `50.0.1`.
+2. Go to the mac folder.
+3. Select your preferred language, you will find the dmg package inside, download it.
+4. Drag and drop the application to any other folder but the `Applications` folder.
+5. Rename the application to something like `Firefox_Old`.
+6. Move the application to the `Applications` folder.
+7. Open up a terminal and run `/Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager` to create a new profile specific to that Firefox version.
+8. Once the profile has been created, quit the app, and run it again like normal. You now have a working older Firefox version.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index bd199b55a61..d7bf6838fb3 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -259,6 +259,7 @@ GFM will recognize the following:
| `@user_name` | specific user |
| `@group_name` | specific group |
| `@all` | entire team |
+| `namespace/project>` | project |
| `#12345` | issue |
| `!123` | merge request |
| `$123` | snippet |
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 8e486318980..77fa517b5b1 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1122,7 +1122,6 @@ X-Gitlab-Event: Build Hook
},
"repository": {
"name": "gitlab_test",
- "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
"description": "Atque in sunt eos similique dolores voluptatem.",
"homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
"git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb
new file mode 100644
index 00000000000..83cf45097ed
--- /dev/null
+++ b/lib/banzai/filter/project_reference_filter.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that replaces project references with links.
+ class ProjectReferenceFilter < ReferenceFilter
+ self.reference_type = :project
+
+ # Public: Find `namespace/project>` project references in text
+ #
+ # ProjectReferenceFilter.references_in(text) do |match, project|
+ # "<a href=...>#{project}></a>"
+ # end
+ #
+ # text - String text to search.
+ #
+ # Yields the String match, and the String project name.
+ #
+ # Returns a String replaced with the return of the block.
+ def self.references_in(text)
+ text.gsub(Project.markdown_reference_pattern) do |match|
+ yield match, "#{$~[:namespace]}/#{$~[:project]}"
+ end
+ end
+
+ def call
+ ref_pattern = Project.markdown_reference_pattern
+ ref_pattern_start = /\A#{ref_pattern}\z/
+
+ nodes.each do |node|
+ if text_node?(node)
+ replace_text_when_pattern_matches(node, ref_pattern) do |content|
+ project_link_filter(content)
+ end
+ elsif element_node?(node)
+ yield_valid_link(node) do |link, inner_html|
+ if link =~ ref_pattern_start
+ replace_link_node_with_href(node, link) do
+ project_link_filter(link, link_content: inner_html)
+ end
+ end
+ end
+ end
+ end
+
+ doc
+ end
+
+ # Replace `namespace/project>` project references in text with links to the referenced
+ # project page.
+ #
+ # text - String text to replace references in.
+ # link_content - Original content of the link being replaced.
+ #
+ # Returns a String with `namespace/project>` references replaced with links. All links
+ # have `gfm` and `gfm-project` class names attached for styling.
+ def project_link_filter(text, link_content: nil)
+ self.class.references_in(text) do |match, project_path|
+ cached_call(:banzai_url_for_object, match, path: [Project, project_path.downcase]) do
+ if project = projects_hash[project_path.downcase]
+ link_to_project(project, link_content: link_content) || match
+ else
+ match
+ end
+ end
+ end
+ end
+
+ # Returns a Hash containing all Project objects for the project
+ # references in the current document.
+ #
+ # The keys of this Hash are the project paths, the values the
+ # corresponding Project objects.
+ def projects_hash
+ @projects ||= Project.eager_load(:route, namespace: [:route])
+ .where_full_path_in(projects)
+ .index_by(&:full_path)
+ .transform_keys(&:downcase)
+ end
+
+ # Returns all projects referenced in the current document.
+ def projects
+ refs = Set.new
+
+ nodes.each do |node|
+ node.to_html.scan(Project.markdown_reference_pattern) do
+ refs << "#{$~[:namespace]}/#{$~[:project]}"
+ end
+ end
+
+ refs.to_a
+ end
+
+ private
+
+ def urls
+ Gitlab::Routing.url_helpers
+ end
+
+ def link_class
+ reference_class(:project)
+ end
+
+ def link_to_project(project, link_content: nil)
+ url = urls.project_url(project, only_path: context[:only_path])
+ data = data_attribute(project: project.id)
+ content = link_content || project.to_reference_with_postfix
+
+ link_tag(url, data, content, project.name)
+ end
+
+ def link_tag(url, data, link_content, title)
+ %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 5dab80dd3eb..e9be05e174e 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -36,6 +36,7 @@ module Banzai
def self.reference_filters
[
Filter::UserReferenceFilter,
+ Filter::ProjectReferenceFilter,
Filter::IssueReferenceFilter,
Filter::ExternalIssueReferenceFilter,
Filter::MergeRequestReferenceFilter,
diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb
new file mode 100644
index 00000000000..b4e3a55b4f1
--- /dev/null
+++ b/lib/banzai/reference_parser/project_parser.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Banzai
+ module ReferenceParser
+ class ProjectParser < BaseParser
+ include Gitlab::Utils::StrongMemoize
+
+ self.reference_type = :project
+
+ def references_relation
+ Project
+ end
+
+ private
+
+ # Returns an Array of Project ids that can be read by the given user.
+ #
+ # user - The User for which to check the projects
+ def readable_project_ids_for(user)
+ @project_ids_by_user ||= {}
+ @project_ids_by_user[user] ||=
+ Project.public_or_visible_to_user(user).where("projects.id IN (?)", @projects_for_nodes.values.map(&:id)).pluck(:id)
+ end
+
+ def can_read_reference?(user, ref_project, node)
+ readable_project_ids_for(user).include?(ref_project.try(:id))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 091e81028bb..81807ed659c 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -47,7 +47,7 @@ module Gitlab
end
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: [])
- @relation_name = OVERRIDES[relation_sym] || relation_sym
+ @relation_name = self.class.overrides[relation_sym] || relation_sym
@relation_hash = relation_hash.except('noteable_id')
@members_mapper = members_mapper
@user = user
@@ -76,6 +76,10 @@ module Gitlab
generate_imported_object
end
+ def self.overrides
+ OVERRIDES
+ end
+
private
def setup_models
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bec60cf592a..92f424ccdf0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -101,6 +101,9 @@ msgstr[1] ""
msgid "%{filePath} deleted"
msgstr ""
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
@@ -640,6 +643,9 @@ msgstr ""
msgid "Authentication log"
msgstr ""
+msgid "Authentication method"
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -2169,6 +2175,9 @@ msgstr ""
msgid "Diffs|Something went wrong while fetching diff lines."
msgstr ""
+msgid "Direction"
+msgstr ""
+
msgid "Directory name"
msgstr ""
@@ -2391,6 +2400,9 @@ msgstr ""
msgid "Environments|You don't have any environments right now."
msgstr ""
+msgid "Error"
+msgstr ""
+
msgid "Error Reporting and Logging"
msgstr ""
@@ -2439,6 +2451,9 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
msgid "Estimated"
msgstr ""
@@ -2517,6 +2532,9 @@ msgstr ""
msgid "Failed to remove issue from board, please try again."
msgstr ""
+msgid "Failed to remove mirror."
+msgstr ""
+
msgid "Failed to remove the pipeline schedule"
msgstr ""
@@ -2717,6 +2735,9 @@ msgstr ""
msgid "Go back"
msgstr ""
+msgid "Go to"
+msgstr ""
+
msgid "Go to %{link_to_google_takeout}."
msgstr ""
@@ -3031,12 +3052,18 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
msgid "Incompatible Project"
msgstr ""
msgid "Inline"
msgstr ""
+msgid "Input your repository URL"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -3468,6 +3495,21 @@ msgstr ""
msgid "Milestones|Promote Milestone"
msgstr ""
+msgid "Mirror a repository"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -3528,6 +3570,9 @@ msgstr ""
msgid "Network"
msgstr ""
+msgid "Never"
+msgstr ""
+
msgid "New"
msgstr ""
@@ -3791,6 +3836,9 @@ msgstr ""
msgid "Only comments from the following commit are shown below"
msgstr ""
+msgid "Only mirror protected branches"
+msgstr ""
+
msgid "Only project members can comment."
msgstr ""
@@ -4379,6 +4427,9 @@ msgstr ""
msgid "Public pipelines"
msgstr ""
+msgid "Push"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -4588,6 +4639,9 @@ msgstr ""
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr ""
+msgid "Runners page"
+msgstr ""
+
msgid "Running"
msgstr ""
@@ -4780,6 +4834,9 @@ msgstr ""
msgid "Set up Koding"
msgstr ""
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
@@ -5189,6 +5246,9 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
msgstr ""
@@ -5246,6 +5306,9 @@ msgstr ""
msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
msgstr ""
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
+msgstr ""
+
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
msgstr ""
@@ -5270,6 +5333,9 @@ msgstr ""
msgid "The time taken by each data entry gathered by that stage."
msgstr ""
+msgid "The update action will time out after 15 minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -5408,6 +5474,9 @@ msgstr ""
msgid "This user has no identities"
msgstr ""
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -5721,9 +5790,15 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update now"
+msgstr ""
+
msgid "Update your group name, description, avatar, and other general settings."
msgstr ""
+msgid "Updating"
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index aeed38aeb76..7203c5b1c21 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Blob shortcuts' do
+describe 'Blob shortcuts', :js do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
let(:path) { project.repository.ls_files(project.repository.root_ref)[0] }
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index f56174fc85c..612722eeaad 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -210,7 +210,7 @@ describe "User browses files" do
end
end
- context "when browsing a file content" do
+ context "when browsing a file content", :js do
before do
visit(tree_path_root_ref)
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 0e9f83a16ce..5d37877ccb3 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Files > User deletes files' do
+describe 'Projects > Files > User deletes files', :js do
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index ccc1bc1bc10..072dc5820c4 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Files > User edits files' do
+describe 'Projects > Files > User edits files', :js do
include ProjectForksHelper
let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index 3a81e77c4ba..3f973338305 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Files > User replaces files' do
+describe 'Projects > Files > User replaces files', :js do
include DropzoneHelper
let(:fork_message) do
diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb
index 5259a8942dc..33e9b73efe8 100644
--- a/spec/features/projects/remote_mirror_spec.rb
+++ b/spec/features/projects/remote_mirror_spec.rb
@@ -17,7 +17,7 @@ describe 'Project remote mirror', :feature do
visit project_mirror_path(project)
- expect(page).to have_content('The remote repository failed to update.')
+ expect_mirror_to_have_error_and_timeago('Never')
end
end
@@ -27,8 +27,14 @@ describe 'Project remote mirror', :feature do
visit project_mirror_path(project)
- expect(page).to have_content('The remote repository failed to update 5 minutes ago.')
+ expect_mirror_to_have_error_and_timeago('5 minutes ago')
end
end
+
+ def expect_mirror_to_have_error_and_timeago(timeago)
+ row = first('.js-mirrors-table-body tr')
+ expect(row).to have_content('Error')
+ expect(row).to have_content(timeago)
+ end
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index a0f5b234ebc..377a75cbcb3 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -129,9 +129,8 @@ describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
end
- it 'shows push mirror settings' do
- expect(page).to have_selector('#project_remote_mirrors_attributes_0_enabled')
- expect(page).to have_selector('#project_remote_mirrors_attributes_0_url')
+ it 'shows push mirror settings', :js do
+ expect(page).to have_selector('#mirror_direction')
end
end
end
diff --git a/spec/javascripts/ide/components/new_dropdown/button_spec.js b/spec/javascripts/ide/components/new_dropdown/button_spec.js
index ef083d06ba7..6a326b5bd92 100644
--- a/spec/javascripts/ide/components/new_dropdown/button_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/button_spec.js
@@ -46,4 +46,20 @@ describe('IDE new entry dropdown button component', () => {
done();
});
});
+
+ describe('tooltipTitle', () => {
+ it('returns empty string when showLabel is true', () => {
+ expect(vm.tooltipTitle).toBe('');
+ });
+
+ it('returns label', done => {
+ vm.showLabel = false;
+
+ vm.$nextTick(() => {
+ expect(vm.tooltipTitle).toBe('Testing');
+
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/preview/clientside_spec.js b/spec/javascripts/ide/components/preview/clientside_spec.js
index 3ec65882418..d6983f5a3b8 100644
--- a/spec/javascripts/ide/components/preview/clientside_spec.js
+++ b/spec/javascripts/ide/components/preview/clientside_spec.js
@@ -292,6 +292,8 @@ describe('IDE clientside preview', () => {
describe('update', () => {
beforeEach(() => {
jasmine.clock().install();
+
+ vm.sandpackReady = true;
vm.manager.updatePreview = jasmine.createSpy('updatePreview');
vm.manager.listener = jasmine.createSpy('updatePreview');
});
@@ -306,7 +308,7 @@ describe('IDE clientside preview', () => {
vm.update();
- jasmine.clock().tick(500);
+ jasmine.clock().tick(250);
expect(vm.initPreview).toHaveBeenCalled();
});
@@ -314,7 +316,7 @@ describe('IDE clientside preview', () => {
it('calls updatePreview', () => {
vm.update();
- jasmine.clock().tick(500);
+ jasmine.clock().tick(250);
expect(vm.manager.updatePreview).toHaveBeenCalledWith(vm.sandboxOpts);
});
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
index 90c28c769f7..8564f04ce8a 100644
--- a/spec/javascripts/ide/stores/actions/merge_request_spec.js
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -1,12 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
-import {
+import actions, {
getMergeRequestData,
getMergeRequestChanges,
getMergeRequestVersions,
+ openMergeRequest,
} from '~/ide/stores/actions/merge_request';
import service from '~/ide/services';
+import { activityBarViews } from '~/ide/constants';
import { resetStore } from '../../helpers';
describe('IDE store merge request actions', () => {
@@ -238,4 +240,101 @@ describe('IDE store merge request actions', () => {
});
});
});
+
+ describe('openMergeRequest', () => {
+ const mr = {
+ projectId: 'abcproject',
+ targetProjectId: 'defproject',
+ mergeRequestId: 2,
+ };
+ let testMergeRequest;
+ let testMergeRequestChanges;
+
+ beforeEach(() => {
+ testMergeRequest = {
+ source_branch: 'abcbranch',
+ };
+ testMergeRequestChanges = {
+ changes: [],
+ };
+ store.state.entries = {
+ foo: {},
+ bar: {},
+ };
+
+ spyOn(store, 'dispatch').and.callFake((type) => {
+ switch (type) {
+ case 'getMergeRequestData':
+ return Promise.resolve(testMergeRequest);
+ case 'getMergeRequestChanges':
+ return Promise.resolve(testMergeRequestChanges);
+ default:
+ return Promise.resolve();
+ }
+ });
+ });
+
+ it('dispatch actions for merge request data', done => {
+ openMergeRequest(store, mr)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['getMergeRequestData', mr],
+ ['setCurrentBranchId', testMergeRequest.source_branch],
+ ['getBranchData', {
+ projectId: mr.projectId,
+ branchId: testMergeRequest.source_branch,
+ }],
+ ['getFiles', {
+ projectId: mr.projectId,
+ branchId: testMergeRequest.source_branch,
+ }],
+ ['getMergeRequestVersions', mr],
+ ['getMergeRequestChanges', mr],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('updates activity bar view and gets file data, if changes are found', done => {
+ testMergeRequestChanges.changes = [
+ { new_path: 'foo' },
+ { new_path: 'bar' },
+ ];
+
+ openMergeRequest(store, mr)
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith('updateActivityBarView', activityBarViews.review);
+
+ testMergeRequestChanges.changes.forEach((change, i) => {
+ expect(store.dispatch).toHaveBeenCalledWith('setFileMrChange', {
+ file: store.state.entries[change.new_path],
+ mrChange: change,
+ });
+ expect(store.dispatch).toHaveBeenCalledWith('getFileData', {
+ path: change.new_path,
+ makeFileActive: i === 0,
+ });
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('flashes message, if error', done => {
+ const flashSpy = spyOnDependency(actions, 'flash');
+ store.dispatch.and.returnValue(Promise.reject());
+
+ openMergeRequest(store, mr)
+ .then(() => {
+ fail('Expected openMergeRequest to throw an error');
+ })
+ .catch(() => {
+ expect(flashSpy).toHaveBeenCalledWith(jasmine.any(String));
+ })
+ .then(done)
+ .catch(done.fail);
+
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index 6a85968e199..667e3e0a7ef 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -5,6 +5,7 @@ import {
showBranchNotFoundError,
createNewBranchFromDefault,
getBranchData,
+ openBranch,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
@@ -224,4 +225,55 @@ describe('IDE store project actions', () => {
});
});
});
+
+ describe('openBranch', () => {
+ const branch = {
+ projectId: 'feature/lorem-ipsum',
+ branchId: '123-lorem',
+ };
+
+ beforeEach(() => {
+ store.state.entries = {
+ foo: { pending: false },
+ 'foo/bar-pending': { pending: true },
+ 'foo/bar': { pending: false },
+ };
+
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
+ });
+
+ it('dispatches branch actions', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['getBranchData', branch],
+ ['getFiles', branch],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('handles tree entry action, if basePath is given', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar/' })
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ store.state.entries['foo/bar'],
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not handle tree entry action, if entry is pending', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything());
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/branches/actions_spec.js b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
index a0fce578958..010f56af03b 100644
--- a/spec/javascripts/ide/stores/modules/branches/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
@@ -9,7 +9,6 @@ import {
receiveBranchesSuccess,
fetchBranches,
resetBranches,
- openBranch,
} from '~/ide/stores/modules/branches/actions';
import { branches, projectData } from '../../../mock_data';
@@ -174,20 +173,5 @@ describe('IDE branches actions', () => {
);
});
});
-
- describe('openBranch', () => {
- it('dispatches goToRoute action with path', done => {
- const branchId = branches[0].name;
- const expectedPath = `/project/${projectData.name_with_namespace}/edit/${branchId}`;
- testAction(
- openBranch,
- branchId,
- mockedState,
- [],
- [{ type: 'goToRoute', payload: expectedPath }],
- done,
- );
- });
- });
});
});
diff --git a/spec/javascripts/jobs/stuck_block_spec.js b/spec/javascripts/jobs/stuck_block_spec.js
new file mode 100644
index 00000000000..4e2108dfdfb
--- /dev/null
+++ b/spec/javascripts/jobs/stuck_block_spec.js
@@ -0,0 +1,81 @@
+import Vue from 'vue';
+import component from '~/jobs/components/stuck_block.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Stuck Block Job component', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with no runners for project', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: true,
+ runnersPath: '/root/project/runners#js-runners-settings',
+ });
+ });
+
+ it('renders only information about project not having runners', () => {
+ expect(vm.$el.querySelector('.js-stuck-no-runners')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull();
+ });
+
+ it('renders link to runners page', () => {
+ expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual(
+ '/root/project/runners#js-runners-settings',
+ );
+ });
+ });
+
+ describe('with tags', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: false,
+ tags: ['docker', 'gitlab-org'],
+ runnersPath: '/root/project/runners#js-runners-settings',
+ });
+ });
+
+ it('renders information about the tags not being set', () => {
+ expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-with-tags')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull();
+ });
+
+ it('renders tags', () => {
+ expect(vm.$el.textContent).toContain('docker');
+ expect(vm.$el.textContent).toContain('gitlab-org');
+ });
+
+ it('renders link to runners page', () => {
+ expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual(
+ '/root/project/runners#js-runners-settings',
+ );
+ });
+ });
+
+ describe('without active runners', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: false,
+ runnersPath: '/root/project/runners#js-runners-settings',
+ });
+ });
+
+ it('renders information about project not having runners', () => {
+ expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull();
+ expect(vm.$el.querySelector('.js-stuck-no-active-runner')).not.toBeNull();
+ });
+
+ it('renders link to runners page', () => {
+ expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual(
+ '/root/project/runners#js-runners-settings',
+ );
+ });
+ });
+});
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
new file mode 100644
index 00000000000..48140305e26
--- /dev/null
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::ProjectReferenceFilter do
+ include FilterSpecHelper
+
+ def invalidate_reference(reference)
+ "#{reference.reverse}"
+ end
+
+ def get_reference(project)
+ project.to_reference_with_postfix
+ end
+
+ let(:project) { create(:project, :public) }
+ subject { project }
+ let(:subject_name) { "project" }
+ let(:reference) { get_reference(project) }
+
+ it_behaves_like 'user reference or project reference'
+
+ it 'ignores invalid projects' do
+ exp = act = "Hey #{invalidate_reference(reference)}"
+
+ expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp))
+ end
+
+ it 'allows references with text after the > character' do
+ doc = reference_filter("Hey #{reference}foo")
+ expect(doc.css('a').first.attr('href')).to eq urls.project_url(subject)
+ end
+
+ %w(pre code a style).each do |elem|
+ it "ignores valid references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>Hey #{CGI.escapeHTML(reference)}</#{elem}>"
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip'
+ end
+
+ context 'in group context' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ let(:nested_group) { create(:group, :nested) }
+ let(:nested_project) { create(:project, group: nested_group) }
+
+ it 'supports mentioning a project' do
+ reference = get_reference(project)
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.project_url(project)
+ end
+
+ it 'supports mentioning a project in a nested group' do
+ reference = get_reference(nested_project)
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.project_url(nested_project)
+ end
+ end
+
+ describe '#projects_hash' do
+ it 'returns a Hash containing all Projects' do
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>")
+ filter = described_class.new(document, project: project)
+
+ expect(filter.projects_hash).to eq({ project.full_path => project })
+ end
+ end
+
+ describe '#projects' do
+ it 'returns the projects mentioned in a document' do
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>")
+ filter = described_class.new(document, project: project)
+
+ expect(filter.projects).to eq([project.full_path])
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 2f86a046d28..334d29a5368 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -3,9 +3,17 @@ require 'spec_helper'
describe Banzai::Filter::UserReferenceFilter do
include FilterSpecHelper
+ def get_reference(user)
+ user.to_reference
+ end
+
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
- let(:reference) { user.to_reference }
+ subject { user }
+ let(:subject_name) { "user" }
+ let(:reference) { get_reference(user) }
+
+ it_behaves_like 'user reference or project reference'
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
@@ -66,45 +74,6 @@ describe Banzai::Filter::UserReferenceFilter do
end
end
- context 'mentioning a user' do
- it_behaves_like 'a reference containing an element node'
-
- it 'links to a User' do
- doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
- end
-
- it 'links to a User with a period' do
- user = create(:user, name: 'alphA.Beta')
-
- doc = reference_filter("Hey #{user.to_reference}")
- expect(doc.css('a').length).to eq 1
- end
-
- it 'links to a User with an underscore' do
- user = create(:user, name: 'ping_pong_king')
-
- doc = reference_filter("Hey #{user.to_reference}")
- expect(doc.css('a').length).to eq 1
- end
-
- it 'links to a User with different case-sensitivity' do
- user = create(:user, username: 'RescueRanger')
-
- doc = reference_filter("Hey #{user.to_reference.upcase}")
- expect(doc.css('a').length).to eq 1
- expect(doc.css('a').text).to eq(user.to_reference)
- end
-
- it 'includes a data-user attribute' do
- doc = reference_filter("Hey #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-user')
- expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
- end
- end
-
context 'mentioning a group' do
it_behaves_like 'a reference containing an element node'
@@ -154,36 +123,6 @@ describe Banzai::Filter::UserReferenceFilter do
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip'
end
- it 'supports an :only_path context' do
- doc = reference_filter("Hey #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
-
- expect(link).not_to match %r(https?://)
- expect(link).to eq urls.user_path(user)
- end
-
- context 'referencing a user in a link href' do
- let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
-
- it 'links to a User' do
- doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
- end
-
- it 'links with adjacent text' do
- doc = reference_filter("Mention me (#{reference}.)")
- expect(doc.to_html).to match(%r{\(<a.+>User</a>\.\)})
- end
-
- it 'includes a data-user attribute' do
- doc = reference_filter("Hey #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-user')
- expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
- end
- end
-
context 'when a project is not specified' do
let(:project) { nil }
@@ -227,7 +166,7 @@ describe Banzai::Filter::UserReferenceFilter do
end
it 'supports mentioning a single user' do
- reference = group_member.to_reference
+ reference = get_reference(group_member)
doc = reference_filter("Hey #{reference}", context)
expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member)
@@ -243,7 +182,7 @@ describe Banzai::Filter::UserReferenceFilter do
describe '#namespaces' do
it 'returns a Hash containing all Namespaces' do
- document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>")
filter = described_class.new(document, project: project)
ns = user.namespace
@@ -253,7 +192,7 @@ describe Banzai::Filter::UserReferenceFilter do
describe '#usernames' do
it 'returns the usernames mentioned in a document' do
- document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
+ document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>")
filter = described_class.new(document, project: project)
expect(filter.usernames).to eq([user.username])
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
new file mode 100644
index 00000000000..e4936aa9e57
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::ProjectParser do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ describe 'when the link has a data-project attribute' do
+ context 'using an existing project ID' do
+ it 'returns an Array of projects' do
+ link['data-project'] = project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([project])
+ end
+ end
+
+ context 'using a non-existing project ID' do
+ it 'returns an empty Array' do
+ link['data-project'] = ''
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+
+ context 'using a private project ID' do
+ it 'returns an empty Array when unauthorized' do
+ private_project = create(:project, :private)
+
+ link['data-project'] = private_project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+
+ it 'returns an Array when authorized' do
+ private_project = create(:project, :private, namespace: user.namespace)
+
+ link['data-project'] = private_project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([private_project])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 32b8755ee9a..42b627b6823 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1103,6 +1103,12 @@ describe Ci::Build do
it { is_expected.to be_cancelable }
end
+
+ context 'when build is created' do
+ let(:build) { create(:ci_build, :created) }
+
+ it { is_expected.to be_cancelable }
+ end
end
context 'when build is not cancelable' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 03beb9187ed..076de06cf99 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -364,6 +364,15 @@ describe Project do
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
end
+ describe '#to_reference_with_postfix' do
+ it 'returns the full path with reference_postfix' do
+ namespace = create(:namespace, path: 'sample-namespace')
+ project = create(:project, path: 'sample-project', namespace: namespace)
+
+ expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>'
+ end
+ end
+
describe '#to_reference' do
let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
diff --git a/spec/serializers/project_mirror_serializer_spec.rb b/spec/serializers/project_mirror_serializer_spec.rb
new file mode 100644
index 00000000000..5e47163532a
--- /dev/null
+++ b/spec/serializers/project_mirror_serializer_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe ProjectMirrorSerializer do
+ it 'represents ProjectMirror entities' do
+ expect(described_class.entity_class).to eq(ProjectMirrorEntity)
+ end
+end
diff --git a/spec/services/ci/enqueue_build_service_spec.rb b/spec/services/ci/enqueue_build_service_spec.rb
new file mode 100644
index 00000000000..e41b8e4800b
--- /dev/null
+++ b/spec/services/ci/enqueue_build_service_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Ci::EnqueueBuildService, '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:ci_build) { create(:ci_build, :created) }
+
+ subject { described_class.new(project, user).execute(ci_build) }
+
+ it 'enqueues the build' do
+ subject
+
+ expect(ci_build.pending?).to be_truthy
+ end
+end
diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb
index eb5da662ab5..476d80f3a93 100644
--- a/spec/support/banzai/reference_filter_shared_examples.rb
+++ b/spec/support/banzai/reference_filter_shared_examples.rb
@@ -11,3 +11,76 @@ shared_examples 'a reference containing an element node' do
expect(doc.children.first.inner_html).to eq(inner_html)
end
end
+
+# Requires a reference, subject and subject_name:
+# subject { create(:user) }
+# let(:reference) { subject.to_reference }
+# let(:subject_name) { 'user' }
+shared_examples 'user reference or project reference' do
+ shared_examples 'it contains a data- attribute' do
+ it 'includes a data- attribute' do
+ doc = reference_filter("Hey #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute("data-#{subject_name}")
+ expect(link.attr("data-#{subject_name}")).to eq subject.id.to_s
+ end
+ end
+
+ context 'mentioning a resource' do
+ it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'it contains a data- attribute'
+
+ it "links to a resource" do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.send("#{subject_name}_url", subject)
+ end
+
+ it 'links to a resource with a period' do
+ subject = create(subject_name.to_sym, name: 'alphA.Beta')
+
+ doc = reference_filter("Hey #{get_reference(subject)}")
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'links to a resource with an underscore' do
+ subject = create(subject_name.to_sym, name: 'ping_pong_king')
+
+ doc = reference_filter("Hey #{get_reference(subject)}")
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'links to a resource with different case-sensitivity' do
+ subject = create(subject_name.to_sym, name: 'RescueRanger')
+ reference = get_reference(subject)
+
+ doc = reference_filter("Hey #{reference.upcase}")
+ expect(doc.css('a').length).to eq 1
+ expect(doc.css('a').text).to eq(reference)
+ end
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Hey #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.send "#{subject_name}_path", subject
+ end
+
+ context 'referencing a resource in a link href' do
+ let(:reference) { %Q{<a href="#{get_reference(subject)}">Some text</a>} }
+
+ it_behaves_like 'it contains a data- attribute'
+
+ it 'links to the resource' do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.send "#{subject_name}_url", subject
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Mention me (#{reference}.)")
+ expect(doc.to_html).to match(%r{\(<a.+>Some text</a>\.\)})
+ end
+ end
+end
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index bbac6ca6a9c..b4164cff922 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -9,7 +9,7 @@ module ConfigurationHelper
end
def relation_class_for_name(relation_name)
- relation_name = Gitlab::ImportExport::RelationFactory::OVERRIDES[relation_name.to_sym] || relation_name
+ relation_name = Gitlab::ImportExport::RelationFactory.overrides[relation_name.to_sym] || relation_name
Gitlab::ImportExport::RelationFactory.relation_class(relation_name)
end