summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.gitlab-ci.yml14
-rw-r--r--CONTRIBUTING.md7
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/boards/components/modal/filters.js57
-rw-r--r--app/assets/javascripts/boards/components/modal/filters/label.js54
-rw-r--r--app/assets/javascripts/boards/components/modal/filters/milestone.js55
-rw-r--r--app/assets/javascripts/boards/components/modal/filters/user.js96
-rw-r--r--app/assets/javascripts/boards/components/modal/header.js16
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js14
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js11
-rw-r--r--app/assets/javascripts/boards/models/list.js21
-rw-r--r--app/assets/javascripts/boards/stores/modal_store.js14
-rw-r--r--app/assets/javascripts/boards/utils/query_data.js21
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/filtered_search/container.js14
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js16
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js25
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js20
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js11
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js26
-rw-r--r--app/assets/javascripts/milestone_select.js10
-rw-r--r--app/assets/javascripts/profile/profile.js1
-rw-r--r--app/assets/javascripts/todos.js67
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js4
-rw-r--r--app/assets/stylesheets/pages/boards.scss9
-rw-r--r--app/assets/stylesheets/pages/commits.scss32
-rw-r--r--app/controllers/dashboard/projects_controller.rb2
-rw-r--r--app/controllers/dashboard/todos_controller.rb10
-rw-r--r--app/controllers/explore/projects_controller.rb14
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb5
-rw-r--r--app/finders/issuable_finder.rb6
-rw-r--r--app/helpers/ci_status_helper.rb18
-rw-r--r--app/helpers/issuables_helper.rb11
-rw-r--r--app/helpers/milestones_helper.rb9
-rw-r--r--app/helpers/projects_helper.rb7
-rw-r--r--app/helpers/sorting_helper.rb11
-rw-r--r--app/helpers/todos_helper.rb3
-rw-r--r--app/models/ci/pipeline.rb6
-rw-r--r--app/models/ci/pipeline_status.rb86
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/issuable.rb33
-rw-r--r--app/models/milestone.rb1
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/todo.rb8
-rw-r--r--app/models/wiki_page.rb6
-rw-r--r--app/services/notification_service.rb11
-rw-r--r--app/services/todo_service.rb8
-rw-r--r--app/views/dashboard/todos/_todo.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml25
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/groups/_settings_head.html.haml14
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/projects.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml6
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml18
-rw-r--r--app/views/profiles/notifications/show.html.haml5
-rw-r--r--app/views/projects/boards/_show.html.haml1
-rw-r--r--app/views/projects/commit/_commit_box.html.haml14
-rw-r--r--app/views/projects/new.html.haml3
-rw-r--r--app/views/projects/wikis/_main_links.html.haml2
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/empty_states/_labels.html.haml2
-rw-r--r--app/views/shared/issuable/_filter.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml41
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml7
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--changelogs/unreleased/27114-add-undo-mark-all-as-done-to-todos.yml4
-rw-r--r--changelogs/unreleased/27174-filter-filters.yml4
-rw-r--r--changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml4
-rw-r--r--changelogs/unreleased/27988-fix-transient-failure-in-commits-api.yml5
-rw-r--r--changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml4
-rw-r--r--changelogs/unreleased/28991-viewing-old-wiki-page-version-edit-button-exists.yml4
-rw-r--r--changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml4
-rw-r--r--changelogs/unreleased/add_quick_submit_for_snippets_form.yml4
-rw-r--r--changelogs/unreleased/better-priority-sorting-2.yml4
-rw-r--r--changelogs/unreleased/better-priority-sorting.yml4
-rw-r--r--changelogs/unreleased/chore-23493-remaining-time-tooltip.yml5
-rw-r--r--changelogs/unreleased/fix_visibility_level.yml4
-rw-r--r--changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml4
-rw-r--r--changelogs/unreleased/option-to-be-notified-of-own-activity.yml4
-rw-r--r--changelogs/unreleased/pipeline-tooltips-overflow.yml4
-rw-r--r--config/application.rb2
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--config/initializers/rspec_profiling.rb2
-rw-r--r--config/karma.config.js2
-rw-r--r--config/routes/dashboard.rb1
-rw-r--r--config/webpack.config.js4
-rw-r--r--db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb14
-rw-r--r--db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb24
-rw-r--r--db/post_migrate/20170306170512_migrate_legacy_manual_actions.rb4
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/monitoring/prometheus/index.md57
-rw-r--r--doc/administration/pages/index.md27
-rw-r--r--doc/administration/pages/source.md82
-rw-r--r--doc/api/issues.md12
-rw-r--r--doc/ci/docker/using_docker_build.md14
-rw-r--r--doc/ci/environments.md38
-rw-r--r--doc/ci/review_apps/index.md2
-rw-r--r--doc/ci/ssh_keys/README.md14
-rw-r--r--doc/ci/variables/README.md52
-rw-r--r--doc/ci/yaml/README.md26
-rw-r--r--doc/development/changelog.md20
-rw-r--r--doc/development/testing.md4
-rw-r--r--doc/install/requirements.md12
-rw-r--r--doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.pngbin0 -> 113092 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_gcp_firewall_rule.pngbin0 -> 15247 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_gcp_node_name.pngbin0 -> 52622 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_service_configuration.pngbin0 -> 18100 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_yaml_deploy.pngbin0 -> 23567 bytes
-rw-r--r--doc/user/project/integrations/project_services.md1
-rw-r--r--doc/user/project/integrations/prometheus.md196
-rw-r--r--doc/user/project/integrations/samples/prometheus.yml69
-rw-r--r--doc/user/project/labels.md13
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md4
-rw-r--r--doc/user/project/repository/web_editor.md7
-rw-r--r--doc/workflow/milestones.md19
-rw-r--r--features/steps/dashboard/todos.rb6
-rw-r--r--features/steps/project/commits/commits.rb2
-rw-r--r--features/steps/project/pages.rb6
-rw-r--r--lib/api/access_requests.rb2
-rw-r--r--lib/api/award_emoji.rb6
-rw-r--r--lib/api/boards.rb2
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/commit_statuses.rb7
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/deploy_keys.rb2
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/environments.rb2
-rw-r--r--lib/api/files.rb2
-rw-r--r--lib/api/groups.rb4
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/jobs.rb2
-rw-r--r--lib/api/labels.rb2
-rw-r--r--lib/api/members.rb2
-rw-r--r--lib/api/merge_request_diffs.rb7
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/milestones.rb2
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/notification_settings.rb9
-rw-r--r--lib/api/pipelines.rb2
-rw-r--r--lib/api/project_hooks.rb2
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/services.rb7
-rw-r--r--lib/api/subscriptions.rb2
-rw-r--r--lib/api/tags.rb2
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/v3/award_emoji.rb2
-rw-r--r--lib/api/v3/boards.rb2
-rw-r--r--lib/api/v3/branches.rb2
-rw-r--r--lib/api/v3/commits.rb2
-rw-r--r--lib/api/v3/deploy_keys.rb2
-rw-r--r--lib/api/v3/deployments.rb2
-rw-r--r--lib/api/v3/environments.rb2
-rw-r--r--lib/api/v3/files.rb2
-rw-r--r--lib/api/v3/groups.rb4
-rw-r--r--lib/api/v3/issues.rb4
-rw-r--r--lib/api/v3/labels.rb2
-rw-r--r--lib/api/v3/members.rb2
-rw-r--r--lib/api/v3/merge_request_diffs.rb7
-rw-r--r--lib/api/v3/merge_requests.rb2
-rw-r--r--lib/api/v3/milestones.rb2
-rw-r--r--lib/api/v3/notes.rb2
-rw-r--r--lib/api/v3/pipelines.rb2
-rw-r--r--lib/api/v3/project_hooks.rb2
-rw-r--r--lib/api/v3/project_snippets.rb2
-rw-r--r--lib/api/v3/projects.rb2
-rw-r--r--lib/api/v3/repositories.rb2
-rw-r--r--lib/api/v3/runners.rb2
-rw-r--r--lib/api/v3/services.rb7
-rw-r--r--lib/api/v3/subscriptions.rb2
-rw-r--r--lib/api/v3/tags.rb2
-rw-r--r--lib/api/v3/todos.rb4
-rw-r--r--lib/api/v3/triggers.rb2
-rw-r--r--lib/api/v3/variables.rb2
-rw-r--r--lib/api/variables.rb2
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb4
-rw-r--r--lib/gitlab/emoji.rb18
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--lib/gitlab/redis.rb8
-rw-r--r--lib/support/init.d/gitlab.default.example4
-rw-r--r--lib/tasks/gitlab/dev.rake2
-rw-r--r--package.json7
-rw-r--r--qa/.gitignore1
-rw-r--r--qa/Dockerfile5
-rw-r--r--qa/Gemfile.lock61
-rw-r--r--spec/config/mail_room_spec.rb61
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb14
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb45
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb6
-rw-r--r--spec/features/boards/modal_filter_spec.rb183
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb26
-rw-r--r--spec/features/issues/award_emoji_spec.rb15
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb11
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb32
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb55
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb9
-rw-r--r--spec/features/projects/wiki/user_views_project_wiki_page_spec.rb44
-rw-r--r--spec/features/todos/todos_spec.rb56
-rw-r--r--spec/finders/issues_finder_spec.rb35
-rw-r--r--spec/helpers/ci_status_helper_spec.rb7
-rw-r--r--spec/helpers/milestones_helper_spec.rb50
-rw-r--r--spec/helpers/projects_helper_spec.rb40
-rw-r--r--spec/helpers/todos_helper_spec.rb23
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js31
-rw-r--r--spec/javascripts/test_bundle.js4
-rw-r--r--spec/lib/expand_variables_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb4
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb6
-rw-r--r--spec/lib/gitlab/redis_spec.rb59
-rw-r--r--spec/models/ci/pipeline_spec.rb13
-rw-r--r--spec/models/ci/pipeline_status_spec.rb173
-rw-r--r--spec/models/commit_spec.rb19
-rw-r--r--spec/models/concerns/issuable_spec.rb40
-rw-r--r--spec/models/concerns/milestoneish_spec.rb26
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/project_spec.rb11
-rw-r--r--spec/requests/api/commits_spec.rb16
-rw-r--r--spec/requests/api/issues_spec.rb10
-rw-r--r--spec/requests/api/runner_spec.rb10
-rw-r--r--spec/requests/api/v3/commits_spec.rb15
-rw-r--r--spec/requests/ci/api/builds_spec.rb10
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/create_deployment_service_spec.rb8
-rw-r--r--spec/services/notification_service_spec.rb59
-rw-r--r--spec/services/todo_service_spec.rb24
-rw-r--r--spec/simplecov_env.rb6
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb2
241 files changed, 2288 insertions, 1132 deletions
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 70cce05d2b5..00000000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-*.js.es6 gitlab-language=javascript
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9aecdde266d..ea273334d4c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -58,7 +58,7 @@ stages:
<<: *dedicated-runner
<<: *use-db
script:
- - JOB_NAME=( $CI_BUILD_NAME )
+ - JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
@@ -78,7 +78,7 @@ stages:
<<: *dedicated-runner
<<: *use-db
script:
- - JOB_NAME=( $CI_BUILD_NAME )
+ - JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
@@ -180,7 +180,7 @@ spinach 9 10: *spinach-knapsack
<<: *dedicated-runner
stage: test
script:
- - bundle exec $CI_BUILD_NAME
+ - bundle exec $CI_JOB_NAME
rubocop:
<<: *ruby-static-analysis
@@ -211,7 +211,7 @@ rake ee_compat_check:
- ee_compat_check/repo/
- vendor/ruby
artifacts:
- name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}"
+ name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_REF}"
when: on_failure
expire_in: 10d
paths:
@@ -330,7 +330,7 @@ migration paths:
- sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- - git checkout $CI_BUILD_REF
+ - git checkout $CI_COMMIT_REF
- source scripts/prepare_build.sh
- bundle exec rake db:migrate
@@ -368,7 +368,7 @@ lint:javascript:report:
stage: post-test
before_script: []
script:
- - find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
+ - find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- yarn run eslint-report || true # ignore exit code
artifacts:
name: eslint-report
@@ -402,7 +402,7 @@ notify:slack:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>"
+ - ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_REF"/pipelines>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ae143c58290..a285e8ab74f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -78,6 +78,13 @@ towards getting your issue resolved.
Issues and merge requests should be in English and contain appropriate language
for audiences of all ages.
+If a contributor is no longer actively working on a submitted merge request
+we can decide that the merge request will be finished by one of our
+[Merge request coaches][team] or close the merge request. We make this decision
+based on how important the change is for our product vision. If a Merge request
+coach is going to finish the merge request we assign the
+~"coach will finish" label.
+
## Helping others
Please help other GitLab users when you can. The channels people will reach out
diff --git a/Gemfile.lock b/Gemfile.lock
index e38f45b8e98..c60c045a4c2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -304,7 +304,7 @@ GEM
multi_json (~> 1.10)
retriable (~> 1.4)
signet (~> 0.6)
- google-protobuf (3.2.0.1)
+ google-protobuf (3.2.0)
googleauth (0.5.1)
faraday (~> 0.9)
jwt (~> 1.4)
diff --git a/app/assets/javascripts/boards/components/modal/filters.js b/app/assets/javascripts/boards/components/modal/filters.js
index 6de06811d94..bd394a2318c 100644
--- a/app/assets/javascripts/boards/components/modal/filters.js
+++ b/app/assets/javascripts/boards/components/modal/filters.js
@@ -1,49 +1,24 @@
-/* global Vue */
-const userFilter = require('./filters/user');
-const milestoneFilter = require('./filters/milestone');
-const labelFilter = require('./filters/label');
+import FilteredSearchBoards from '../../filtered_search_boards';
+import FilteredSearchContainer from '../../../filtered_search/container';
-module.exports = Vue.extend({
+export default {
name: 'modal-filters',
props: {
- projectId: {
- type: Number,
- required: true,
- },
- milestonePath: {
- type: String,
- required: true,
- },
- labelPath: {
- type: String,
+ store: {
+ type: Object,
required: true,
},
},
- destroyed() {
- gl.issueBoards.ModalStore.setDefaultFilter();
+ mounted() {
+ FilteredSearchContainer.container = this.$el;
+
+ this.filteredSearch = new FilteredSearchBoards(this.store);
+ this.filteredSearch.removeTokens();
},
- components: {
- userFilter,
- milestoneFilter,
- labelFilter,
+ beforeDestroy() {
+ this.filteredSearch.cleanup();
+ FilteredSearchContainer.container = document;
+ this.store.path = '';
},
- template: `
- <div class="modal-filters">
- <user-filter
- dropdown-class-name="dropdown-menu-author"
- toggle-class-name="js-user-search js-author-search"
- toggle-label="Author"
- field-name="author_id"
- :project-id="projectId"></user-filter>
- <user-filter
- dropdown-class-name="dropdown-menu-author"
- toggle-class-name="js-assignee-search"
- toggle-label="Assignee"
- field-name="assignee_id"
- :null-user="true"
- :project-id="projectId"></user-filter>
- <milestone-filter :milestone-path="milestonePath"></milestone-filter>
- <label-filter :label-path="labelPath"></label-filter>
- </div>
- `,
-});
+ template: '#js-board-modal-filter',
+};
diff --git a/app/assets/javascripts/boards/components/modal/filters/label.js b/app/assets/javascripts/boards/components/modal/filters/label.js
deleted file mode 100644
index 4fc8f72a145..00000000000
--- a/app/assets/javascripts/boards/components/modal/filters/label.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/* eslint-disable no-new */
-/* global Vue */
-/* global LabelsSelect */
-module.exports = Vue.extend({
- name: 'filter-label',
- props: {
- labelPath: {
- type: String,
- required: true,
- },
- },
- mounted() {
- new LabelsSelect(this.$refs.dropdown);
- },
- template: `
- <div class="dropdown">
- <button
- class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
- type="button"
- data-toggle="dropdown"
- data-show-any="true"
- data-show-no="true"
- :data-labels="labelPath"
- ref="dropdown">
- <span class="dropdown-toggle-text">
- Label
- </span>
- <i class="fa fa-chevron-down"></i>
- </button>
- <div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
- <div class="dropdown-title">
- Filter by label
- <button
- class="dropdown-title-button dropdown-menu-close"
- aria-label="Close"
- type="button">
- <i class="fa fa-times dropdown-menu-close-icon"></i>
- </button>
- </div>
- <div class="dropdown-input">
- <input
- type="search"
- class="dropdown-input-field"
- placeholder="Search"
- autocomplete="off" />
- <i class="fa fa-search dropdown-input-search"></i>
- <i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
- </div>
- <div class="dropdown-content"></div>
- <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
- </div>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/boards/components/modal/filters/milestone.js b/app/assets/javascripts/boards/components/modal/filters/milestone.js
deleted file mode 100644
index d555599d300..00000000000
--- a/app/assets/javascripts/boards/components/modal/filters/milestone.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-disable no-new */
-/* global Vue */
-/* global MilestoneSelect */
-module.exports = Vue.extend({
- name: 'filter-milestone',
- props: {
- milestonePath: {
- type: String,
- required: true,
- },
- },
- mounted() {
- new MilestoneSelect(null, this.$refs.dropdown);
- },
- template: `
- <div class="dropdown">
- <button
- class="dropdown-menu-toggle js-milestone-select"
- type="button"
- data-toggle="dropdown"
- data-show-any="true"
- data-show-upcoming="true"
- data-field-name="milestone_title"
- :data-milestones="milestonePath"
- ref="dropdown">
- <span class="dropdown-toggle-text">
- Milestone
- </span>
- <i class="fa fa-chevron-down"></i>
- </button>
- <div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
- <div class="dropdown-title">
- <span>Filter by milestone</span>
- <button
- class="dropdown-title-button dropdown-menu-close"
- aria-label="Close"
- type="button">
- <i class="fa fa-times dropdown-menu-close-icon"></i>
- </button>
- </div>
- <div class="dropdown-input">
- <input
- type="search"
- class="dropdown-input-field"
- placeholder="Search milestones"
- autocomplete="off" />
- <i class="fa fa-search dropdown-input-search"></i>
- <i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
- </div>
- <div class="dropdown-content"></div>
- <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
- </div>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/boards/components/modal/filters/user.js b/app/assets/javascripts/boards/components/modal/filters/user.js
deleted file mode 100644
index 8523028c29c..00000000000
--- a/app/assets/javascripts/boards/components/modal/filters/user.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/* eslint-disable no-new */
-/* global Vue */
-/* global UsersSelect */
-module.exports = Vue.extend({
- name: 'filter-user',
- props: {
- toggleClassName: {
- type: String,
- required: true,
- },
- dropdownClassName: {
- type: String,
- required: false,
- default: '',
- },
- toggleLabel: {
- type: String,
- required: true,
- },
- fieldName: {
- type: String,
- required: true,
- },
- nullUser: {
- type: Boolean,
- required: false,
- default: false,
- },
- projectId: {
- type: Number,
- required: true,
- },
- },
- mounted() {
- new UsersSelect(null, this.$refs.dropdown);
- },
- computed: {
- currentUsername() {
- return gon.current_username;
- },
- dropdownTitle() {
- return `Filter by ${this.toggleLabel.toLowerCase()}`;
- },
- inputPlaceholder() {
- return `Search ${this.toggleLabel.toLowerCase()}`;
- },
- },
- template: `
- <div class="dropdown">
- <button
- class="dropdown-menu-toggle js-user-search"
- :class="toggleClassName"
- type="button"
- data-toggle="dropdown"
- data-current-user="true"
- :data-any-user="'Any ' + toggleLabel"
- :data-null-user="nullUser"
- :data-field-name="fieldName"
- :data-project-id="projectId"
- :data-first-user="currentUsername"
- ref="dropdown">
- <span class="dropdown-toggle-text">
- {{ toggleLabel }}
- </span>
- <i class="fa fa-chevron-down"></i>
- </button>
- <div
- class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
- :class="dropdownClassName">
- <div class="dropdown-title">
- {{ dropdownTitle }}
- <button
- class="dropdown-title-button dropdown-menu-close"
- aria-label="Close"
- type="button">
- <i class="fa fa-times dropdown-menu-close-icon"></i>
- </button>
- </div>
- <div class="dropdown-input">
- <input
- type="search"
- class="dropdown-input-field"
- autocomplete="off"
- :placeholder="inputPlaceholder" />
- <i class="fa fa-search dropdown-input-search"></i>
- <i
- role="button"
- class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
- </i>
- </div>
- <div class="dropdown-content"></div>
- <div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
- </div>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 70c088f9054..116e29cd177 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -1,6 +1,7 @@
-/* global Vue */
+import Vue from 'vue';
+import modalFilters from './filters';
+
require('./tabs');
-const modalFilters = require('./filters');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
@@ -66,16 +67,7 @@ const modalFilters = require('./filters');
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
- <modal-filters
- :project-id="projectId"
- :milestone-path="milestonePath"
- :label-path="labelPath">
- </modal-filters>
- <input
- placeholder="Search issues..."
- class="form-control"
- type="search"
- v-model="searchTerm" />
+ <modal-filters :store="filter" />
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index f290cd13763..1b66c8b922d 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -1,5 +1,6 @@
/* global Vue */
/* global ListIssue */
+import queryData from '../../utils/query_data';
require('./header');
require('./list');
@@ -47,9 +48,6 @@ require('./empty_state');
page() {
this.loadIssues();
},
- searchTerm() {
- this.searchOperation();
- },
showAddIssuesModal() {
if (this.showAddIssuesModal && !this.issues.length) {
this.loading = true;
@@ -72,19 +70,13 @@ require('./empty_state');
},
},
methods: {
- searchOperation: _.debounce(function searchOperationDebounce() {
- this.loadIssues(true);
- }, 500),
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
- const queryData = Object.assign({}, this.filter, {
- search: this.searchTerm,
+ return gl.boardService.getBacklog(queryData(this.filter.path, {
page: this.page,
per: this.perPage,
- });
-
- return gl.boardService.getBacklog(queryData).then((res) => {
+ })).then((res) => {
const data = res.json();
if (clearIssues) {
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 47448b02bdd..101732309ea 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -1,3 +1,6 @@
+/* eslint-disable class-methods-use-this */
+import FilteredSearchContainer from '../filtered_search/container';
+
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
constructor(store, updateUrl = false) {
super('boards');
@@ -18,13 +21,17 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
}
}
- updateTokens() {
- const tokens = document.querySelectorAll('.js-visual-token');
+ removeTokens() {
+ const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token');
// Remove all the tokens as they will be replaced by the search manager
[].forEach.call(tokens, (el) => {
el.parentNode.removeChild(el);
});
+ }
+
+ updateTokens() {
+ this.removeTokens();
this.loadSearchParamsFromURL();
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 3251ca76b26..f18ad2a0fac 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -1,6 +1,7 @@
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */
/* global ListLabel */
+import queryData from '../utils/query_data';
class List {
constructor (obj) {
@@ -64,25 +65,7 @@ class List {
}
getIssues (emptyIssues = true) {
- const data = gl.issueBoards.BoardsStore.filter.path.split('&').reduce((data, filterParam) => {
- if (filterParam === '') return data;
- const paramSplit = filterParam.split('=');
- const paramKeyNormalized = paramSplit[0].replace('[]', '');
- const isArray = paramSplit[0].indexOf('[]');
- const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
-
- if (isArray !== -1) {
- if (!data[paramKeyNormalized]) {
- data[paramKeyNormalized] = [];
- }
-
- data[paramKeyNormalized].push(value);
- } else {
- data[paramKeyNormalized] = value;
- }
-
- return data;
- }, { page: this.page });
+ const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
if (this.label && data.label_name) {
data.label_name = data.label_name.filter(label => label !== this.label.title);
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
index 15fc6c79e8d..7ee266a831f 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ b/app/assets/javascripts/boards/stores/modal_store.js
@@ -17,17 +17,9 @@
loadingNewPage: false,
page: 1,
perPage: 50,
- };
-
- this.setDefaultFilter();
- }
-
- setDefaultFilter() {
- this.store.filter = {
- author_id: '',
- assignee_id: '',
- milestone_title: '',
- label_name: [],
+ filter: {
+ path: '',
+ },
};
}
diff --git a/app/assets/javascripts/boards/utils/query_data.js b/app/assets/javascripts/boards/utils/query_data.js
new file mode 100644
index 00000000000..2cd3c146f11
--- /dev/null
+++ b/app/assets/javascripts/boards/utils/query_data.js
@@ -0,0 +1,21 @@
+export default (path, extraData) => path.split('&').reduce((dataParam, filterParam) => {
+ if (filterParam === '') return dataParam;
+
+ const data = dataParam;
+ const paramSplit = filterParam.split('=');
+ const paramKeyNormalized = paramSplit[0].replace('[]', '');
+ const isArray = paramSplit[0].indexOf('[]');
+ const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
+
+ if (isArray !== -1) {
+ if (!data[paramKeyNormalized]) {
+ data[paramKeyNormalized] = [];
+ }
+
+ data[paramKeyNormalized].push(value);
+ } else {
+ data[paramKeyNormalized] = value;
+ }
+
+ return data;
+}, extraData);
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index f1c86dd14f1..db1a2848d8d 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -201,10 +201,13 @@ const UserCallout = require('./user_callout');
new gl.Diff();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
+ new MiniPipelineGraph({
+ container: '.js-commit-pipeline-graph',
+ }).bindEvents();
break;
case 'projects:commit:pipelines':
new MiniPipelineGraph({
- container: '.js-pipeline-table',
+ container: '.js-commit-pipeline-graph',
}).bindEvents();
break;
case 'projects:commits:show':
diff --git a/app/assets/javascripts/filtered_search/container.js b/app/assets/javascripts/filtered_search/container.js
new file mode 100644
index 00000000000..2243c4dd2c5
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/container.js
@@ -0,0 +1,14 @@
+/* eslint-disable class-methods-use-this */
+let container = document;
+
+class FilteredSearchContainerClass {
+ set container(containerParam) {
+ container = containerParam;
+ }
+
+ get container() {
+ return container;
+ }
+}
+
+export default new FilteredSearchContainerClass();
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 38ff3fb7158..98dcb697af9 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -45,7 +45,7 @@ require('./filtered_search_dropdown');
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
- gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''));
+ gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
}
this.dismissDropdown();
this.dispatchInputEvent();
@@ -57,13 +57,15 @@ require('./filtered_search_dropdown');
const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
- const { icon, hint, tag } = dropdownMenu.dataset;
+ const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
- dropdownData.push({
- icon: `fa-${icon}`,
- hint,
- tag: `&lt;${tag}&gt;`,
- });
+ dropdownData.push(
+ Object.assign({
+ icon: `fa-${icon}`,
+ hint,
+ tag: `&lt;${tag}&gt;`,
+ }, type && { type }),
+ );
}
});
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index a5a6b56a0d3..432b0c0dfd2 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -1,3 +1,5 @@
+import FilteredSearchContainer from './container';
+
(() => {
class DropdownUtils {
static getEscapedText(text) {
@@ -51,14 +53,18 @@
static filterHint(input, item) {
const updatedItem = item;
- const searchInput = gl.DropdownUtils.getSearchInput(input);
- let { lastToken } = gl.FilteredSearchTokenizer.processTokens(searchInput);
- lastToken = lastToken.key || lastToken || '';
-
- if (!lastToken || searchInput.split('').last() === ' ') {
+ const searchInput = gl.DropdownUtils.getSearchQuery(input);
+ const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
+ const lastKey = lastToken.key || lastToken || '';
+ const allowMultiple = item.type === 'array';
+ const itemInExistingTokens = tokens.some(t => t.key === item.hint);
+
+ if (!allowMultiple && itemInExistingTokens) {
+ updatedItem.droplab_hidden = true;
+ } else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
- } else if (lastToken) {
- const split = lastToken.split(':');
+ } else if (lastKey) {
+ const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
@@ -81,7 +87,8 @@
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
- const tokens = [].slice.call(document.querySelectorAll('.tokens-container li'));
+ const container = FilteredSearchContainer.container;
+ const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = [];
if (untilInput) {
@@ -110,7 +117,7 @@
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
- const input = document.querySelector('.filtered-search');
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
if (isLastVisualTokenValid) {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index d37c812c1f7..5fbe0450bb8 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -1,12 +1,14 @@
/* global DropLab */
+import FilteredSearchContainer from './container';
(() => {
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) {
+ this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
- this.filteredSearchInput = document.querySelector('.filtered-search');
+ this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.setupMapping();
@@ -31,35 +33,35 @@
author: {
reference: null,
gl: 'DropdownUser',
- element: document.querySelector('#js-dropdown-author'),
+ element: this.container.querySelector('#js-dropdown-author'),
},
assignee: {
reference: null,
gl: 'DropdownUser',
- element: document.querySelector('#js-dropdown-assignee'),
+ element: this.container.querySelector('#js-dropdown-assignee'),
},
milestone: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
- element: document.querySelector('#js-dropdown-milestone'),
+ element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
- element: document.querySelector('#js-dropdown-label'),
+ element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
reference: null,
gl: 'DropdownHint',
- element: document.querySelector('#js-dropdown-hint'),
+ element: this.container.querySelector('#js-dropdown-hint'),
},
};
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
- const input = document.querySelector('.filtered-search');
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
input.value = '';
@@ -75,13 +77,13 @@
updateDropdownOffset(key) {
// Always align dropdown with the input field
- let offset = this.filteredSearchInput.getBoundingClientRect().left - document.querySelector('.scroll-container').getBoundingClientRect().left;
+ let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
const maxInputWidth = 240;
const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
// Make sure offset never exceeds the input container
- const offsetMaxWidth = document.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
+ const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) {
offset = offsetMaxWidth;
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index f885932bd91..7ace51748aa 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -1,9 +1,12 @@
+import FilteredSearchContainer from './container';
+
(() => {
class FilteredSearchManager {
constructor(page) {
- this.filteredSearchInput = document.querySelector('.filtered-search');
- this.clearSearchButton = document.querySelector('.clear-search');
- this.tokensContainer = document.querySelector('.tokens-container');
+ this.container = FilteredSearchContainer.container;
+ this.filteredSearchInput = this.container.querySelector('.filtered-search');
+ this.clearSearchButton = this.container.querySelector('.clear-search');
+ this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) {
@@ -132,7 +135,7 @@
}
unselectEditTokens(e) {
- const inputContainer = document.querySelector('.filtered-search-input-container');
+ const inputContainer = this.container.querySelector('.filtered-search-input-container');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementTokensContainer = e.target.classList.contains('tokens-container');
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index e6b53cd4b55..6d5df86f2a5 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -43,6 +43,10 @@
tokenKey: 'milestone',
value: 'upcoming',
}, {
+ url: 'milestone_title=%23started',
+ tokenKey: 'milestone',
+ value: 'started',
+ }, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 320afa26130..e48d7196c7b 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -1,6 +1,8 @@
+import FilteredSearchContainer from './container';
+
class FilteredSearchVisualTokens {
static getLastVisualTokenBeforeInput() {
- const inputLi = document.querySelector('.input-token');
+ const inputLi = FilteredSearchContainer.container.querySelector('.input-token');
const lastVisualToken = inputLi && inputLi.previousElementSibling;
return {
@@ -10,7 +12,7 @@ class FilteredSearchVisualTokens {
}
static unselectTokens() {
- const otherTokens = document.querySelectorAll('.js-visual-token .selectable.selected');
+ const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
@@ -24,7 +26,7 @@ class FilteredSearchVisualTokens {
}
static removeSelectedToken() {
- const selected = document.querySelector('.js-visual-token .selected');
+ const selected = FilteredSearchContainer.container.querySelector('.js-visual-token .selected');
if (selected) {
const li = selected.closest('.js-visual-token');
@@ -54,8 +56,8 @@ class FilteredSearchVisualTokens {
}
li.querySelector('.name').innerText = name;
- const tokensContainer = document.querySelector('.tokens-container');
- const input = document.querySelector('.filtered-search');
+ const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
tokensContainer.insertBefore(li, input.parentElement);
}
@@ -77,14 +79,14 @@ class FilteredSearchVisualTokens {
const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement;
if (isLastVisualTokenValid) {
- addVisualTokenElement(tokenName, tokenValue);
+ addVisualTokenElement(tokenName, tokenValue, false);
} else {
const previousTokenName = lastVisualToken.querySelector('.name').innerText;
- const tokensContainer = document.querySelector('.tokens-container');
+ const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
tokensContainer.removeChild(lastVisualToken);
const value = tokenValue || tokenName;
- addVisualTokenElement(previousTokenName, value);
+ addVisualTokenElement(previousTokenName, value, false);
}
}
@@ -129,7 +131,7 @@ class FilteredSearchVisualTokens {
}
static tokenizeInput() {
- const input = document.querySelector('.filtered-search');
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
@@ -145,7 +147,7 @@ class FilteredSearchVisualTokens {
}
static editToken(token) {
- const input = document.querySelector('.filtered-search');
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
FilteredSearchVisualTokens.tokenizeInput();
@@ -174,9 +176,9 @@ class FilteredSearchVisualTokens {
}
static moveInputToTheRight() {
- const input = document.querySelector('.filtered-search');
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputLi = input.parentElement;
- const tokenContainer = document.querySelector('.tokens-container');
+ const tokenContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
FilteredSearchVisualTokens.tokenizeInput();
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 4c4f94cb9f3..02ff6f5682c 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -19,7 +19,7 @@
}
$els.each(function(i, dropdown) {
- var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
+ var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove;
$dropdown = $(dropdown);
projectId = $dropdown.data('project-id');
milestonesUrl = $dropdown.data('milestones');
@@ -29,6 +29,7 @@
showAny = $dropdown.data('show-any');
showMenuAbove = $dropdown.data('showMenuAbove');
showUpcoming = $dropdown.data('show-upcoming');
+ showStarted = $dropdown.data('show-started');
useId = $dropdown.data('use-id');
defaultLabel = $dropdown.data('default-label');
issuableId = $dropdown.data('issuable-id');
@@ -71,6 +72,13 @@
title: 'Upcoming'
});
}
+ if (showStarted) {
+ extraOptions.push({
+ id: -3,
+ name: '#started',
+ title: 'Started'
+ });
+ }
if (extraOptions.length) {
extraOptions.push('divider');
}
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index 4ccea0624ee..c38bc762675 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -25,7 +25,6 @@
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
- $('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
$('.update-username').on('ajax:complete', this.afterUpdateUsername);
$('.update-notifications').on('ajax:success', this.onUpdateNotifs);
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index caaf6484a34..8be58023c84 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -5,6 +5,7 @@ class Todos {
constructor() {
this.initFilters();
this.bindEvents();
+ this.todo_ids = [];
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
@@ -17,16 +18,16 @@ class Todos {
unbindEvents() {
$('.js-done-todo, .js-undo-todo, .js-add-todo').off('click', this.updateRowStateClickedWrapper);
- $('.js-todos-mark-all').off('click', this.allDoneClickedWrapper);
+ $('.js-todos-mark-all', '.js-todos-undo-all').off('click', this.updateallStateClickedWrapper);
$('.todo').off('click', this.goToTodoUrl);
}
bindEvents() {
this.updateRowStateClickedWrapper = this.updateRowStateClicked.bind(this);
- this.allDoneClickedWrapper = this.allDoneClicked.bind(this);
+ this.updateAllStateClickedWrapper = this.updateAllStateClicked.bind(this);
$('.js-done-todo, .js-undo-todo, .js-add-todo').on('click', this.updateRowStateClickedWrapper);
- $('.js-todos-mark-all').on('click', this.allDoneClickedWrapper);
+ $('.js-todos-mark-all, .js-todos-undo-all').on('click', this.updateAllStateClickedWrapper);
$('.todo').on('click', this.goToTodoUrl);
}
@@ -57,14 +58,14 @@ class Todos {
e.preventDefault();
const target = e.target;
- target.setAttribute('disabled', '');
+ target.setAttribute('disabled', true);
target.classList.add('disabled');
$.ajax({
type: 'POST',
- url: target.getAttribute('href'),
+ url: target.dataset.href,
dataType: 'json',
data: {
- '_method': target.getAttribute('data-method'),
+ '_method': target.dataset.method,
},
success: (data) => {
this.updateRowState(target);
@@ -73,25 +74,6 @@ class Todos {
});
}
- allDoneClicked(e) {
- e.preventDefault();
- const $target = $(e.currentTarget);
- $target.disable();
- $.ajax({
- type: 'POST',
- url: $target.attr('href'),
- dataType: 'json',
- data: {
- '_method': 'delete',
- },
- success: (data) => {
- $target.remove();
- $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
- this.updateBadges(data);
- },
- });
- }
-
updateRowState(target) {
const row = target.closest('li');
const restoreBtn = row.querySelector('.js-undo-todo');
@@ -112,6 +94,41 @@ class Todos {
}
}
+ updateAllStateClicked(e) {
+ e.preventDefault();
+
+ const target = e.currentTarget;
+ const requestData = { '_method': target.dataset.method, ids: this.todo_ids };
+ target.setAttribute('disabled', true);
+ target.classList.add('disabled');
+ $.ajax({
+ type: 'POST',
+ url: target.dataset.href,
+ dataType: 'json',
+ data: requestData,
+ success: (data) => {
+ this.updateAllState(target, data);
+ return this.updateBadges(data);
+ },
+ });
+ }
+
+ updateAllState(target, data) {
+ const markAllDoneBtn = document.querySelector('.js-todos-mark-all');
+ const undoAllBtn = document.querySelector('.js-todos-undo-all');
+ const todoListContainer = document.querySelector('.js-todos-list-container');
+ const nothingHereContainer = document.querySelector('.js-nothing-here-container');
+
+ target.removeAttribute('disabled');
+ target.classList.remove('disabled');
+
+ this.todo_ids = (target === markAllDoneBtn) ? data.updated_ids : [];
+ undoAllBtn.classList.toggle('hidden');
+ markAllDoneBtn.classList.toggle('hidden');
+ todoListContainer.classList.toggle('hidden');
+ nothingHereContainer.classList.toggle('hidden');
+ }
+
updateBadges(data) {
$(document).trigger('todo:toggle', data.count);
document.querySelector('.todos-pending .badge').innerHTML = data.count;
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
index 891f1f17fb3..583d6915a85 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
@@ -47,6 +47,7 @@ const playIconSvg = require('icons/_icon_play.svg');
data-toggle="dropdown"
title="Manual job"
data-placement="top"
+ data-container="body"
aria-label="Manual job">
<span v-html="playIconSvg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
@@ -69,6 +70,7 @@ const playIconSvg = require('icons/_icon_play.svg');
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
title="Artifacts"
data-placement="top"
+ data-container="body"
data-toggle="dropdown"
aria-label="Artifacts">
<i class="fa fa-download" aria-hidden="true"></i>
@@ -92,6 +94,7 @@ const playIconSvg = require('icons/_icon_play.svg');
rel="nofollow"
data-method="post"
data-placement="top"
+ data-container="body"
data-toggle="dropdown"
:href='pipeline.retry_path'
aria-label="Retry">
@@ -105,6 +108,7 @@ const playIconSvg = require('icons/_icon_play.svg');
rel="nofollow"
data-method="post"
data-placement="top"
+ data-container="body"
data-toggle="dropdown"
:href='pipeline.cancel_path'
aria-label="Cancel">
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 9a36d76136b..f9ee33019cd 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -420,12 +420,9 @@
display: -webkit-flex;
display: flex;
- .form-control {
- margin-left: auto;
-
- @media (min-width: $screen-sm-min) {
- max-width: 200px;
- }
+ .issues-filters {
+ -webkit-flex: 1;
+ flex: 1;
}
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 2029b6893ef..da8410eca66 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -38,6 +38,38 @@
}
}
+.pipeline-info {
+ .status-icon-container {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 3px;
+
+ svg {
+ display: block;
+ width: 22px;
+ height: 22px;
+ }
+ }
+
+ .mr-widget-pipeline-graph {
+ display: inline-block;
+ vertical-align: middle;
+ margin: 0 -6px 0 0;
+
+ .dropdown-menu {
+ margin-top: 11px;
+ }
+ }
+}
+
+.branch-info .commit-icon {
+ margin-right: 3px;
+
+ svg {
+ top: 3px;
+ }
+}
+
/*
* Commit message textarea for web editor and
* custom merge request message
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 325ae565537..be00d765f73 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -42,7 +42,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
def load_projects(base_scope)
- projects = base_scope.sorted_by_activity.includes(:namespace)
+ projects = base_scope.sorted_by_activity.includes(:route, namespace: :route)
filter_projects(projects)
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 5848ca62777..498690e8f11 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -22,12 +22,12 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def destroy_all
- TodoService.new.mark_todos_as_done(@todos, current_user)
+ updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { head :ok }
- format.json { render json: todos_counts }
+ format.json { render json: todos_counts.merge(updated_ids: updated_ids) }
end
end
@@ -37,6 +37,12 @@ class Dashboard::TodosController < Dashboard::ApplicationController
render json: todos_counts
end
+ def bulk_restore
+ TodoService.new.mark_todos_as_pending_by_ids(params[:ids], current_user)
+
+ render json: todos_counts
+ end
+
# Used in TodosHelper also
def self.todos_count_format(count)
count >= 100 ? '99+' : count
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 26e17a7553e..6167f9bd335 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -2,7 +2,7 @@ class Explore::ProjectsController < Explore::ApplicationController
include FilterProjects
def index
- @projects = ProjectsFinder.new.execute(current_user)
+ @projects = load_projects
@tags = @projects.tags_on(:tags)
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@@ -21,7 +21,8 @@ class Explore::ProjectsController < Explore::ApplicationController
end
def trending
- @projects = filter_projects(Project.trending)
+ @projects = load_projects(Project.trending)
+ @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
@@ -36,7 +37,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end
def starred
- @projects = ProjectsFinder.new.execute(current_user)
+ @projects = load_projects
@projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page])
@@ -50,4 +51,11 @@ class Explore::ProjectsController < Explore::ApplicationController
end
end
end
+
+ protected
+
+ def load_projects(base_scope = nil)
+ base_scope ||= ProjectsFinder.new.execute(current_user)
+ base_scope.includes(:route, namespace: :route)
+ end
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index a271e2dfc4b..b8b71d295f6 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -17,6 +17,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def user_params
- params.require(:user).permit(:notification_email, :notified_of_own_activity)
+ params.require(:user).permit(:notification_email)
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e4fd53eed29..395a8bffe92 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -267,8 +267,9 @@ class ProjectsController < Projects::ApplicationController
@project_wiki = @project.wiki
@wiki_home = @project_wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
- @issues = issues_collection
- @issues = @issues.page(params[:page])
+ @issues = issues_collection.page(params[:page])
+ @collection_type = 'Issue'
+ @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
end
render :show
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 2fca012252e..f7ebb1807d7 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -310,6 +310,10 @@ class IssuableFinder
params[:milestone_title] == Milestone::Upcoming.name
end
+ def filter_by_started_milestone?
+ params[:milestone_title] == Milestone::Started.name
+ end
+
def by_milestone(items)
if milestones?
if filter_by_no_milestone?
@@ -317,6 +321,8 @@ class IssuableFinder
elsif filter_by_upcoming_milestone?
upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
+ elsif filter_by_started_milestone?
+ items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
else
items = items.with_milestone(params[:milestone_title])
items_projects = projects(items)
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index a7cdca9ba2e..2de9e0de310 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -59,6 +59,24 @@ module CiStatusHelper
custom_icon(icon_name)
end
+ def pipeline_status_cache_key(pipeline_status)
+ "pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}"
+ end
+
+ def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
+ project = pipeline_status.project
+ path = pipelines_namespace_project_commit_path(
+ project.namespace,
+ project,
+ pipeline_status.sha)
+
+ render_status_with_link(
+ 'commit',
+ pipeline_status.status,
+ path,
+ tooltip_placement: tooltip_placement)
+ end
+
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
path = pipelines_namespace_project_commit_path(
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index aad83731b87..a777db2826b 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -90,11 +90,14 @@ module IssuablesHelper
end
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
- if milestone_title == Milestone::Upcoming.name
- milestone_title = Milestone::Upcoming.title
- end
+ title =
+ case milestone_title
+ when Milestone::Upcoming.name then Milestone::Upcoming.title
+ when Milestone::Started.name then Milestone::Started.title
+ else milestone_title.presence
+ end
- h(milestone_title.presence || default_label)
+ h(title || default_label)
end
def to_url_reference(issuable)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 7011e670cee..5053b937c02 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -82,12 +82,13 @@ module MilestonesHelper
def milestone_remaining_days(milestone)
if milestone.expired?
content_tag(:strong, 'Past due')
- elsif milestone.due_date
- days = milestone.remaining_days
- content = content_tag(:strong, days)
- content << " #{'day'.pluralize(days)} remaining"
elsif milestone.upcoming?
content_tag(:strong, 'Upcoming')
+ elsif milestone.due_date
+ time_ago = time_ago_in_words(milestone.due_date)
+ content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" }
+ content.slice!("about ")
+ content << " remaining"
elsif milestone.start_date && milestone.start_date.past?
days = milestone.elapsed_days
content = content_tag(:strong, days)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 4befeacc135..bd0c2cd661e 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -159,6 +159,13 @@ module ProjectsHelper
choose a GitLab CI Yaml template and commit your changes. #{link_to_autodeploy_doc}".html_safe
end
+ def project_list_cache_key(project)
+ key = [project.namespace.cache_key, project.cache_key, controller.controller_name, controller.action_name, current_application_settings.cache_key, 'v2.3']
+ key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?
+
+ key
+ end
+
private
def repo_children_classes(field)
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 18734f1411f..959ee310867 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -16,7 +16,8 @@ module SortingHelper
sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
sort_value_upvotes => sort_title_upvotes,
- sort_value_priority => sort_title_priority
+ sort_value_priority => sort_title_priority,
+ sort_value_label_priority => sort_title_label_priority
}
end
@@ -50,6 +51,10 @@ module SortingHelper
end
def sort_title_priority
+ 'Priority'
+ end
+
+ def sort_title_label_priority
'Label priority'
end
@@ -161,6 +166,10 @@ module SortingHelper
'priority'
end
+ def sort_value_label_priority
+ 'label_priority'
+ end
+
def sort_value_oldest_updated
'updated_asc'
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 7f8efb0a4ac..4f5adf623f2 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -99,8 +99,7 @@ module TodosHelper
end
def todo_projects_options
- projects = current_user.authorized_projects.sorted_by_activity.non_archived
- projects = projects.includes(:namespace)
+ projects = current_user.authorized_projects.sorted_by_activity.non_archived.with_route
projects = projects.map do |project|
{ id: project.id, text: project.name_with_namespace }
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 8a5a9aa4adb..65d08a22b4c 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -22,6 +22,7 @@ module Ci
validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing?
+ after_create :refresh_build_status_cache
state_machine :status, initial: :created do
event :enqueue do
@@ -328,6 +329,7 @@ module Ci
when 'manual' then block
end
end
+ refresh_build_status_cache
end
def predefined_variables
@@ -369,6 +371,10 @@ module Ci
.fabricate!
end
+ def refresh_build_status_cache
+ Ci::PipelineStatus.new(project, sha: sha, status: status).store_in_cache_if_needed
+ end
+
private
def pipeline_data
diff --git a/app/models/ci/pipeline_status.rb b/app/models/ci/pipeline_status.rb
new file mode 100644
index 00000000000..048047d0e34
--- /dev/null
+++ b/app/models/ci/pipeline_status.rb
@@ -0,0 +1,86 @@
+# This class is not backed by a table in the main database.
+# It loads the latest Pipeline for the HEAD of a repository, and caches that
+# in Redis.
+module Ci
+ class PipelineStatus
+ attr_accessor :sha, :status, :project, :loaded
+
+ delegate :commit, to: :project
+
+ def self.load_for_project(project)
+ new(project).tap do |status|
+ status.load_status
+ end
+ end
+
+ def initialize(project, sha: nil, status: nil)
+ @project = project
+ @sha = sha
+ @status = status
+ end
+
+ def has_status?
+ loaded? && sha.present? && status.present?
+ end
+
+ def load_status
+ return if loaded?
+
+ if has_cache?
+ load_from_cache
+ else
+ load_from_commit
+ store_in_cache
+ end
+
+ self.loaded = true
+ end
+
+ def load_from_commit
+ return unless commit
+
+ self.sha = commit.sha
+ self.status = commit.status
+ end
+
+ # We only cache the status for the HEAD commit of a project
+ # This status is rendered in project lists
+ def store_in_cache_if_needed
+ return unless sha
+ return delete_from_cache unless commit
+ store_in_cache if commit.sha == self.sha
+ end
+
+ def load_from_cache
+ Gitlab::Redis.with do |redis|
+ self.sha, self.status = redis.hmget(cache_key, :sha, :status)
+ end
+ end
+
+ def store_in_cache
+ Gitlab::Redis.with do |redis|
+ redis.mapped_hmset(cache_key, { sha: sha, status: status })
+ end
+ end
+
+ def delete_from_cache
+ Gitlab::Redis.with do |redis|
+ redis.del(cache_key)
+ end
+ end
+
+ def has_cache?
+ Gitlab::Redis.with do |redis|
+ redis.exists(cache_key)
+ end
+ end
+
+ def loaded?
+ self.loaded
+ end
+
+ def cache_key
+ "projects/#{project.id}/build_status"
+ end
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 0a18986ef26..6ea5b1ae51f 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -231,6 +231,10 @@ class Commit
project.pipelines.where(sha: sha)
end
+ def latest_pipeline
+ pipelines.last
+ end
+
def status(ref = nil)
@statuses ||= {}
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 3cf4c67d7e7..3b2c6a178e7 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -144,7 +144,8 @@ module Issuable
when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
- when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
+ when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
+ when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
when 'position_asc' then order_position_asc
else
order_by(method)
@@ -154,7 +155,28 @@ module Issuable
sorted.order(id: :desc)
end
- def order_labels_priority(excluded_labels: [])
+ def order_due_date_and_labels_priority(excluded_labels: [])
+ # The order_ methods also modify the query in other ways:
+ #
+ # - For milestones, we add a JOIN.
+ # - For label priority, we change the SELECT, and add a GROUP BY.#
+ #
+ # After doing those, we need to reorder to the order we want. The existing
+ # ORDER BYs won't work because:
+ #
+ # 1. We need milestone due date first.
+ # 2. We can't ORDER BY a column that isn't in the GROUP BY and doesn't
+ # have an aggregate function applied, so we do a useless MIN() instead.
+ #
+ milestones_due_date = 'MIN(milestones.due_date)'
+
+ order_milestone_due_asc.
+ order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date]).
+ reorder(Gitlab::Database.nulls_last_order(milestones_due_date, 'ASC'),
+ Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
+ end
+
+ def order_labels_priority(excluded_labels: [], extra_select_columns: [])
params = {
target_type: name,
target_column: "#{table_name}.id",
@@ -164,7 +186,12 @@ module Issuable
highest_priority = highest_label_priority(params).to_sql
- select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
+ select_columns = [
+ "#{table_name}.*",
+ "(#{highest_priority}) AS highest_priority"
+ ] + extra_select_columns
+
+ select(select_columns.join(', ')).
group(arel_table[:id]).
reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 7331000a9f2..c0deb59ec4c 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -5,6 +5,7 @@ class Milestone < ActiveRecord::Base
None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
Any = MilestoneStruct.new('Any Milestone', '', -1)
Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
+ Started = MilestoneStruct.new('Started', '#started', -3)
include CacheMarkdownField
include InternalId
diff --git a/app/models/project.rb b/app/models/project.rb
index 8c2dadf4659..2ffaaac93f3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1209,6 +1209,10 @@ class Project < ActiveRecord::Base
end
end
+ def pipeline_status
+ @pipeline_status ||= Ci::PipelineStatus.load_for_project(self)
+ end
+
def mark_import_as_failed(error_message)
original_errors = errors.dup
sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 47789a21133..da3fa7277c2 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -48,8 +48,14 @@ class Todo < ActiveRecord::Base
after_save :keep_around_commit
class << self
+ # Priority sorting isn't displayed in the dropdown, because we don't show
+ # milestones, but still show something if the user has a URL with that
+ # selected.
def sort(method)
- method == "priority" ? order_by_labels_priority : order_by(method)
+ case method.to_s
+ when 'priority', 'label_priority' then order_by_labels_priority
+ else order_by(method)
+ end
end
# Order by priority depending on which issue/merge request the Todo belongs to
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 2caebb496db..465c4d903ac 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -149,6 +149,12 @@ class WikiPage
end
# Returns boolean True or False if this instance
+ # is the latest commit version of the page.
+ def latest?
+ !historical?
+ end
+
+ # Returns boolean True or False if this instance
# has been fully saved to disk or not.
def persisted?
@persisted == true
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index d12692ecc90..fdaba9b95fb 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -217,7 +217,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
- recipients.delete(note.author) unless note.author.notified_of_own_activity?
+ recipients.delete(note.author)
recipients = recipients.uniq
notify_method = "note_#{note.to_ability_name}_email".to_sym
@@ -327,9 +327,8 @@ class NotificationService
recipients ||= build_recipients(
pipeline,
pipeline.project,
- pipeline.user,
- action: pipeline.status,
- skip_current_user: false).map(&:notification_email)
+ nil, # The acting user, who won't be added to recipients
+ action: pipeline.status).map(&:notification_email)
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
@@ -628,7 +627,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
+ recipients.delete(current_user) if skip_current_user
recipients.uniq
end
@@ -637,7 +636,7 @@ class NotificationService
recipients = add_labels_subscribers([], project, target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) unless current_user.notified_of_own_activity?
+ recipients.delete(current_user)
recipients.uniq
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 8787a1c93a9..bf7e76ec59e 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -201,10 +201,12 @@ class TodoService
def update_todos_state_by_ids(ids, current_user, state)
todos = current_user.todos.where(id: ids)
- # Only return those that are not really on that state
- marked_todos = todos.where.not(state: state).update_all(state: state)
+ # Only update those that are not really on that state
+ todos = todos.where.not(state: state)
+ todos_ids = todos.pluck(:id)
+ todos.update_all(state: state)
current_user.update_todos_count_cache
- marked_todos
+ todos_ids
end
def create_todos(users, attributes)
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 388190642aa..d0c12aa57ae 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -36,14 +36,14 @@
- if todo.pending?
.todo-actions
- = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading js-done-todo' do
+ = link_to dashboard_todo_path(todo), method: :delete, class: 'btn btn-loading js-done-todo', data: { href: dashboard_todo_path(todo) } do
Done
= icon('spinner spin')
- = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden' do
+ = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden', data: { href: restore_dashboard_todo_path(todo) } do
Undo
= icon('spinner spin')
- else
.todo-actions
- = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-add-todo' do
+ = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-add-todo', data: { href: restore_dashboard_todo_path(todo) } do
Add todo
= icon('spinner spin')
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index d7e0a8e4b2c..d31ced004a0 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -19,9 +19,12 @@
.nav-controls
- if @todos.any?(&:pending?)
- = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
+ = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
Mark all as done
= icon('spinner spin')
+ = link_to bulk_restore_dashboard_todos_path, class: 'btn btn-loading js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do
+ Undo mark all as done
+ = icon('spinner spin')
.todos-filters
.row-content-block.second-block
@@ -57,8 +60,8 @@
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-sort
%li
- = link_to todos_filter_path(sort: sort_value_priority) do
- = sort_title_priority
+ = link_to todos_filter_path(sort: sort_value_label_priority) do
+ = sort_title_label_priority
= link_to todos_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to todos_filter_path(sort: sort_value_oldest_created) do
@@ -67,12 +70,16 @@
.js-todos-all
- if @todos.any?
- .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- .panel.panel-default.panel-small.panel-without-border
- %ul.content-list.todos-list
- = render @todos
- = paginate @todos, theme: "gitlab"
-
+ .js-todos-list-container
+ .js-todos-options{ data: { per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages } }
+ .panel.panel-default.panel-small.panel-without-border
+ %ul.content-list.todos-list
+ = render @todos
+ = paginate @todos, theme: "gitlab"
+ .js-nothing-here-container.todos-all-done.hidden
+ = render "shared/empty_states/icons/todos_all_done.svg"
+ %h4.text-center
+ You're all done!
- elsif current_user.todos.any?
.todos-all-done
= render "shared/empty_states/icons/todos_all_done.svg"
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 8374f5a009f..bb2cd0d44c8 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -11,5 +11,3 @@
= render 'groups'
- else
.nothing-here-block No public groups
-
-= paginate @groups, theme: "gitlab"
diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml
new file mode 100644
index 00000000000..2454e7355a7
--- /dev/null
+++ b/app/views/groups/_settings_head.html.haml
@@ -0,0 +1,14 @@
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: container_class }
+ = nav_link(path: 'groups#edit') do
+ = link_to edit_group_path(@group), title: 'General' do
+ %span
+ General
+
+ = nav_link(path: 'groups#projects') do
+ = link_to projects_group_path(@group), title: 'Projects' do
+ %span
+ Projects
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 2706e8692d1..80a77dab97f 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,3 +1,4 @@
+= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
Group settings
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 2e7e5e5c309..83bdd654f27 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,4 +1,4 @@
-- page_title "Projects"
+= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index a6e96942021..8605380848d 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,4 +1,3 @@
-= render 'layouts/nav/group_settings'
.scrolling-tabs-container{ class: nav_control_class }
.fade-left
= icon('angle-left')
@@ -25,3 +24,8 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
+ - if current_user && can?(current_user, :admin_group, @group)
+ = nav_link(path: %w[groups#projects groups#edit]) do
+ = link_to edit_group_path(@group), title: 'Settings' do
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
deleted file mode 100644
index 30feb6813b4..00000000000
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-- if current_user
- - can_admin_group = can?(current_user, :admin_group, @group)
- - can_edit = can?(current_user, :admin_group, @group)
-
- - if can_admin_group || can_edit
- .controls
- .dropdown.group-settings-dropdown
- %a.dropdown-new.btn.btn-default#group-settings-button{ href: '#', 'data-toggle' => 'dropdown' }
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - if can_admin_group
- = nav_link(path: 'groups#projects') do
- = link_to 'Projects', projects_group_path(@group), title: 'Projects'
- - if can_edit && can_admin_group
- %li.divider
- %li
- = link_to 'Edit Group', edit_group_path(@group)
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 51c4e8e5a73..5c5e5940365 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -34,11 +34,6 @@
.clearfix
- = form_for @user, url: profile_notifications_path, method: :put do |f|
- %label{ for: 'user_notified_of_own_activity' }
- = f.check_box :notified_of_own_activity
- %span Receive notifications about your own activity
-
%hr
%h5
Groups (#{@group_notifications.count})
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index fa463edd526..added3f669b 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -10,6 +10,7 @@
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
+ %script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
= render "projects/issues/head"
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index d001e01609a..a0a292d0508 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -63,15 +63,15 @@
- if @commit.status
.well-segment.pipeline-info
- %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
- = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
+ .status-icon-container{ class: "ci-status-icon-#{@commit.status}" }
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.latest_pipeline.id) do
= ci_icon_for_status(@commit.status)
Pipeline
- = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace"
- for
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
- %span.ci-status-label
- = ci_label_for_status(@commit.status)
+ = link_to "##{@commit.latest_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.latest_pipeline.id), class: "monospace"
+ = ci_label_for_status(@commit.status)
+ - if @commit.latest_pipeline.stages.any?
+ .mr-widget-pipeline-graph
+ = render 'shared/mini_pipeline_graph', pipeline: @commit.latest_pipeline, klass: 'js-commit-pipeline-graph'
in
= time_interval_in_words @commit.pipelines.total_duration
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 2a98bba05ee..d129da943f8 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,6 @@
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
+- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
.project-edit-container
.project-edit-errors
@@ -95,7 +96,7 @@
= f.label :visibility_level, class: 'label-light' do
Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access")
- = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project, with_label: false
+ = render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 763c2fea39b..5211ade1a5f 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -4,6 +4,6 @@
New Page
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page History
- - if can?(current_user, :create_wiki, @project)
+ - if can?(current_user, :create_wiki, @project) && @page.latest?
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
Edit
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 0ce0d759e86..367aa550a78 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -10,6 +10,8 @@
%li
= link_to page_filter_path(sort: sort_value_priority, label: true) do
= sort_title_priority
+ = link_to page_filter_path(sort: sort_value_label_priority, label: true) do
+ = sort_title_label_priority
= link_to page_filter_path(sort: sort_value_recently_created, label: true) do
= sort_title_recently_created
= link_to page_filter_path(sort: sort_value_oldest_created, label: true) do
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index ba5c2dae09d..00fb77bdb3b 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -5,7 +5,7 @@
.col-xs-12.col-sm-6
.text-content
%h4 Labels can be applied to issues and merge requests to categorize them.
- %p You can also star label to make it a priority label.
+ %p You can also star a label to make it a priority label.
- if can?(current_user, :admin_label, @project)
= link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
= link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index f0bad69a989..847a86e2e68 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -24,7 +24,7 @@
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
- = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true
+ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 415361f8fbf..f0d50828e2a 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -6,7 +6,7 @@
- if selected.present? || params[:milestone_title].present?
= hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
- placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
+ placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if project
%ul.dropdown-footer-list
- if can? current_user, :admin_milestone, project
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index f1730b1791c..b58640c3ef0 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -1,7 +1,8 @@
- type = local_assigns.fetch(:type)
+- block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
.issues-filters
- .issues-details-filters.row-content-block.second-block.filtered-search-block
+ .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal }
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
@@ -14,7 +15,7 @@
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
- %input.form-control.filtered-search{ placeholder: 'Search or filter results...', data: { id: 'filtered-search', 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
+ %input.form-control.filtered-search{ placeholder: 'Search or filter results...', data: { id: "filtered-search-#{type.to_s}", 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
= icon('filter')
%button.clear-search.hidden{ type: 'button' }
= icon('times')
@@ -68,12 +69,15 @@
%li.filter-dropdown-item{ data: { value: 'upcoming' } }
%button.btn.btn-link
Upcoming
+ %li.filter-dropdown-item{ 'data-value' => 'started' }
+ %button.btn.btn-link
+ Started
%li.divider
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value
{{title}}
- #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } }
+ #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label', type: 'array' } }
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
@@ -97,7 +101,7 @@
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
#js-add-issues-btn.prepend-left-10
- - else
+ - elsif type != :boards_modal
= render 'shared/sort_dropdown'
- if @bulk_edit
@@ -130,19 +134,20 @@
.filter-item.inline.update-issues-btn
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
-:javascript
- new UsersSelect();
- new LabelsSelect();
- new MilestoneSelect();
- new IssueStatusSelect();
- new SubscriptionSelect();
+- unless type === :boards_modal
+ :javascript
+ new UsersSelect();
+ new LabelsSelect();
+ new MilestoneSelect();
+ new IssueStatusSelect();
+ new SubscriptionSelect();
- $(document).off('page:restore').on('page:restore', function (event) {
- if (gl.FilteredSearchManager) {
- new gl.FilteredSearchManager();
- }
- Issuable.init();
- new gl.IssuableBulkActions({
- prefixId: 'issue_',
+ $(document).off('page:restore').on('page:restore', function (event) {
+ if (gl.FilteredSearchManager) {
+ new gl.FilteredSearchManager();
+ }
+ Issuable.init();
+ new gl.IssuableBulkActions({
+ prefixId: 'issue_',
+ });
});
- });
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index 7a21f19ded4..9dbfedb84f1 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -21,7 +21,7 @@
= form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
+ = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group
- has_labels = @labels && @labels.any?
= form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 7e9fb7bb4d3..df21857e1ad 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -6,17 +6,16 @@
- css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
-- cache_key.push(project.commit&.sha, project.commit&.status)
+- cache_key = project_list_cache_key(project)
%li.project-row{ class: css_class }
= cache(cache_key) do
.controls
- if project.archived
%span.label.label-warning archived
- - if project.commit.try(:status)
+ - if project.pipeline_status.has_status?
%span
- = render_commit_status(project.commit)
+ = render_project_pipeline_status(project.pipeline_status)
- if forks
%span
= icon('code-fork')
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index e7f7db73223..0296597b294 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -3,7 +3,7 @@
= page_specific_javascript_bundle_tag('snippet')
.snippet-form-holder
- = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
+ = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit" } do |f|
= form_errors(@snippet)
.form-group
diff --git a/changelogs/unreleased/27114-add-undo-mark-all-as-done-to-todos.yml b/changelogs/unreleased/27114-add-undo-mark-all-as-done-to-todos.yml
new file mode 100644
index 00000000000..44aae486574
--- /dev/null
+++ b/changelogs/unreleased/27114-add-undo-mark-all-as-done-to-todos.yml
@@ -0,0 +1,4 @@
+---
+title: Add Undo mark all as done to Todos
+merge_request: 9890
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/27174-filter-filters.yml b/changelogs/unreleased/27174-filter-filters.yml
new file mode 100644
index 00000000000..0da1e4d5d3b
--- /dev/null
+++ b/changelogs/unreleased/27174-filter-filters.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent filtering issues by multiple Milestones or Authors
+merge_request:
+author:
diff --git a/changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml b/changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml
new file mode 100644
index 00000000000..a116c68ad87
--- /dev/null
+++ b/changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml
@@ -0,0 +1,4 @@
+---
+title: Speed up project dashboard by caching pipeline status and eager loading routes
+merge_request: 9903
+author:
diff --git a/changelogs/unreleased/27988-fix-transient-failure-in-commits-api.yml b/changelogs/unreleased/27988-fix-transient-failure-in-commits-api.yml
new file mode 100644
index 00000000000..c6ba9572f26
--- /dev/null
+++ b/changelogs/unreleased/27988-fix-transient-failure-in-commits-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'Add `requirements: { id: /.+/ }` for all projects and groups namespaced API
+ routes'
+merge_request: 9944
+author:
diff --git a/changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml b/changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml
new file mode 100644
index 00000000000..67dbc30e760
--- /dev/null
+++ b/changelogs/unreleased/28494-mini-pipeline-graph-commit-view.yml
@@ -0,0 +1,4 @@
+---
+title: Adds pipeline mini-graph to system information box in Commit View
+merge_request:
+author:
diff --git a/changelogs/unreleased/28991-viewing-old-wiki-page-version-edit-button-exists.yml b/changelogs/unreleased/28991-viewing-old-wiki-page-version-edit-button-exists.yml
new file mode 100644
index 00000000000..26989c14958
--- /dev/null
+++ b/changelogs/unreleased/28991-viewing-old-wiki-page-version-edit-button-exists.yml
@@ -0,0 +1,4 @@
+---
+title: When viewing old wiki page version, edit button should be disabled
+merge_request: 9966
+author: TM Lee
diff --git a/changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml b/changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml
new file mode 100644
index 00000000000..d0a04b0a130
--- /dev/null
+++ b/changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml
@@ -0,0 +1,4 @@
+---
+title: Fix archive prefix bug for refs containing dots
+merge_request:
+author:
diff --git a/changelogs/unreleased/add_quick_submit_for_snippets_form.yml b/changelogs/unreleased/add_quick_submit_for_snippets_form.yml
new file mode 100644
index 00000000000..088f1335796
--- /dev/null
+++ b/changelogs/unreleased/add_quick_submit_for_snippets_form.yml
@@ -0,0 +1,4 @@
+---
+title: Add quick submit for snippet forms
+merge_request: 9911
+author: blackst0ne
diff --git a/changelogs/unreleased/better-priority-sorting-2.yml b/changelogs/unreleased/better-priority-sorting-2.yml
new file mode 100644
index 00000000000..ca0d14718dc
--- /dev/null
+++ b/changelogs/unreleased/better-priority-sorting-2.yml
@@ -0,0 +1,4 @@
+---
+title: Allow filtering by all started milestones
+merge_request:
+author:
diff --git a/changelogs/unreleased/better-priority-sorting.yml b/changelogs/unreleased/better-priority-sorting.yml
new file mode 100644
index 00000000000..a44cd090ceb
--- /dev/null
+++ b/changelogs/unreleased/better-priority-sorting.yml
@@ -0,0 +1,4 @@
+---
+title: Allow sorting by due date and priority
+merge_request:
+author:
diff --git a/changelogs/unreleased/chore-23493-remaining-time-tooltip.yml b/changelogs/unreleased/chore-23493-remaining-time-tooltip.yml
new file mode 100644
index 00000000000..dc315ca2367
--- /dev/null
+++ b/changelogs/unreleased/chore-23493-remaining-time-tooltip.yml
@@ -0,0 +1,5 @@
+---
+title: Added remaining_time method to milestoneish, specs and updated the milestone_helper
+ milestone_remaining_days method to correctly return the correct remaining time.
+merge_request:
+author: Michael Robinson
diff --git a/changelogs/unreleased/fix_visibility_level.yml b/changelogs/unreleased/fix_visibility_level.yml
new file mode 100644
index 00000000000..4cf649124ca
--- /dev/null
+++ b/changelogs/unreleased/fix_visibility_level.yml
@@ -0,0 +1,4 @@
+---
+title: Fix visibility level on new project page
+merge_request: 9885
+author: blackst0ne
diff --git a/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml b/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml
new file mode 100644
index 00000000000..aff1bdd957c
--- /dev/null
+++ b/changelogs/unreleased/group-gear-setting-dropdown-to-tab.yml
@@ -0,0 +1,4 @@
+---
+title: Moved the gear settings dropdown to a tab in the groups view
+merge_request:
+author:
diff --git a/changelogs/unreleased/option-to-be-notified-of-own-activity.yml b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml
deleted file mode 100644
index c2e0410cc33..00000000000
--- a/changelogs/unreleased/option-to-be-notified-of-own-activity.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add option to receive email notifications about your own activity
-merge_request: 8836
-author: Richard Macklin
diff --git a/changelogs/unreleased/pipeline-tooltips-overflow.yml b/changelogs/unreleased/pipeline-tooltips-overflow.yml
new file mode 100644
index 00000000000..184da8049f3
--- /dev/null
+++ b/changelogs/unreleased/pipeline-tooltips-overflow.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed pipeline actions tooltips overflowing
+merge_request:
+author:
diff --git a/config/application.rb b/config/application.rb
index 98b2759a8a7..f9f01b66473 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -92,6 +92,8 @@ module Gitlab
# Enable the asset pipeline
config.assets.enabled = true
+ # Support legacy unicode file named img emojis, `1F939.png`
+ config.assets.paths << Gemojione.images_path
config.assets.paths << "vendor/assets/fonts"
config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 954809a882c..ba7f6773985 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -157,8 +157,8 @@ production: &base
host: example.com
port: 80 # Set to 443 if you serve the pages with HTTPS
https: false # Set to true if you serve the pages with HTTPS
- # external_http: "1.1.1.1:80" # If defined, enables custom domain support in GitLab Pages
- # external_https: "1.1.1.1:443" # If defined, enables custom domain and certificate support in GitLab Pages
+ # external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages
+ # external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
## Mattermost
## For enabling Add to Mattermost button
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index d049ae9476f..62020fa9a75 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -278,8 +278,8 @@ Settings.pages['host'] ||= "example.com"
Settings.pages['port'] ||= Settings.pages.https ? 443 : 80
Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http"
Settings.pages['url'] ||= Settings.send(:build_pages_url)
-Settings.pages['external_http'] ||= false if Settings.pages['external_http'].nil?
-Settings.pages['external_https'] ||= false if Settings.pages['external_https'].nil?
+Settings.pages['external_http'] ||= false unless Settings.pages['external_http'].present?
+Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present?
#
# Git LFS
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index ac353d14499..70177995356 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -7,7 +7,7 @@ module RspecProfilingExt
module Git
def branch
- ENV['CI_BUILD_REF_NAME'] || super
+ ENV['CI_COMMIT_REF_NAME'] || super
end
end
diff --git a/config/karma.config.js b/config/karma.config.js
index a23e62f5022..c1d3751d88f 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -36,7 +36,7 @@ module.exports = function(config) {
{ pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw)', included: false },
],
preprocessors: {
- 'spec/javascripts/**/*.js?(.es6)': ['webpack', 'sourcemap'],
+ 'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
},
reporters: [progressReporter, 'coverage-istanbul'],
coverageIstanbulReporter: {
diff --git a/config/routes/dashboard.rb b/config/routes/dashboard.rb
index adc3ad207cc..8e380a0b0ac 100644
--- a/config/routes/dashboard.rb
+++ b/config/routes/dashboard.rb
@@ -13,6 +13,7 @@ resource :dashboard, controller: 'dashboard', only: [] do
resources :todos, only: [:index, :destroy] do
collection do
delete :destroy_all
+ patch :bulk_restore
end
member do
patch :restore
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 8e2b11a4145..cbcc9ac5aea 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -56,7 +56,7 @@ var config = {
module: {
rules: [
{
- test: /\.(js|es6)$/,
+ test: /\.js$/,
exclude: /(node_modules|vendor\/assets)/,
loader: 'babel-loader',
options: {
@@ -130,7 +130,7 @@ var config = {
],
resolve: {
- extensions: ['.js', '.es6', '.js.es6'],
+ extensions: ['.js'],
alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
'emojis': path.join(ROOT_PATH, 'fixtures/emojis'),
diff --git a/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb b/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb
deleted file mode 100644
index f90637e1e35..00000000000
--- a/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class AddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration
- include Gitlab::Database::MigrationHelpers
- disable_ddl_transaction!
-
- DOWNTIME = false
-
- def up
- add_column_with_default :users, :notified_of_own_activity, :boolean, default: false
- end
-
- def down
- remove_column :users, :notified_of_own_activity
- end
-end
diff --git a/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb b/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb
new file mode 100644
index 00000000000..b39c0a3be0f
--- /dev/null
+++ b/db/migrate/20170315174634_revert_add_notified_of_own_activity_to_users.rb
@@ -0,0 +1,24 @@
+class RevertAddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ if our_column_exists?
+ remove_column :users, :notified_of_own_activity
+ end
+ end
+
+ def down
+ unless our_column_exists?
+ add_column_with_default :users, :notified_of_own_activity, :boolean, default: false
+ end
+ end
+
+ private
+
+ def our_column_exists?
+ column_exists?(:users, :notified_of_own_activity)
+ end
+end
diff --git a/db/post_migrate/20170306170512_migrate_legacy_manual_actions.rb b/db/post_migrate/20170306170512_migrate_legacy_manual_actions.rb
index 9020e0d054c..ec6e8cdfc45 100644
--- a/db/post_migrate/20170306170512_migrate_legacy_manual_actions.rb
+++ b/db/post_migrate/20170306170512_migrate_legacy_manual_actions.rb
@@ -4,6 +4,8 @@ class MigrateLegacyManualActions < ActiveRecord::Migration
DOWNTIME = false
def up
+ disable_statement_timeout
+
execute <<-EOS
UPDATE ci_builds SET status = 'manual', allow_failure = true
WHERE ci_builds.when = 'manual' AND ci_builds.status = 'skipped';
@@ -11,6 +13,8 @@ class MigrateLegacyManualActions < ActiveRecord::Migration
end
def down
+ disable_statement_timeout
+
execute <<-EOS
UPDATE ci_builds SET status = 'skipped', allow_failure = false
WHERE ci_builds.when = 'manual' AND ci_builds.status = 'manual';
diff --git a/db/schema.rb b/db/schema.rb
index ca88198079f..3bef910c1d6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170313133418) do
+ActiveRecord::Schema.define(version: 20170315174634) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1318,7 +1318,6 @@ ActiveRecord::Schema.define(version: 20170313133418) do
t.string "incoming_email_token"
t.string "organization"
t.boolean "authorized_projects_populated"
- t.boolean "notified_of_own_activity", default: false, null: false
t.boolean "ghost"
end
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 3a394c561db..2a690dd28d4 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -3,10 +3,9 @@
>**Notes:**
- Prometheus and the various exporters listed in this page are bundled in the
Omnibus GitLab package. Check each exporter's documentation for the timeline
- they got added. For installations from source you will have to install
- them yourself. Over subsequent releases additional GitLab metrics will be
- captured.
-- Prometheus services are off by default but will be on starting with GitLab 9.0.
+ they got added. For installations from source you will have to install them
+ yourself. Over subsequent releases additional GitLab metrics will be captured.
+- Prometheus services are on by default with GitLab 9.0.
- Prometheus and its exporters do not authenticate users, and will be available
to anyone who can access them.
@@ -26,26 +25,25 @@ dashboard tool like [Grafana].
## Configuring Prometheus
>**Note:**
-Available since Omnibus GitLab 8.16. For installations from source you'll
-have to install and configure it yourself.
+For installations from source you'll have to install and configure it yourself.
-To enable Prometheus:
+Prometheus and it's exporters are on by default, starting with GitLab 9.0.
+Prometheus will run as the `gitlab-prometheus` user and listen on
+`http://localhost:9090`. Each exporter will be automatically be set up as a
+monitoring target for Prometheus, unless individually disabled.
+
+To disable Prometheus and all of its exporters, as well as any added in the future:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
```ruby
- prometheus['enable'] = true
+ prometheus['disable_all'] = true
```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
-By default, Prometheus will run as the `gitlab-prometheus` user and listen on
-`http://localhost:9090`. If the [node exporter](#node-exporter) service
-has been enabled, it will automatically be set up as a monitoring target for
-Prometheus.
-
## Changing the port Prometheus listens on
>**Note:**
@@ -71,16 +69,14 @@ To change the address/port that Prometheus listens on:
## Viewing performance metrics
-After you have [enabled Prometheus](#configuring-prometheus), you can visit
-`http://localhost:9090` for the dashboard that Prometheus offers by default.
+You can visit `http://localhost:9090` for the dashboard that Prometheus offers by default.
>**Note:**
If SSL has been enabled on your GitLab instance, you may not be able to access
Prometheus on the same browser as GitLab due to [HSTS][hsts]. We plan to
[provide access via GitLab][multi-user-prometheus], but in the interim there are
some workarounds: using a separate browser for Prometheus, resetting HSTS, or
-having [Nginx proxy it][nginx-custom-config]. Follow issue [#27069] for more
-information.
+having [Nginx proxy it][nginx-custom-config].
The performance data collected by Prometheus can be viewed directly in the
Prometheus console or through a compatible dashboard tool.
@@ -96,6 +92,30 @@ Sample Prometheus queries:
- **Data transmitted:** `irate(node_network_transmit_bytes[5m])`
- **Data received:** `irate(node_network_receive_bytes[5m])`
+## Configuring Prometheus to monitor Kubernetes
+
+> Introduced in GitLab 9.0.
+
+If your GitLab server is running within Kubernetes, an option is now available
+to monitor the health of each node in the cluster. This is particularly helpful
+if your CI/CD environments run in the same cluster, and you would like enable
+[Prometheus integration][] to monitor them.
+
+When enabled, the bundled Prometheus server monitors Kubernetes and automatically
+[collects metrics][prometheus-cadvisor-metrics] from each Node in the cluster.
+
+To enable the Kubernetes monitoring:
+
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line:
+
+ ```ruby
+ prometheus['monitor_kubernetes'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect
+
## Prometheus exporters
There are a number of libraries and servers which help in exporting existing
@@ -143,5 +163,6 @@ The GitLab monitor exporter allows you to measure various GitLab metrics.
[prom-grafana]: https://prometheus.io/docs/visualization/grafana/
[scrape-config]: https://prometheus.io/docs/operating/configuration/#%3Cscrape_config%3E
[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
-[#27069]: https://gitlab.com/gitlab-org/gitlab-ce/issues/27069
[1261]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1261
+[prometheus integration]: ../../../user/project/integrations/prometheus.md
+[rometheus-cadvisor-metrics]: https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 62b0468da79..0c63b0b59a7 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -26,9 +26,9 @@ it works.
---
-In the case of [custom domains](#custom-domains) (but not
-[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on
-ports `80` and/or `443`. For that reason, there is some flexibility in the way
+In the case of [custom domains](#custom-domains) (but not
+[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on
+ports `80` and/or `443`. For that reason, there is some flexibility in the way
which you can set it up:
1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP.
@@ -65,11 +65,13 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this:
```
-*.example.io. 1800 IN A 1.1.1.1
+*.example.io. 1800 IN A 1.1.1.1
+*.example.io. 1800 IN AAAA 2001::1
```
where `example.io` is the domain under which GitLab Pages will be served
-and `1.1.1.1` is the IP address of your GitLab instance.
+and `1.1.1.1` is the IPv4 address of your GitLab instance and `2001::1` is the
+IPv6 address. If you don't have IPv6, you can omit the AAAA record.
> **Note:**
You should not use the GitLab domain to serve user pages. For more information
@@ -141,7 +143,8 @@ outside world.
In addition to the wildcard domains, you can also have the option to configure
GitLab Pages to work with custom domains. Again, there are two options here:
support custom domains with and without TLS certificates. The easiest setup is
-that without TLS certificates.
+that without TLS certificates. In either case, you'll need a secondary IP. If
+you have IPv6 as well as IPv4 addresses, you can use them both.
### Custom domains
@@ -163,11 +166,12 @@ world. Custom domains are supported, but no TLS.
pages_external_url "http://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
pages_nginx['enable'] = false
- gitlab_pages['external_http'] = '1.1.1.2:80'
+ gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80']
```
where `1.1.1.1` is the primary IP address that GitLab is listening to and
- `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
+ `1.1.1.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
+ listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure]
@@ -194,12 +198,13 @@ world. Custom domains and TLS are supported.
pages_nginx['enable'] = false
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
- gitlab_pages['external_http'] = '1.1.1.2:80'
- gitlab_pages['external_https'] = '1.1.1.2:443'
+ gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80']
+ gitlab_pages['external_https'] = ['1.1.1.2:443', '[2001::2]:443']
```
where `1.1.1.1` is the primary IP address that GitLab is listening to and
- `1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
+ `1.1.1.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
+ listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure]
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index f6f50e2c571..a45c3306457 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -1,5 +1,9 @@
# GitLab Pages administration for source installations
+>**Note:**
+Before attempting to enable GitLab Pages, first make sure you have
+[installed GitLab](../../install/installation.md) successfully.
+
This is the documentation for configuring a GitLab Pages when you have installed
GitLab from source and not using the Omnibus packages.
@@ -13,7 +17,33 @@ Pages to the latest supported version.
## Overview
-[Read the Omnibus overview section.](index.md#overview)
+GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server
+written in Go that can listen on an external IP address and provide support for
+custom domains and custom certificates. It supports dynamic certificates through
+SNI and exposes pages using HTTP2 by default.
+You are encouraged to read its [README][pages-readme] to fully understand how
+it works.
+
+---
+
+In the case of [custom domains](#custom-domains) (but not
+[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on
+ports `80` and/or `443`. For that reason, there is some flexibility in the way
+which you can set it up:
+
+1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP.
+1. Run the Pages daemon in a separate server. In that case, the
+ [Pages path](#change-storage-path) must also be present in the server that
+ the Pages daemon is installed, so you will have to share it via network.
+1. Run the Pages daemon in the same server as GitLab, listening on the same IP
+ but on different ports. In that case, you will have to proxy the traffic with
+ a loadbalancer. If you choose that route note that you should use TCP load
+ balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the
+ pages will not be able to be served with user provided certificates. For
+ HTTP it's OK to use HTTP or TCP load balancing.
+
+In this document, we will proceed assuming the first option. If you are not
+supporting custom domains a secondary IP is not needed.
## Prerequisites
@@ -75,7 +105,7 @@ The Pages daemon doesn't listen to the outside world.
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
cd gitlab-pages
- sudo -u git -H git checkout v0.2.4
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
@@ -100,14 +130,21 @@ The Pages daemon doesn't listen to the outside world.
https: false
```
-1. Copy the `gitlab-pages-ssl` Nginx configuration file:
+1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
+ order to enable the pages daemon. In `gitlab_pages_options` the
+ `-pages-domain` must match the `host` setting that you set above.
- ```bash
- sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090
+ ```
+
+1. Copy the `gitlab-pages` Nginx configuration file:
- Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
+ ```
1. Restart NGINX
1. [Restart GitLab][restart]
@@ -131,7 +168,7 @@ outside world.
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
cd gitlab-pages
- sudo -u git -H git checkout v0.2.4
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
@@ -149,6 +186,17 @@ outside world.
https: true
```
+1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
+ order to enable the pages daemon. In `gitlab_pages_options` the
+ `-pages-domain` must match the `host` setting that you set above.
+ The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
+ of the `example.io` domain:
+
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
+ ```
+
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
```bash
@@ -156,12 +204,9 @@ outside world.
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
```
- Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
-
1. Restart NGINX
1. [Restart GitLab][restart]
-
## Advanced configuration
In addition to the wildcard domains, you can also have the option to configure
@@ -189,7 +234,7 @@ world. Custom domains are supported, but no TLS.
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
cd gitlab-pages
- sudo -u git -H git checkout v0.2.4
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
@@ -224,12 +269,10 @@ world. Custom domains are supported, but no TLS.
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
```bash
- sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
+ sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
```
- Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
-
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
listens to.
@@ -257,7 +300,7 @@ world. Custom domains and TLS are supported.
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
cd gitlab-pages
- sudo -u git -H git checkout v0.2.4
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
@@ -300,8 +343,6 @@ world. Custom domains and TLS are supported.
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
```
- Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
-
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
listens to.
@@ -392,5 +433,6 @@ than GitLab to prevent XSS attacks.
[pages-userguide]: ../../user/project/pages/index.md
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
-[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4
+[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.4.0
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example
[shared runners]: ../../ci/runners/README.md
diff --git a/doc/api/issues.md b/doc/api/issues.md
index cb437ffb174..a19c965a8c3 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -23,7 +23,6 @@ GET /issues?state=closed
GET /issues?labels=foo
GET /issues?labels=foo,bar
GET /issues?labels=foo,bar&state=opened
-GET /projects/:id/issues?labels_name=No+Label
GET /issues?milestone=1.0.0
GET /issues?milestone=1.0.0&state=opened
GET /issues?iids[]=42&iids[]=43
@@ -32,8 +31,7 @@ GET /issues?iids[]=42&iids[]=43
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
-| `labels_name` | string | no | Return all issues with the mentioned label. `No+Label` lists all issues with no labels |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string| no | The milestone title |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
@@ -103,7 +101,6 @@ GET /groups/:id/issues?state=closed
GET /groups/:id/issues?labels=foo
GET /groups/:id/issues?labels=foo,bar
GET /groups/:id/issues?labels=foo,bar&state=opened
-GET /projects/:id/issues?labels_name=No+Label
GET /groups/:id/issues?milestone=1.0.0
GET /groups/:id/issues?milestone=1.0.0&state=opened
GET /groups/:id/issues?iids[]=42&iids[]=43
@@ -113,8 +110,7 @@ GET /groups/:id/issues?iids[]=42&iids[]=43
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a group |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
-| `labels_name` | string | no | Return all issues with the mentioned label. `No+Label` lists all issues with no labels |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
@@ -185,7 +181,6 @@ GET /projects/:id/issues?state=closed
GET /projects/:id/issues?labels=foo
GET /projects/:id/issues?labels=foo,bar
GET /projects/:id/issues?labels=foo,bar&state=opened
-GET /projects/:id/issues?labels_name=No+Label
GET /projects/:id/issues?milestone=1.0.0
GET /projects/:id/issues?milestone=1.0.0&state=opened
GET /projects/:id/issues?iids[]=42&iids[]=43
@@ -196,8 +191,7 @@ GET /projects/:id/issues?iids[]=42&iids[]=43
| `id` | integer | yes | The ID of a project |
| `iids` | Array[integer] | no | Return only the milestone having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
-| `labels_name` | string | no | Return all issues with the mentioned label. `No+Label` lists all issues with no labels |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 8620984d40d..b3c9fe275c4 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -298,14 +298,14 @@ could look like:
- docker:dind
stage: build
script:
- - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
- docker build -t registry.example.com/group/project:latest .
- docker push registry.example.com/group/project:latest
```
You have to use the special `gitlab-ci-token` user created for you in order to
push to the Registry connected to your project. Its password is provided in the
-`$CI_BUILD_TOKEN` variable. This allows you to automate building and deployment
+`$CI_JOB_TOKEN` variable. This allows you to automate building and deployment
of your Docker images.
You can also make use of [other variables](../variables/README.md) to avoid hardcoding:
@@ -315,10 +315,10 @@ services:
- docker:dind
variables:
- IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME
+ IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
before_script:
- - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
+ - docker login -u gitlab-ci-token -p $CI_COMMIT_TOKEN $CI_REGISTRY
build:
stage: build
@@ -328,7 +328,7 @@ build:
```
Here, `$CI_REGISTRY_IMAGE` would be resolved to the address of the registry tied
-to this project, and `$CI_BUILD_REF_NAME` would be resolved to the branch or
+to this project, and `$CI_COMMIT_REF_NAME` would be resolved to the branch or
tag name for this particular job. We also declare our own variable, `$IMAGE_TAG`,
combining the two to save us some typing in the `script` section.
@@ -350,11 +350,11 @@ stages:
- deploy
variables:
- CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project:$CI_BUILD_REF_NAME
+ CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project:$CI_COMMIT_REF_NAME
CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project:latest
before_script:
- - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
build:
stage: build
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 3c31ba45d3d..b28f3e13eae 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -263,7 +263,7 @@ This works just like any other terminal - you'll be in the container created
by your deployment, so you can run shell commands and get responses in real
time, check the logs, try out configuration or code tweaks, etc. You can open
multiple terminals to the same environment - they each get their own shell
-session - and even a multiplexer like `screen` or `tmux`!
+session - and even a multiplexer like `screen` or `tmux`!
>**Note:**
Container-based deployments often lack basic tools (like an editor), and may
@@ -295,7 +295,7 @@ deploy_review:
script:
- echo "Deploy a review app"
environment:
- name: review/$CI_BUILD_REF_NAME
+ name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.example.com
only:
- branches
@@ -306,22 +306,22 @@ deploy_review:
Let's break it down in pieces. The job's name is `deploy_review` and it runs
on the `deploy` stage. The `script` at this point is fictional, you'd have to
use your own based on your deployment. Then, we set the `environment` with the
-`environment:name` being `review/$CI_BUILD_REF_NAME`. Now that's an interesting
+`environment:name` being `review/$CI_COMMIT_REF_NAME`. Now that's an interesting
one. Since the [environment name][env-name] can contain slashes (`/`), we can
use this pattern to distinguish between dynamic environments and the regular
ones.
-So, the first part is `review`, followed by a `/` and then `$CI_BUILD_REF_NAME`
-which takes the value of the branch name. Since `$CI_BUILD_REF_NAME` itself may
+So, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME`
+which takes the value of the branch name. Since `$CI_COMMIT_REF_NAME` itself may
also contain `/`, or other characters that would be invalid in a domain name or
URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the
environment can get a specific and distinct URL for each branch. In this case,
-given a `$CI_BUILD_REF_NAME` of `100-Do-The-Thing`, the URL will be something
+given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL will be something
like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
the web server to serve these requests is based on your setup.
-You could also use `$CI_BUILD_REF_SLUG` in `environment:url`, e.g.:
-`https://$CI_BUILD_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG`
+You could also use `$CI_COMMIT_REF_SLUG` in `environment:url`, e.g.:
+`https://$CI_COMMIT_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG`
here because it is guaranteed to be unique, but if you're using a workflow like
[GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer
environment names to be more closely based on the branch name - the example
@@ -356,7 +356,7 @@ deploy_review:
script:
- echo "Deploy a review app"
environment:
- name: review/$CI_BUILD_REF_NAME
+ name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.example.com
only:
- branches
@@ -387,16 +387,16 @@ deploy_prod:
A more realistic example would include copying files to a location where a
webserver (NGINX) could then read and serve. The example below will copy the
-`public` directory to `/srv/nginx/$CI_BUILD_REF_SLUG/public`:
+`public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`:
```yaml
review_app:
stage: deploy
script:
- - rsync -av --delete public /srv/nginx/$CI_BUILD_REF_SLUG
+ - rsync -av --delete public /srv/nginx/$CI_COMMIT_REF_SLUG
environment:
- name: review/$CI_BUILD_REF_NAME
- url: https://$CI_BUILD_REF_SLUG.example.com
+ name: review/$CI_COMMIT_REF_NAME
+ url: https://$CI_COMMIT_REF_SLUG.example.com
```
It is assumed that the user has already setup NGINX and GitLab Runner in the
@@ -526,7 +526,7 @@ deploy_review:
script:
- echo "Deploy a review app"
environment:
- name: review/$CI_BUILD_REF_NAME
+ name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.example.com
on_stop: stop_review
only:
@@ -542,7 +542,7 @@ stop_review:
- echo "Remove review app"
when: manual
environment:
- name: review/$CI_BUILD_REF_NAME
+ name: review/$CI_COMMIT_REF_NAME
action: stop
```
@@ -568,13 +568,13 @@ You can read more in the [`.gitlab-ci.yml` reference][onstop].
As we've seen in the [dynamic environments](#dynamic-environments), you can
prepend their name with a word, then followed by a `/` and finally the branch
-name which is automatically defined by the `CI_BUILD_REF_NAME` variable.
+name which is automatically defined by the `CI_COMMIT_REF_NAME` variable.
In short, environments that are named like `type/foo` are presented under a
group named `type`.
-In our minimal example, we name the environments `review/$CI_BUILD_REF_NAME`
-where `$CI_BUILD_REF_NAME` is the branch name:
+In our minimal example, we name the environments `review/$CI_COMMIT_REF_NAME`
+where `$CI_COMMIT_REF_NAME` is the branch name:
```yaml
deploy_review:
@@ -582,7 +582,7 @@ deploy_review:
script:
- echo "Deploy a review app"
environment:
- name: review/$CI_BUILD_REF_NAME
+ name: review/$CI_COMMIT_REF_NAME
```
In that case, if you visit the Environments page, and provided the branches
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index c679ea4e298..28c484ddbe6 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -80,7 +80,7 @@ The process of adding Review Apps in your workflow would look like:
1. [Install][install-runner] and [configure][conf-runner] a Runner that does
the deployment.
1. Set up a job in `.gitlab-ci.yml` that uses the predefined
- [predefined CI environment variable][variables] `${CI_BUILD_REF_NAME}` to
+ [predefined CI environment variable][variables] `${CI_COMMIT_REF_NAME}` to
create dynamic environments and restrict it to run only on branches.
1. Optionally set a job that [manually stops][manual-env] the Review Apps.
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index d00faaadc8b..befaa06e918 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -38,6 +38,15 @@ following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
and in the **Value** field paste the content of your _private_ key that you
created earlier.
+It is also good practice to check the server's own public key to make sure you
+are not being targeted by a man-in-the-middle attack. To do this, add another
+variable named `SSH_SERVER_HOSTKEYS`. To find out the hostkeys of your server, run
+the `ssh-keyscan YOUR_SERVER` command from a trusted network (ideally, from the
+server itself), and paste its output into the `SSH_SERVER_HOSTKEY` variable. If
+you need to connect to multiple servers, concatenate all the server public keys
+that you collected into the **Value** of the variable. There must be one key per
+line.
+
Next you need to modify your `.gitlab-ci.yml` with a `before_script` action.
Add it to the top:
@@ -59,6 +68,11 @@ before_script:
# you will overwrite your user's SSH config.
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+ # In order to properly check the server's host key, assuming you created the
+ # SSH_SERVER_HOSTKEYS variable previously, uncomment the following two lines
+ # instead.
+ # - mkdir -p ~/.ssh
+ # - '[[ -f /.dockerenv ]] && echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts'
```
As a final step, add the _public_ key from the one you created earlier to the
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 03e6b5303c5..5baff9cc6ba 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -236,18 +236,18 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI=true
++ export CI_DEBUG_TRACE=false
++ CI_DEBUG_TRACE=false
-++ export CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ export CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ export CI_BUILD_REF_NAME=master
-++ CI_BUILD_REF_NAME=master
-++ export CI_BUILD_ID=7046507
-++ CI_BUILD_ID=7046507
-++ export CI_BUILD_REPO=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git
-++ CI_BUILD_REPO=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git
-++ export CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx
-++ CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx
+++ export CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ export CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ export CI_COMMIT_REF_NAME=master
+++ CI_COMMIT_REF_NAME=master
+++ export CI_JOB_ID=7046507
+++ CI_JOB_ID=7046507
+++ export CI_REPOSITORY_URL=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git
+++ CI_REPOSITORY_URL=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git
+++ export CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx
+++ CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx
++ export CI_PROJECT_ID=1796893
++ CI_PROJECT_ID=1796893
++ export CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace
@@ -266,20 +266,20 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI=true
++ export GITLAB_CI=true
++ GITLAB_CI=true
-++ export CI_BUILD_ID=7046507
-++ CI_BUILD_ID=7046507
-++ export CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx
-++ CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx
-++ export CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ export CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
-++ export CI_BUILD_REF_NAME=master
-++ CI_BUILD_REF_NAME=master
-++ export CI_BUILD_NAME=debug_trace
-++ CI_BUILD_NAME=debug_trace
-++ export CI_BUILD_STAGE=test
-++ CI_BUILD_STAGE=test
+++ export CI_JOB_ID=7046507
+++ CI_JOB_ID=7046507
+++ export CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx
+++ CI_JOB_TOKEN=xxxxxxxxxxxxxxxxxxxx
+++ export CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ CI_COMMIT_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ export CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ export CI_COMMIT_REF_NAME=master
+++ CI_COMMIT_REF_NAME=master
+++ export CI_COMMIT_NAME=debug_trace
+++ CI_JOB_NAME=debug_trace
+++ export CI_JOB_STAGE=test
+++ CI_JOB_STAGE=test
++ export CI_SERVER_NAME=GitLab
++ CI_SERVER_NAME=GitLab
++ export CI_SERVER_VERSION=8.14.3-ee
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 49fa8761e5e..ad3ebd144df 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -153,7 +153,7 @@ thus allowing to fine tune them. Variables can be also defined on a
[job level](#job-variables).
Except for the user defined variables, there are also the ones set up by the
-Runner itself. One example would be `CI_BUILD_REF_NAME` which has the value of
+Runner itself. One example would be `CI_COMMIT_REF_NAME` which has the value of
the branch or tag name for which project is built. Apart from the variables
you can set in `.gitlab-ci.yml`, there are also the so called secret variables
which can be set in GitLab's UI.
@@ -252,7 +252,7 @@ To enable per-job caching:
```yaml
cache:
- key: "$CI_BUILD_NAME"
+ key: "$CI_JOB_NAME"
untracked: true
```
@@ -260,7 +260,7 @@ To enable per-branch caching:
```yaml
cache:
- key: "$CI_BUILD_REF_NAME"
+ key: "$CI_COMMIT_REF_NAME"
untracked: true
```
@@ -268,7 +268,7 @@ To enable per-job and per-branch caching:
```yaml
cache:
- key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
+ key: "$CI_JOB_NAME/$CI_COMMIT_REF_NAME"
untracked: true
```
@@ -276,7 +276,7 @@ To enable per-branch and per-stage caching:
```yaml
cache:
- key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
+ key: "$CI_JOB_STAGE/$CI_COMMIT_REF_NAME"
untracked: true
```
@@ -285,7 +285,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace
```yaml
cache:
- key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
+ key: "%CI_JOB_STAGE%/%CI_COMMIT_REF_NAME%"
untracked: true
```
@@ -739,12 +739,12 @@ deploy as review app:
stage: deploy
script: make deploy
environment:
- name: review/$CI_BUILD_REF_NAME
+ name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.example.com/
```
The `deploy as review app` job will be marked as deployment to dynamically
-create the `review/$CI_BUILD_REF_NAME` environment, where `$CI_BUILD_REF_NAME`
+create the `review/$CI_COMMIT_REF_NAME` environment, where `$CI_COMMIT_REF_NAME`
is an [environment variable][variables] set by the Runner. The
`$CI_ENVIRONMENT_SLUG` variable is based on the environment name, but suitable
for inclusion in URLs. In this case, if the `deploy as review app` job was run
@@ -850,7 +850,7 @@ To create an archive with a name of the current job:
```yaml
job:
artifacts:
- name: "$CI_BUILD_NAME"
+ name: "$CI_JOB_NAME"
```
To create an archive with a name of the current branch or tag including only
@@ -859,7 +859,7 @@ the files that are untracked by Git:
```yaml
job:
artifacts:
- name: "$CI_BUILD_REF_NAME"
+ name: "$CI_COMMIT_REF_NAME"
untracked: true
```
@@ -869,7 +869,7 @@ tag including only the files that are untracked by Git:
```yaml
job:
artifacts:
- name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}"
+ name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
untracked: true
```
@@ -878,7 +878,7 @@ To create an archive with a name of the current [stage](#stages) and branch name
```yaml
job:
artifacts:
- name: "${CI_BUILD_STAGE}_${CI_BUILD_REF_NAME}"
+ name: "${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}"
untracked: true
```
@@ -890,7 +890,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace
```yaml
job:
artifacts:
- name: "%CI_BUILD_STAGE%_%CI_BUILD_REF_NAME%"
+ name: "%CI_JOB_STAGE%_%CI_COMMIT_REF_NAME%"
untracked: true
```
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index ff9a4fc4fec..ce39a379a0e 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -51,14 +51,34 @@ making it both concise and descriptive, err on the side of descriptive.
- **Bad:** Go to a project order.
- **Good:** Show a user's starred projects at the top of the "Go to project"
dropdown.
+
+The first example provides no context of where the change was made, or why, or
+how it benefits the user.
+
- **Bad:** Copy [some text] to clipboard.
- **Good:** Update the "Copy to clipboard" tooltip to indicate what's being
copied.
+
+Again, the first example is too vague and provides no context.
+
- **Bad:** Fixes and Improves CSS and HTML problems in mini pipeline graph and
builds dropdown.
- **Good:** Fix tooltips and hover states in mini pipeline graph and builds
dropdown.
+The first example is too focused on implementation details. The user doesn't
+care that we changed CSS and HTML, they care about the _end result_ of those
+changes.
+
+- **Bad:** Strip out `nil`s in the Array of Commit objects returned from
+ `find_commits_by_message_with_elastic`
+- **Good:** Fix 500 errors caused by elasticsearch results referencing
+ garbage-collected commits
+
+The first example focuses on _how_ we fixed something, not on _what_ it fixes.
+The rewritten version clearly describes the _end benefit_ to the user (fewer 500
+errors), and _when_ (searching commits with ElasticSearch).
+
Use your best judgement and try to put yourself in the mindset of someone
reading the compiled changelog. Does this entry add value? Does it offer context
about _where_ and _why_ the change was made?
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 9b545d7f0f1..5ac7b8dadeb 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -35,8 +35,8 @@ GitLab uses [Karma] to run its [Jasmine] JavaScript specs. They can be run on
the command line via `bundle exec karma`.
- JavaScript tests live in `spec/javascripts/`, matching the folder structure of
- `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.es6` has a corresponding
- `spec/javascripts/behaviors/autosize_spec.js.es6` file.
+ `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js` has a corresponding
+ `spec/javascripts/behaviors/autosize_spec.js` file.
- Haml fixtures required for JavaScript tests live in
`spec/javascripts/fixtures`. They should contain the bare minimum amount of
markup necessary for the test.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 3f90597ec80..c2d5be2caeb 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -108,7 +108,7 @@ use the CI features.
## Unicorn Workers
-It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests.
+It's possible to increase the amount of unicorn workers and this will usually help to reduce the response time of the applications and increase the ability to handle parallel requests.
For most instances we recommend using: CPU cores + 1 = unicorn workers.
So for a machine with 2 cores, 3 unicorn workers is ideal.
@@ -148,6 +148,16 @@ Sidekiq processes the background jobs with a multithreaded process.
This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks.
On a very active server (10,000 active users) the Sidekiq process can use 1GB+ of memory.
+## Prometheus and its exporters
+
+As of Omnibus GitLab 9.0, [Prometheus](https://prometheus.io) and its related
+exporters are enabled by default, to enable easy and in depth monitoring of
+GitLab. Approximately 200MB of memory will be consumed by these processes, with
+default settings.
+
+If you would like to disable Prometheus and it's exporters or read more information
+about it, check the [Prometheus documentation](../administration/monitoring/prometheus/index.md).
+
## Supported web browsers
We support the current and the previous major release of Firefox, Chrome/Chromium, Safari and Microsoft browsers (Microsoft Edge and Internet Explorer 11).
diff --git a/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png b/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png
new file mode 100644
index 00000000000..1f5a44f8820
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_environment_detail_with_metrics.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_gcp_firewall_rule.png b/doc/user/project/integrations/img/prometheus_gcp_firewall_rule.png
new file mode 100644
index 00000000000..e30cba211e6
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_gcp_firewall_rule.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_gcp_node_name.png b/doc/user/project/integrations/img/prometheus_gcp_node_name.png
new file mode 100644
index 00000000000..ea289431454
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_gcp_node_name.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_service_configuration.png b/doc/user/project/integrations/img/prometheus_service_configuration.png
new file mode 100644
index 00000000000..c7dfe874817
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_service_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_yaml_deploy.png b/doc/user/project/integrations/img/prometheus_yaml_deploy.png
new file mode 100644
index 00000000000..978cd7eaa50
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_yaml_deploy.png
Binary files differ
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index a3a163a4c6b..3cf1cc704a2 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -51,6 +51,7 @@ Click on the service links to see further configuration instructions and details
| [Slack Notifications](slack.md) | Receive event notifications in Slack |
| [Slack slash commands](slack_slash_commands.md) | Slack chat and ChatOps slash commands |
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
+| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker |
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
new file mode 100644
index 00000000000..676a21e85c4
--- /dev/null
+++ b/doc/user/project/integrations/prometheus.md
@@ -0,0 +1,196 @@
+# Prometheus integration
+
+> [Introduced][ce-8935] in GitLab 9.0.
+
+GitLab offers powerful integration with [Prometheus] for monitoring your apps.
+Metrics are retrieved from the configured Prometheus server, and then displayed
+within the GitLab interface.
+
+Each project can be configured with its own specific Prometheus server, see the
+[configuration](#configuration) section for more details. If you have a single
+Prometheus server which monitors all of your infrastructure, you can pre-fill
+the settings page with a default template. To configure the template, see the
+[Services templates](services_templates.md) document.
+
+## Requirements
+
+Integration with Prometheus requires the following:
+
+1. GitLab 9.0 or higher
+1. Your app must be deployed on [Kubernetes][]
+1. Prometheus must be configured to collect Kubernetes metrics
+1. Each metric must be have a label to indicate the environment
+1. GitLab must have network connectivity to the Prometheus sever
+
+There are a few steps necessary to set up integration between Prometheus and
+GitLab.
+
+## Configuring Prometheus to collect Kubernetes metrics
+
+In order for Prometheus to collect Kubernetes metrics, you first must have a
+Prometheus server up and running. You have two options here:
+
+- If you installed Omnibus GitLab inside of Kubernetes, you can simply use the
+ [bundled version of Prometheus][promgldocs]. In that case, follow the info in the
+ [Omnibus GitLab section](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes)
+ below.
+- If you are using GitLab.com or installed GitLab outside of Kubernetes, you
+ will likely need to run a Prometheus server within the Kubernetes cluster.
+ Once installed, the easiest way to monitor Kubernetes is to simply use
+ Prometheus' support for [Kubernetes Service Discovery][prometheus-k8s-sd].
+ In that case, follow the instructions on
+ [configuring your own Prometheus server within Kubernetes](#configuring-your-own-prometheus-server-within-kubernetes).
+
+### Configuring Omnibus GitLab Prometheus to monitor Kubernetes
+
+With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled
+version of Prometheus to collect the required metrics.
+
+1. Read how to configure the bundled Prometheus server in the
+ [Administration guide][gitlab-prometheus-k8s-monitor].
+1. Now that Prometheus is configured, proceed on
+ [configuring the Prometheus project service in GitLab](#configuration-in-gitlab).
+
+### Configuring your own Prometheus server within Kubernetes
+
+Setting up and configuring Prometheus within Kubernetes is quick and painless.
+The Prometheus project provides an [official Docker image][prometheus-docker-image]
+which we can use as a starting point.
+
+To get started quickly, we have provided a [sample YML file][prometheus-yml]
+that can be used as a template. This file will create a `prometheus` **Namespace**,
+**Service**, **Deployment**, and **ConfigMap** in Kubernetes. You can upload
+this file to the Kubernetes dashboard using **+ Create** at the top right.
+
+![Deploy Prometheus](img/prometheus_yaml_deploy.png)
+
+Or use `kubectl`:
+
+```bash
+kubectl apply -f path/to/prometheus.yml
+```
+
+Once deployed, you should see the Prometheus service, deployment, and
+pod start within the `prometheus` namespace. The server will begin to collect
+metrics from each Kubernetes Node in the cluster, based on the configuration
+provided in the template.
+
+Since GitLab is not running within Kubernetes, the template provides external
+network access via a `NodePort` running on `30090`. This method allows access
+to be controlled using provider firewall rules, like within Google Compute Engine.
+
+Since a `NodePort` does not automatically have firewall rules created for it,
+one will need to be created manually to allow access. In GCP/GKE, you will want
+to confirm the Node that the Prometheus pod is running on. This can be done
+either by looking at the Pod in the Kubernetes dashboard, or by running:
+
+```bash
+kubectl describe pods -n prometheus
+```
+
+Next on GKE, we need to get the `tag` of the Node or VM Instance, so we can
+create an accurate firewall rule. The easiest way to do this is to go into the
+Google Cloud Platform Compute console and select the VM instance that matches
+the name of the Node gathered from the step above. In this case, the node tag
+needed is `gke-prometheus-demo-5d5ada10-node`. Also make a note of the
+**External IP**, which will be the IP address the Prometheus server is reachable
+on.
+
+![GCP Node Detail](img/prometheus_gcp_node_name.png)
+
+Armed with the proper Node tag, the firewall rule can now be created
+specifically for this node. To create the firewall rule, open the Google Cloud
+Platform Networking console, and select **Firewall Rules**.
+
+Create a new rule:
+
+- Specify the source IP range to match your desired access list, which should
+ include your GitLab server. A sample of GitLab.com's IP address range is
+ available [in this issue][gitlab.com-ip-range], but note that GitLab.com's IPs
+ are subject to change without prior notification.
+- Allowed protocol and port should be `tcp:30090`.
+- The target tags should match the Node tag identified earlier in this step.
+
+![GCP Firewall Rule](img/prometheus_gcp_firewall_rule.png)
+
+---
+
+Now that Prometheus is configured, proceed to
+[configure the Prometheus project service in GitLab](##configuration-in-gitlab).
+
+## Configuration in GitLab
+
+The actual configuration of Prometheus integration within GitLab is very simple.
+All you will need is the DNS or IP address of the Prometheus server you'd like
+to integrate with.
+
+1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
+1. Click the **Prometheus** service
+1. Provide the base URL of the your server, for example `http://prometheus.example.com/`.
+ The **Test Settings** button can be used to confirm connectivity from GitLab
+ to the Prometheus server.
+
+![Configure Prometheus Service](img/prometheus_service_configuration.png)
+
+## Metrics and Labels
+
+GitLab retrieves performance data from two metrics, `container_cpu_usage_seconds_total`
+and `container_memory_usage_bytes`. These metrics are collected from the
+Kubernetes pods via Prometheus, and report CPU and Memory utilization of each
+container or Pod running in the cluster.
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which pods are associated. To do that,
+GitLab will specifically request metrics that have an `environment` tag that
+matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug].
+
+If you are using [GitLab Auto-Deploy][autodeploy] and one of the methods of
+configuring Prometheus above, the `environment` will be automatically added.
+
+### GitLab Prometheus queries
+
+The queries utilized by GitLab are shown in the following table.
+
+| Metric | Query |
+| ------ | ----- |
+| Average Memory (MB) | `(sum(container_memory_usage_bytes{container_name="app",environment="$CI_ENVIRONMENT_SLUG"}) / count(container_memory_usage_bytes{container_name="app",environment="$CI_ENVIRONMENT_SLUG"})) /1024/1024` |
+| Average CPU Utilization (%) | `sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="$CI_ENVIRONMENT_SLUG"}[2m])) / count(container_cpu_usage_seconds_total{container_name="app",environment="$CI_ENVIRONMENT_SLUG"}) * 100` |
+
+## Monitoring CI/CD Environments
+
+Once configured, GitLab will attempt to retrieve performance metrics for any
+environment which has had a successful deployment. If monitoring data was
+successfully retrieved, a metrics button will appear on the environment's
+detail page.
+
+![Environment Detail with Metrics](img/prometheus_environment_detail_with_metrics.png)
+
+Clicking on the metrics button will display a new page, showing up to the last
+8 hours of performance data. It may take a minute or two for data to appear
+after initial deployment.
+
+## Troubleshooting
+
+If the metrics button is not appearing, then one of a few issues may be
+occurring:
+
+- GitLab is not able to reach the Prometheus server. A test request can be sent
+ to the Prometheus server from the [Prometheus Service](#configuration-in-gitlab)
+ configuration screen.
+- No successful deployments have occurred to this environment.
+- Prometheus does not have performance data for this environment, or the metrics
+ are not labeled correctly. To test this, connect to the Prometheus server and
+ [run a query](#gitlab-prometheus-queries), replacing `$CI_ENVIRONMENT_SLUG`
+ with the name of your environment.
+
+[autodeploy]: ../../../ci/autodeploy/index.md
+[kubernetes]: https://kubernetes.io
+[prometheus-k8s-sd]: https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>
+[prometheus]: https://prometheus.io
+[gitlab-prometheus-k8s-monitor]: ../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes
+[prometheus-docker-image]: https://hub.docker.com/r/prom/prometheus/
+[prometheus-yml]:samples/prometheus.yml
+[gitlab.com-ip-range]: https://gitlab.com/gitlab-com/infrastructure/issues/434
+[ci-environment-slug]: https://docs.gitlab.com/ce/ci/variables/#predefined-variables-environment-variables
+[ce-8935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935
+[promgldocs]: ../../../administration/monitoring/prometheus/index.md
diff --git a/doc/user/project/integrations/samples/prometheus.yml b/doc/user/project/integrations/samples/prometheus.yml
new file mode 100644
index 00000000000..01bbcaffe1e
--- /dev/null
+++ b/doc/user/project/integrations/samples/prometheus.yml
@@ -0,0 +1,69 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: prometheus
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: prometheus
+ namespace: prometheus
+data:
+ prometheus.yml: |-
+ scrape_configs:
+ - job_name: 'kubernetes-nodes'
+ scheme: https
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ insecure_skip_verify: true
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ kubernetes_sd_configs:
+ - role: node
+ metric_relabel_configs:
+ - source_labels: [pod_name]
+ target_label: environment
+ regex: (.+)-.+-.+
+ replacement: $1
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: prometheus
+ namespace: prometheus
+spec:
+ selector:
+ app: prometheus
+ ports:
+ - name: prometheus
+ protocol: TCP
+ port: 9090
+ nodePort: 30090
+ type: NodePort
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: prometheus
+ namespace: prometheus
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: prometheus
+ spec:
+ containers:
+ - name: prometheus
+ image: prom/prometheus:latest
+ args:
+ - '-config.file=/prometheus-data/prometheus.yml'
+ ports:
+ - name: prometheus
+ containerPort: 9090
+ volumeMounts:
+ - name: data-volume
+ mountPath: /prometheus-data
+ volumes:
+ - name: data-volume
+ configMap:
+ name: prometheus
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index cf1d9cbe69c..8ec7adad172 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -65,7 +65,7 @@ issues and merge requests assigned to each label.
> https://gitlab.com/gitlab-org/gitlab-ce/issues/18554.
Prioritized labels are like any other label, but sorted by priority. This allows
-you to sort issues and merge requests by priority.
+you to sort issues and merge requests by label priority.
To prioritize labels, navigate to your project's **Issues > Labels** and click
on the star icon next to them to put them in the priority list. Click on the
@@ -77,9 +77,13 @@ having their priority set to null.
![Prioritize labels](img/labels_prioritize.png)
-Now that you have labels prioritized, you can use the 'Priority' filter in the
-issues or merge requests tracker. Those with the highest priority label, will
-appear on top.
+Now that you have labels prioritized, you can use the 'Priority' and 'Label
+priority' filters in the issues or merge requests tracker.
+
+The 'Label priority' filter puts issues with the highest priority label on top.
+
+The 'Priority' filter sorts issues by their soonest milestone due date, then by
+label priority.
![Filter labels by priority](img/labels_filter_by_priority.png)
@@ -156,4 +160,3 @@ mouse over the label in the issue tracker or wherever else the label is
rendered.
![Label tooltips](img/labels_description_tooltip.png)
-
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 5f631f63050..b559d132590 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -119,7 +119,7 @@ And then the users could also use it in their CI jobs all Docker related
commands to interact with GitLab Container Registry. For example:
```
-docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
+docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
```
Using single token had multiple security implications:
@@ -208,7 +208,7 @@ This is how an example usage can look like:
```
test:
script:
- - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker pull $CI_REGISTRY/group/other-project:latest
- docker run $CI_REGISTRY/group/other-project:latest
```
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index c415d566a7c..d47a3acdbe9 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -109,12 +109,19 @@ the title of the issue and as suffix it will have its ID. Thus, the example
screenshot above will yield a branch named
`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`.
+Since GitLab 9.0, when you click the `New branch` in an empty repository project, GitLab automatically creates the master branch, commits a blank `README.md` file to it and creates and redirects you to a new branch based on the issue title.
+If your [project is already configured with a deployment service][project-services-doc] (e.g. Kubernetes), GitLab takes one step further and prompts you to set up [auto deploy][auto-deploy-doc] by helping you create a `.gitlab-ci.yml` file.
+
+
After the branch is created, you can edit files in the repository to fix
the issue. When a merge request is created based on the newly created branch,
the description field will automatically display the [issue closing pattern]
`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the
merge request is merged.
+[project-services-doc]: ../integrations/project_services.md
+[auto-deploy-doc]: ../../../ci/autodeploy/index.md
+
### Create a new branch from a project's dashboard
If you want to make changes to several files before creating a new merge
diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md
index dff36899aec..37afe553e55 100644
--- a/doc/workflow/milestones.md
+++ b/doc/workflow/milestones.md
@@ -1,13 +1,28 @@
# Milestones
-Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date.
+Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date.
A common use is keeping track of an upcoming software version. Milestones are created per-project.
![milestone form](milestones/form.png)
## Groups and milestones
-You can create a milestone for several projects in the same group simultaneously.
+You can create a milestone for several projects in the same group simultaneously.
On the group's milestones page, you will be able to see the status of that milestone across all of the selected projects.
![group milestone form](milestones/group_form.png)
+
+## Special milestone filters
+
+In addition to the milestones that exist in the project or group, there are some
+special options available when filtering by milestone:
+
+* **No Milestone** - only show issues or merge requests without a milestone.
+* **Upcoming** - show issues or merge request that belong to the next open
+ milestone with a due date, by project. (For example: if project A has
+ milestone v1 due in three days, and project B has milestone v2 due in a week,
+ then this will show issues or merge requests from milestone v1 in project A
+ and milestone v2 in project B.)
+* **Started** - show issues or merge requests from any milestone with a start
+ date less than today. Note that this can return results from several
+ milestones in the same project.
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index eb906a55a83..9f01dff776f 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -159,7 +159,11 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end
def should_not_see_todo(title)
- expect(page).not_to have_content title
+ expect(page).not_to have_visible_content title
+ end
+
+ def have_visible_content(text)
+ have_css('*', text: text, visible: true)
end
def john_doe
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 18e267294e4..cf75fac8ac6 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I see commit ci info' do
- expect(page).to have_content "Pipeline #1 for 570e7b2a pending"
+ expect(page).to have_content "Pipeline #1 pending"
end
step 'I search "submodules" commits' do
diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb
index c80c6273807..4045955a8b9 100644
--- a/features/steps/project/pages.rb
+++ b/features/steps/project/pages.rb
@@ -53,13 +53,13 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
end
step 'pages are exposed on external HTTP address' do
- allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80')
+ allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80'])
allow(Gitlab.config.pages).to receive(:external_https).and_return(nil)
end
step 'pages are exposed on external HTTPS address' do
- allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80')
- allow(Gitlab.config.pages).to receive(:external_https).and_return('1.1.1.1:443')
+ allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80'])
+ allow(Gitlab.config.pages).to receive(:external_https).and_return(['1.1.1.1:443'])
end
step 'I should be able to add a New Domain' do
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index 789f45489eb..a5c9f0b509c 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
- resource source_type.pluralize do
+ resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
desc "Gets a list of access requests for a #{source_type}." do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::AccessRequester
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index f9e0c2c4e16..56f19f89642 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -9,13 +9,15 @@ module API
{ type: 'snippet', find_by: :id }
].freeze
- resource :projects do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
AWARDABLES.each do |awardable_params|
awardable_string = awardable_params[:type].pluralize
awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}"
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index b6843c1b6af..5a2d7a681e3 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all project boards' do
detail 'This feature was introduced in 8.13'
success Entities::Board
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 73a7e939627..2cc64fc6712 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository branches' do
success Entities::RepoBranch
end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 9d9f82fdb83..827a38d33da 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -2,7 +2,10 @@ require 'mime/types'
module API
class CommitStatuses < Grape::API
- resource :projects do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
include PaginationParams
before { authenticate! }
@@ -11,7 +14,6 @@ module API
success Entities::CommitStatus
end
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :sha, type: String, desc: 'The commit hash'
optional :ref, type: String, desc: 'The ref'
optional :stage, type: String, desc: 'The stage'
@@ -37,7 +39,6 @@ module API
success Entities::CommitStatus
end
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :sha, type: String, desc: 'The commit hash'
requires :state, type: String, desc: 'The state of the status',
values: %w(pending running success failed canceled)
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 42401abfe0f..66b37fd2bcc 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository commits' do
success Entities::RepoCommit
end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 69e85c27a65..b888ede6fe8 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -17,7 +17,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of the project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
before { authorize_admin_project }
desc "Get a specific project's deploy keys" do
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index 2f1ad12c38c..46b936897f6 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -8,7 +8,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all deployments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Deployment
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index ebe8c3a5b2c..945771d46f3 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all environments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
diff --git a/lib/api/files.rb b/lib/api/files.rb
index bb8f5c3076d..33fc970dc09 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -52,7 +52,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get raw file contents from the repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index b862ff70b31..8f3799417e3 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -84,7 +84,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups do
+ resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Update a group. Available only for users who can administrate groups.' do
success Entities::Group
end
@@ -154,7 +154,7 @@ module API
params do
requires :project_id, type: String, desc: 'The ID or path of the project'
end
- post ":id/projects/:project_id" do
+ post ":id/projects/:project_id", requirements: { project_id: /.+/ } do
authenticated_as_admin!
group = find_group!(params[:id])
project = find_project!(params[:project_id])
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 1abe8639445..b3183357625 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -58,7 +58,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups do
+ resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Get a list of group issues' do
success Entities::IssueBasic
end
@@ -79,7 +79,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 44118522abe..ffab0aafe59 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
helpers do
params :optional_scope do
optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 59f0e7cb647..d9a3cb7bb6b 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all labels of the project' do
success Entities::Label
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index baf85e6075a..c200e46a328 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
- resource source_type.pluralize do
+ resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
desc 'Gets a list of group or project members viewable by the authenticated user.' do
success Entities::Member
end
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index a59e39cca26..4b79eac2b8b 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -5,14 +5,16 @@ module API
before { authenticate! }
- resource :projects do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a list of merge request diff versions' do
detail 'This feature was introduced in GitLab 8.12.'
success Entities::MergeRequestDiff
end
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
use :pagination
end
@@ -28,7 +30,6 @@ module API
end
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 7a03955a045..5cc807d5bff 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
helpers do
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index abd263c1dfc..e7ab82f08db 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -23,7 +23,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a list of project milestones' do
success Entities::Milestone
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 3b3e45cbd06..29ceffdbd2d 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
NOTEABLE_TYPES.each do |noteable_type|
noteables_str = noteable_type.to_s.underscore.pluralize
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index c5e9b3ad69b..992ea5dc24d 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -48,14 +48,14 @@ module API
end
%w[group project].each do |source_type|
- resource source_type.pluralize do
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ end
+ resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
desc "Get #{source_type} level notification level settings, defaults to Global" do
detail 'This feature was introduced in GitLab 8.12'
success Entities::NotificationSetting
end
- params do
- requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME'
- end
get ":id/notification_settings" do
source = find_source(source_type, params[:id])
@@ -69,7 +69,6 @@ module API
success Entities::NotificationSetting
end
params do
- requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME'
optional :level, type: String, desc: "The #{source_type} notification level"
NotificationSetting::EMAIL_EVENTS.each do |event|
optional event, type: Boolean, desc: 'Enable/disable this notification'
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 0721b975ba4..754c3d85a04 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all Pipelines of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::PipelineBasic
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 57a5f97dc7f..53791166c33 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -24,7 +24,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get project hooks' do
success Entities::ProjectHook
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index f57e7ea4032..cfee38a9baf 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 63a4cdd5954..0fbe1669d45 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -142,7 +142,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: /[^\/]+/ } do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a single project' do
success Entities::ProjectWithAccess
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 531ef5a63ea..8f16e532ecb 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 2e41f16f8c6..a77c876a749 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -86,7 +86,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
before { authorize_admin_project }
desc 'Get runners available for project' do
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 5aa2f5eba7b..be614bb8dc0 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -604,7 +604,10 @@ module API
]
}.freeze
- resource :projects do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
before { authenticate! }
before { authorize_admin_project }
@@ -692,7 +695,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc "Trigger a slash command for #{service_slug}" do
detail 'Added in GitLab 8.13'
end
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 772b5cca017..dbe54d3cd31 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -12,7 +12,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
requires :subscribable_id, type: String, desc: 'The ID of a resource'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
subscribable_types.each do |type, finder|
type_singularized = type.singularize
entity_class = Entities.const_get(type_singularized.camelcase)
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index d31ef9de26b..c7b1efe0bfa 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository tags' do
success Entities::RepoTag
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index d9b8837a5bb..d1f7e364029 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -12,7 +12,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_iid".to_sym
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 119e9024712..aa3c9a06ed5 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -5,7 +5,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Trigger a GitLab project pipeline' do
success Entities::Pipeline
end
diff --git a/lib/api/v3/award_emoji.rb b/lib/api/v3/award_emoji.rb
index cf9e1551f60..b96b2d70b12 100644
--- a/lib/api/v3/award_emoji.rb
+++ b/lib/api/v3/award_emoji.rb
@@ -6,7 +6,7 @@ module API
before { authenticate! }
AWARDABLES = %w[issue merge_request snippet].freeze
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
AWARDABLES.each do |awardable_type|
awardable_string = awardable_type.pluralize
awardable_id_string = "#{awardable_type}_id"
diff --git a/lib/api/v3/boards.rb b/lib/api/v3/boards.rb
index b1c2a3c59f2..94acc67171e 100644
--- a/lib/api/v3/boards.rb
+++ b/lib/api/v3/boards.rb
@@ -6,7 +6,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all project boards' do
detail 'This feature was introduced in 8.13'
success ::API::Entities::Board
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
index 699e41b5537..7d9d6246e46 100644
--- a/lib/api/v3/branches.rb
+++ b/lib/api/v3/branches.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository branches' do
success ::API::Entities::RepoBranch
end
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index d254d247042..3414a2883e5 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -11,7 +11,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository commits' do
success ::API::Entities::RepoCommit
end
diff --git a/lib/api/v3/deploy_keys.rb b/lib/api/v3/deploy_keys.rb
index 5bbb167755c..bbb174b6003 100644
--- a/lib/api/v3/deploy_keys.rb
+++ b/lib/api/v3/deploy_keys.rb
@@ -13,7 +13,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of the project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
before { authorize_admin_project }
%w(keys deploy_keys).each do |path|
diff --git a/lib/api/v3/deployments.rb b/lib/api/v3/deployments.rb
index 95114ad1fe1..1d4972eda26 100644
--- a/lib/api/v3/deployments.rb
+++ b/lib/api/v3/deployments.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all deployments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success ::API::V3::Deployments
diff --git a/lib/api/v3/environments.rb b/lib/api/v3/environments.rb
index 3056b70e6ef..6bb4e016a01 100644
--- a/lib/api/v3/environments.rb
+++ b/lib/api/v3/environments.rb
@@ -9,7 +9,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all environments of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Environment
diff --git a/lib/api/v3/files.rb b/lib/api/v3/files.rb
index 4f8d58d37c8..13542b0c71c 100644
--- a/lib/api/v3/files.rb
+++ b/lib/api/v3/files.rb
@@ -40,7 +40,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a file from repository'
params do
requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb'
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
index 0aad87a3f58..c5b37622d79 100644
--- a/lib/api/v3/groups.rb
+++ b/lib/api/v3/groups.rb
@@ -93,7 +93,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups do
+ resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Update a group. Available only for users who can administrate groups.' do
success Entities::Group
end
@@ -163,7 +163,7 @@ module API
params do
requires :project_id, type: String, desc: 'The ID or path of the project'
end
- post ":id/projects/:project_id" do
+ post ":id/projects/:project_id", requirements: { project_id: /.+/ } do
authenticated_as_admin!
group = find_group!(params[:id])
project = find_project!(params[:project_id])
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index 258cbfed022..cead03b1e6b 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -68,7 +68,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups do
+ resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Get a list of group issues' do
success ::API::Entities::Issue
end
@@ -89,7 +89,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb
index 41f45d244e3..bd5eb2175e8 100644
--- a/lib/api/v3/labels.rb
+++ b/lib/api/v3/labels.rb
@@ -6,7 +6,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all labels of the project' do
success ::API::Entities::Label
end
diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb
index 3d4972afd9d..684860b553e 100644
--- a/lib/api/v3/members.rb
+++ b/lib/api/v3/members.rb
@@ -11,7 +11,7 @@ module API
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
- resource source_type.pluralize do
+ resource source_type.pluralize, requirements: { id: %r{[^/]+} } do
desc 'Gets a list of group or project members viewable by the authenticated user.' do
success ::API::Entities::Member
end
diff --git a/lib/api/v3/merge_request_diffs.rb b/lib/api/v3/merge_request_diffs.rb
index a462803e26c..35f462e907b 100644
--- a/lib/api/v3/merge_request_diffs.rb
+++ b/lib/api/v3/merge_request_diffs.rb
@@ -4,14 +4,16 @@ module API
class MergeRequestDiffs < Grape::API
before { authenticate! }
- resource :projects do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a list of merge request diff versions' do
detail 'This feature was introduced in GitLab 8.12.'
success ::API::Entities::MergeRequestDiff
end
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
end
@@ -27,7 +29,6 @@ module API
end
params do
- requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 7dbd4691a94..3077240e650 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
helpers do
diff --git a/lib/api/v3/milestones.rb b/lib/api/v3/milestones.rb
index 2a850a08a8a..be90cec4afc 100644
--- a/lib/api/v3/milestones.rb
+++ b/lib/api/v3/milestones.rb
@@ -18,7 +18,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a list of project milestones' do
success ::API::Entities::Milestone
end
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
index 0796bb62e68..4f8e0eff4ff 100644
--- a/lib/api/v3/notes.rb
+++ b/lib/api/v3/notes.rb
@@ -10,7 +10,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
NOTEABLE_TYPES.each do |noteable_type|
noteables_str = noteable_type.to_s.underscore.pluralize
diff --git a/lib/api/v3/pipelines.rb b/lib/api/v3/pipelines.rb
index 2c26a5f7d35..82827249244 100644
--- a/lib/api/v3/pipelines.rb
+++ b/lib/api/v3/pipelines.rb
@@ -8,7 +8,7 @@ module API
params do
requires :id, type: String, desc: 'The project ID'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get all Pipelines of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success ::API::Entities::Pipeline
diff --git a/lib/api/v3/project_hooks.rb b/lib/api/v3/project_hooks.rb
index 861b991b8e1..94614bfc8b6 100644
--- a/lib/api/v3/project_hooks.rb
+++ b/lib/api/v3/project_hooks.rb
@@ -25,7 +25,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get project hooks' do
success ::API::V3::Entities::ProjectHook
end
diff --git a/lib/api/v3/project_snippets.rb b/lib/api/v3/project_snippets.rb
index 809ca4f37ba..fc065a22d74 100644
--- a/lib/api/v3/project_snippets.rb
+++ b/lib/api/v3/project_snippets.rb
@@ -8,7 +8,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 47bfc12035a..b753dbab381 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -234,7 +234,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: /[^\/]+/ } do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a single project' do
success ::API::V3::Entities::ProjectWithAccess
end
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
index 44584e2eb70..e4d14bc8168 100644
--- a/lib/api/v3/repositories.rb
+++ b/lib/api/v3/repositories.rb
@@ -8,7 +8,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
helpers do
def handle_project_member_errors(errors)
if errors[:project_access].any?
diff --git a/lib/api/v3/runners.rb b/lib/api/v3/runners.rb
index 8967141fe3d..1934d6e578c 100644
--- a/lib/api/v3/runners.rb
+++ b/lib/api/v3/runners.rb
@@ -26,7 +26,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
before { authorize_admin_project }
desc "Disable project's runner" do
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index d77185ffe5a..3bacaeee032 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -554,7 +554,10 @@ module API
]
}.freeze
- resource :projects do
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
before { authenticate! }
before { authorize_admin_project }
@@ -609,7 +612,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc "Trigger a slash command for #{service_slug}" do
detail 'Added in GitLab 8.13'
end
diff --git a/lib/api/v3/subscriptions.rb b/lib/api/v3/subscriptions.rb
index 02a4157c26e..068750ec077 100644
--- a/lib/api/v3/subscriptions.rb
+++ b/lib/api/v3/subscriptions.rb
@@ -14,7 +14,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
requires :subscribable_id, type: String, desc: 'The ID of a resource'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
subscribable_types.each do |type, finder|
type_singularized = type.singularize
entity_class = ::API::Entities.const_get(type_singularized.camelcase)
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
index 6913720d9c5..c2541de2f50 100644
--- a/lib/api/v3/tags.rb
+++ b/lib/api/v3/tags.rb
@@ -6,7 +6,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get a project repository tags' do
success ::API::Entities::RepoTag
end
diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb
index e60cb25e57b..e3b311d61cd 100644
--- a/lib/api/v3/todos.rb
+++ b/lib/api/v3/todos.rb
@@ -20,9 +20,9 @@ module API
desc 'Mark all todos as done'
delete do
status(200)
-
+
todos = TodosFinder.new(current_user, params).execute
- TodoService.new.mark_todos_as_done(todos, current_user)
+ TodoService.new.mark_todos_as_done(todos, current_user).size
end
end
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index 1dfdb6a5956..b46639a2205 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -6,7 +6,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Trigger a GitLab project build' do
success ::API::V3::Entities::TriggerRequest
end
diff --git a/lib/api/v3/variables.rb b/lib/api/v3/variables.rb
index 0f55a14fb28..83972b1e7ce 100644
--- a/lib/api/v3/variables.rb
+++ b/lib/api/v3/variables.rb
@@ -10,7 +10,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Delete an existing variable from a project' do
success ::API::Entities::Variable
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 77e5d54c225..5acde41551b 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -9,7 +9,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects do
+ resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get project variables' do
success Entities::Variable
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index b25d6f18d59..fd4a6a107c2 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -2,10 +2,10 @@ module Banzai
module Pipeline
class GfmPipeline < BasePipeline
# These filters convert GitLab Flavored Markdown (GFM) to HTML.
- # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6
+ # The handlers defined in app/assets/javascripts/copy_as_gfm.js
# consequently convert that same HTML to GFM to be copied to the clipboard.
# Every filter that generates HTML from GFM should have a handler in
- # app/assets/javascripts/copy_as_gfm.js.es6, in reverse order.
+ # app/assets/javascripts/copy_as_gfm.js, in reverse order.
# The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
def self.filters
@filters ||= FilterArray[
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index 35871fd1b7b..a16d9fc2265 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -44,27 +44,17 @@ module Gitlab
end
# CSS sprite fallback takes precedence over image fallback
- def gl_emoji_tag(name, image: false, sprite: false, force_fallback: false)
+ def gl_emoji_tag(name)
emoji_name = emojis_aliases[name] || name
emoji_info = emojis[emoji_name]
- emoji_fallback_image_source = ActionController::Base.helpers.url_to_image("emoji/#{emoji_info['name']}.png")
- emoji_fallback_sprite_class = "emoji-#{emoji_name}"
+ return unless emoji_info
data = {
name: emoji_name,
unicode_version: emoji_unicode_version(emoji_name)
}
- data[:fallback_src] = emoji_fallback_image_source if image
- data[:fallback_sprite_class] = emoji_fallback_sprite_class if sprite
- ActionController::Base.helpers.content_tag 'gl-emoji',
- class: ("emoji-icon #{emoji_fallback_sprite_class}" if force_fallback && sprite),
- data: data do
- if force_fallback && !sprite
- emoji_image_tag(emoji_name, emoji_fallback_image_source)
- else
- emoji_info['moji']
- end
- end
+
+ ActionController::Base.helpers.content_tag('gl-emoji', emoji_info['moji'], data: data)
end
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 228ef7bb7a9..2187dd70ff4 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -201,7 +201,7 @@ module Gitlab
def archive_prefix(ref, sha)
project_name = self.name.chomp('.git')
- "#{project_name}-#{ref.parameterize}-#{sha}"
+ "#{project_name}-#{ref.tr('/', '-')}-#{sha}"
end
def archive_metadata(ref, storage_path, format = "tar.gz")
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
index 62dbd429156..bc5370de32a 100644
--- a/lib/gitlab/redis.rb
+++ b/lib/gitlab/redis.rb
@@ -1,6 +1,7 @@
# This file should not have any direct dependency on Rails environment
# please require all dependencies below:
require 'active_support/core_ext/hash/keys'
+require 'active_support/core_ext/module/delegation'
module Gitlab
class Redis
@@ -9,7 +10,6 @@ module Gitlab
SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze
MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze
DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze
- CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__)
class << self
delegate :params, :url, to: :new
@@ -33,13 +33,17 @@ module Gitlab
return @_raw_config if defined?(@_raw_config)
begin
- @_raw_config = ERB.new(File.read(CONFIG_FILE)).result.freeze
+ @_raw_config = ERB.new(File.read(config_file)).result.freeze
rescue Errno::ENOENT
@_raw_config = false
end
@_raw_config
end
+
+ def config_file
+ ENV['GITLAB_REDIS_CONFIG_FILE'] || File.expand_path('../../config/resque.yml', __dir__)
+ end
end
def initialize(rails_env = nil)
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index e5797d8fe3c..f6642527639 100644
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -56,14 +56,14 @@ gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
# The value of -listen-http must be set to `gitlab.yml > pages > external_http`
# as well. For example:
#
-# -listen-http 1.1.1.1:80
+# -listen-http 1.1.1.1:80 -listen-http [2001::1]:80
#
# To enable HTTPS support for custom domains add the `-listen-https`,
# `-root-cert` and `-root-key` directives in `gitlab_pages_options` below.
# The value of -listen-https must be set to `gitlab.yml > pages > external_https`
# as well. For example:
#
-# -listen-https 1.1.1.1:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key
+# -listen-https 1.1.1.1:443 -listen-http [2001::1]:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key
#
# The -pages-domain must be specified the same as in `gitlab.yml > pages > host`.
# Set `gitlab_pages_enabled=true` if you want to enable the Pages feature.
diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake
index 7db0779def8..7ccda04a35f 100644
--- a/lib/tasks/gitlab/dev.rake
+++ b/lib/tasks/gitlab/dev.rake
@@ -4,7 +4,7 @@ namespace :gitlab do
task :ee_compat_check, [:branch] => :environment do |_, args|
opts =
if ENV['CI']
- { branch: ENV['CI_BUILD_REF_NAME'] }
+ { branch: ENV['CI_COMMIT_REF_NAME'] }
else
unless args[:branch]
puts "Must specify a branch as an argument".color(:red)
diff --git a/package.json b/package.json
index 9652dd8f972..1048e29d0ac 100644
--- a/package.json
+++ b/package.json
@@ -2,9 +2,9 @@
"private": true,
"scripts": {
"dev-server": "webpack-dev-server --config config/webpack.config.js",
- "eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .",
- "eslint-fix": "eslint --max-warnings 0 --ext .js,.js.es6 --fix .",
- "eslint-report": "eslint --max-warnings 0 --ext .js,.js.es6 --format html --output-file ./eslint-report.html .",
+ "eslint": "eslint --max-warnings 0 --ext .js .",
+ "eslint-fix": "eslint --max-warnings 0 --ext .js --fix .",
+ "eslint-report": "eslint --max-warnings 0 --ext .js --format html --output-file ./eslint-report.html .",
"karma": "karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js",
"webpack": "webpack --config config/webpack.config.js",
@@ -62,7 +62,6 @@
"exclude": [
"spec/javascripts/test_bundle.js",
"spec/javascripts/**/*_spec.js",
- "spec/javascripts/**/*_spec.js.es6",
"app/assets/javascripts/droplab/**/*"
]
}
diff --git a/qa/.gitignore b/qa/.gitignore
new file mode 100644
index 00000000000..3fec32c8427
--- /dev/null
+++ b/qa/.gitignore
@@ -0,0 +1 @@
+tmp/
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 2814a7bdef0..72c82503542 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -8,7 +8,10 @@ RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list &&
WORKDIR /home/qa
-COPY ./ ./
+COPY ./Gemfile* ./
+
RUN bundle install
+COPY ./ ./
+
ENTRYPOINT ["bin/test"]
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
new file mode 100644
index 00000000000..6de2abff198
--- /dev/null
+++ b/qa/Gemfile.lock
@@ -0,0 +1,61 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.5.0)
+ public_suffix (~> 2.0, >= 2.0.2)
+ capybara (2.12.1)
+ addressable
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ xpath (~> 2.0)
+ capybara-screenshot (1.0.14)
+ capybara (>= 1.0, < 3)
+ launchy
+ capybara-webkit (1.12.0)
+ capybara (>= 2.3.0, < 2.13.0)
+ json
+ diff-lcs (1.3)
+ json (2.0.3)
+ launchy (2.4.3)
+ addressable (~> 2.3)
+ mime-types (3.1)
+ mime-types-data (~> 3.2015)
+ mime-types-data (3.2016.0521)
+ mini_portile2 (2.1.0)
+ nokogiri (1.7.0.1)
+ mini_portile2 (~> 2.1.0)
+ public_suffix (2.0.5)
+ rack (2.0.1)
+ rack-test (0.6.3)
+ rack (>= 1.0)
+ rake (12.0.0)
+ rspec (3.5.0)
+ rspec-core (~> 3.5.0)
+ rspec-expectations (~> 3.5.0)
+ rspec-mocks (~> 3.5.0)
+ rspec-core (3.5.4)
+ rspec-support (~> 3.5.0)
+ rspec-expectations (3.5.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.5.0)
+ rspec-mocks (3.5.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.5.0)
+ rspec-support (3.5.0)
+ xpath (2.0.0)
+ nokogiri (~> 1.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ capybara (~> 2.12.1)
+ capybara-screenshot (~> 1.0.14)
+ capybara-webkit (~> 1.12.0)
+ rake (~> 12.0.0)
+ rspec (~> 3.5)
+
+BUNDLED WITH
+ 1.14.6
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 0b8ff006d22..092048a6259 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -1,20 +1,36 @@
require 'spec_helper'
describe 'mail_room.yml' do
- let(:config_path) { 'config/mail_room.yml' }
- let(:configuration) { YAML.load(ERB.new(File.read(config_path)).result) }
- before(:each) { clear_raw_config }
- after(:each) { clear_raw_config }
+ include StubENV
- context 'when incoming email is disabled' do
- before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_disabled.yml').to_s
- Gitlab::MailRoom.reset_config!
- end
+ let(:mailroom_config_path) { 'config/mail_room.yml' }
+ let(:gitlab_config_path) { 'config/mail_room.yml' }
+ let(:redis_config_path) { 'config/resque.yml' }
- after do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil
- end
+ let(:configuration) do
+ vars = {
+ 'MAIL_ROOM_GITLAB_CONFIG_FILE' => absolute_path(gitlab_config_path),
+ 'GITLAB_REDIS_CONFIG_FILE' => absolute_path(redis_config_path)
+ }
+ cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result"
+
+ output, status = Gitlab::Popen.popen(%W(ruby -rerb -e #{cmd}), absolute_path('config'), vars)
+ raise "Error interpreting #{mailroom_config_path}: #{output}" unless status.zero?
+
+ YAML.load(output)
+ end
+
+ before(:each) do
+ stub_env('GITLAB_REDIS_CONFIG_FILE', absolute_path(redis_config_path))
+ clear_redis_raw_config
+ end
+
+ after(:each) do
+ clear_redis_raw_config
+ end
+
+ context 'when incoming email is disabled' do
+ let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_disabled.yml' }
it 'contains no configuration' do
expect(configuration[:mailboxes]).to be_nil
@@ -22,21 +38,12 @@ describe 'mail_room.yml' do
end
context 'when incoming email is enabled' do
- let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
- let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
-
- before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_enabled.yml').to_s
- Gitlab::MailRoom.reset_config!
- end
+ let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled.yml' }
+ let(:redis_config_path) { 'spec/fixtures/config/redis_new_format_host.yml' }
- after do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil
- end
+ let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
it 'contains the intended configuration' do
- stub_const('Gitlab::Redis::CONFIG_FILE', redis_config)
-
expect(configuration[:mailboxes].length).to eq(1)
mailbox = configuration[:mailboxes].first
@@ -66,9 +73,13 @@ describe 'mail_room.yml' do
end
end
- def clear_raw_config
+ def clear_redis_raw_config
Gitlab::Redis.remove_instance_variable(:@_raw_config)
rescue NameError
# raised if @_raw_config was not set; ignore
end
+
+ def absolute_path(path)
+ Rails.root.join(path).to_s
+ end
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 7072bd5e87c..71a4a2c43c7 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -49,4 +49,18 @@ describe Dashboard::TodosController do
expect(json_response).to eq({ "count" => "1", "done_count" => "0" })
end
end
+
+ describe 'PATCH #bulk_restore' do
+ let(:todos) { create_list(:todo, 2, :done, user: user, project: project, author: author) }
+
+ it 'restores the todos to pending state' do
+ patch :bulk_restore, ids: todos.map(&:id)
+
+ todos.each do |todo|
+ expect(todo.reload).to be_pending
+ end
+ expect(response).to have_http_status(200)
+ expect(json_response).to eq({ 'count' => '2', 'done_count' => '0' })
+ end
+ end
end
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
deleted file mode 100644
index 58caf7999cf..00000000000
--- a/spec/controllers/profiles/notifications_controller_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'spec_helper'
-
-describe Profiles::NotificationsController do
- let(:user) do
- create(:user) do |user|
- user.emails.create(email: 'original@example.com')
- user.emails.create(email: 'new@example.com')
- user.update(notification_email: 'original@example.com')
- user.save!
- end
- end
-
- describe 'GET show' do
- it 'renders' do
- sign_in(user)
-
- get :show
-
- expect(response).to render_template :show
- end
- end
-
- describe 'POST update' do
- it 'updates only permitted attributes' do
- sign_in(user)
-
- put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true }
-
- user.reload
- expect(user.notification_email).to eq('new@example.com')
- expect(user.notified_of_own_activity).to eq(true)
- expect(user.admin).to eq(false)
- expect(controller).to set_flash[:notice].to('Notification settings saved')
- end
-
- it 'shows an error message if the params are invalid' do
- sign_in(user)
-
- put :update, user: { notification_email: '' }
-
- expect(user.reload.notification_email).to eq('original@example.com')
- expect(controller).to set_flash[:alert].to('Failed to save new settings')
- end
- end
-end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a1ec41322ad..a88ffc1ea6a 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -78,10 +78,12 @@ describe ProjectsController do
it 'shows issues list page if wiki is disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+ create(:issue, project: project)
get :show, namespace_id: project.namespace, id: project
expect(response).to render_template('projects/issues/_issues')
+ expect(assigns(:issuable_meta_data)).not_to be_nil
end
it 'shows customize workflow page if wiki and issues are disabled' do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index f7f2d883d2f..d17a418b8c3 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -107,6 +107,9 @@ describe 'Issue Boards add issue modal', :feature, :js do
it 'returns issues' do
page.within('.add-issues-modal') do
find('.form-control').native.send_keys(issue.title)
+ find('.form-control').native.send_keys(:enter)
+
+ wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
@@ -115,6 +118,9 @@ describe 'Issue Boards add issue modal', :feature, :js do
it 'returns no issues' do
page.within('.add-issues-modal') do
find('.form-control').native.send_keys('testing search')
+ find('.form-control').native.send_keys(:enter)
+
+ wait_for_vue_resource
expect(page).not_to have_selector('.card')
expect(page).not_to have_content("You haven't added any issues to your project yet")
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 1cf0d11d448..e2281a7da55 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :feature, :js do
- include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
@@ -23,6 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
page.within('.add-issues-modal') do
find('.form-control').native.send_keys('testing empty state')
+ find('.form-control').native.send_keys(:enter)
wait_for_vue_resource
@@ -33,13 +33,11 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
it 'restores filters when closing' do
visit_board
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link 'Upcoming'
+ set_filter('milestone')
+ click_filter_link('Upcoming')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
expect(page).to have_selector('.card', count: 0)
@@ -56,39 +54,44 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
end
end
- context 'author' do
- let!(:issue) { create(:issue, project: project, author: user2) }
-
- before do
- project.team << [user2, :developer]
+ it 'resotres filters after clicking clear button' do
+ visit_board
- visit_board
- end
+ set_filter('milestone')
+ click_filter_link('Upcoming')
+ submit_filter
- it 'filters by any author' do
- page.within('.add-issues-modal') do
- click_button 'Author'
+ page.within('.add-issues-modal') do
+ wait_for_vue_resource
- wait_for_ajax
+ expect(page).to have_selector('.card', count: 0)
- click_link 'Any Author'
+ find('.clear-search').click
- wait_for_vue_resource
+ wait_for_vue_resource
- expect(page).to have_selector('.card', count: 2)
- end
+ expect(page).to have_selector('.card', count: 1)
end
+ end
- it 'filters by selected user' do
- page.within('.add-issues-modal') do
- click_button 'Author'
+ context 'author' do
+ let!(:issue) { create(:issue, project: project, author: user2) }
+
+ before do
+ project.team << [user2, :developer]
- wait_for_ajax
+ visit_board
+ end
- click_link user2.name
+ it 'filters by selected user' do
+ set_filter('author')
+ click_filter_link(user2.name)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: user2.username)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -103,46 +106,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
end
- it 'filters by any assignee' do
- page.within('.add-issues-modal') do
- click_button 'Assignee'
-
- wait_for_ajax
-
- click_link 'Any Assignee'
-
- wait_for_vue_resource
-
- expect(page).to have_selector('.card', count: 2)
- end
- end
-
it 'filters by unassigned' do
- page.within('.add-issues-modal') do
- click_button 'Assignee'
-
- wait_for_ajax
-
- click_link 'Unassigned'
+ set_filter('assignee')
+ click_filter_link('No Assignee')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
end
end
it 'filters by selected user' do
- page.within('.add-issues-modal') do
- click_button 'Assignee'
-
- wait_for_ajax
-
- page.within '.dropdown-menu-user' do
- click_link user2.name
- end
+ set_filter('assignee')
+ click_filter_link(user2.name)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: user2.username)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -156,44 +141,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
end
- it 'filters by any milestone' do
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link 'Any Milestone'
-
- wait_for_vue_resource
-
- expect(page).to have_selector('.card', count: 2)
- end
- end
-
it 'filters by upcoming milestone' do
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link 'Upcoming'
+ set_filter('milestone')
+ click_filter_link('Upcoming')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: 'upcoming')
expect(page).to have_selector('.card', count: 0)
end
end
it 'filters by selected milestone' do
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link milestone.name
+ set_filter('milestone')
+ click_filter_link(milestone.name)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: milestone.name)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -207,44 +176,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
end
- it 'filters by any label' do
- page.within('.add-issues-modal') do
- click_button 'Label'
-
- wait_for_ajax
-
- click_link 'Any Label'
-
- wait_for_vue_resource
-
- expect(page).to have_selector('.card', count: 2)
- end
- end
-
it 'filters by no label' do
- page.within('.add-issues-modal') do
- click_button 'Label'
-
- wait_for_ajax
-
- click_link 'No Label'
+ set_filter('label')
+ click_filter_link('No Label')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
end
end
it 'filters by label' do
- page.within('.add-issues-modal') do
- click_button 'Label'
-
- wait_for_ajax
-
- click_link label.title
+ set_filter('label')
+ click_filter_link(label.title)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: label.title)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -256,4 +209,20 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
click_button('Add issues')
end
+
+ def set_filter(type, text = '')
+ find('.add-issues-modal .filtered-search').native.send_keys("#{type}:#{text}")
+ end
+
+ def submit_filter
+ find('.add-issues-modal .filtered-search').native.send_keys(:enter)
+ end
+
+ def click_filter_link(link_text)
+ page.within('.add-issues-modal .filtered-search-input-container') do
+ expect(page).to have_button(link_text)
+
+ click_button(link_text)
+ end
+ end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index ce34f48aaaf..55df7e45f79 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -20,7 +20,7 @@ describe 'Copy as GFM', feature: true, js: true do
end
# The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
- # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM.
+ # The handlers defined in app/assets/javascripts/copy_as_gfm.js consequently convert that same HTML to GFM.
# To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle
# by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper.
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 63eb5c697c2..c4e58d14f75 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,10 +1,32 @@
require 'spec_helper'
RSpec.describe 'Dashboard Projects', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: "awesome stuff") }
+
before do
- login_as(create :user)
+ project.team << [user, :developer]
+ login_as user
visit dashboard_projects_path
end
-
+
+ it 'shows the project the user in a member of in the list' do
+ visit dashboard_projects_path
+ expect(page).to have_content('awesome stuff')
+ end
+
+ describe "with a pipeline" do
+ let(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.sha) }
+
+ before do
+ pipeline
+ end
+
+ it 'shows that the last pipeline passed' do
+ visit dashboard_projects_path
+ expect(page).to have_xpath("//a[@href='#{pipelines_namespace_project_commit_path(project.namespace, project, project.commit)}']")
+ end
+ end
+
it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index f424186cf30..16e453bc328 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -17,8 +17,21 @@ describe 'Awards Emoji', feature: true do
login_as(user)
end
+ describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
+ before do
+ # The `heart_tip` emoji is not valid anymore so we need to skip validation
+ issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
+ it 'does not shows a 500 page' do
+ expect(page).to have_text(issue.title)
+ end
+ end
+
describe 'Click award emoji from issue#show' do
- let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
+ let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
before do
visit namespace_project_issue_path(project.namespace, project, issue)
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 85ffffe4b6d..ce96a420699 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -202,6 +202,14 @@ describe 'Dropdown milestone', :feature, :js do
expect_tokens([{ name: 'milestone', value: 'upcoming' }])
expect_filtered_search_input_empty
end
+
+ it 'selects `started milestones`' do
+ click_static_milestone('Started')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect_tokens([{ name: 'milestone', value: 'started' }])
+ expect_filtered_search_input_empty
+ end
end
describe 'input has existing content' do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index f079a9627e4..f463312bf57 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -8,13 +8,12 @@ describe 'Filter issues', js: true, feature: true do
let!(:project) { create(:project, group: group) }
let!(:user) { create(:user) }
let!(:user2) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
let!(:bug_label) { create(:label, project: project, title: 'bug') }
let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') }
- let!(:milestone) { create(:milestone, title: "8", project: project) }
+ let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) }
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
@@ -505,6 +504,14 @@ describe 'Filter issues', js: true, feature: true do
expect_filtered_search_input_empty
end
+ it 'filters issues by started milestones' do
+ input_filtered_search("milestone:started")
+
+ expect_tokens([{ name: 'milestone', value: 'started' }])
+ expect_issues_list_count(5)
+ expect_filtered_search_input_empty
+ end
+
it 'filters issues by invalid milestones' do
skip('to be tested, issue #26546')
end
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
deleted file mode 100644
index e05fbb3715c..00000000000
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do
- let(:user) { create(:user) }
-
- before do
- login_as(user)
- end
-
- scenario 'User opts into receiving notifications about their own activity' do
- visit profile_notifications_path
-
- expect(page).not_to have_checked_field('user[notified_of_own_activity]')
-
- check 'user[notified_of_own_activity]'
-
- expect(page).to have_content('Notification settings saved')
- expect(page).to have_checked_field('user[notified_of_own_activity]')
- end
-
- scenario 'User opts out of receiving notifications about their own activity' do
- user.update!(notified_of_own_activity: true)
- visit profile_notifications_path
-
- expect(page).to have_checked_field('user[notified_of_own_activity]')
-
- uncheck 'user[notified_of_own_activity]'
-
- expect(page).to have_content('Notification settings saved')
- expect(page).not_to have_checked_field('user[notified_of_own_activity]')
- end
-end
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
new file mode 100644
index 00000000000..30a2b2bcf8c
--- /dev/null
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -0,0 +1,55 @@
+require 'rails_helper'
+
+feature 'Mini Pipeline Graph in Commit View', :js, :feature do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ login_as(user)
+ end
+
+ context 'when commit has pipelines' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ ref: project.default_branch,
+ sha: project.commit.sha)
+ end
+
+ let(:build) do
+ create(:ci_build, pipeline: pipeline)
+ end
+
+ before do
+ build.run
+ visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ end
+
+ it 'should display a mini pipeline graph' do
+ expect(page).to have_selector('.mr-widget-pipeline-graph')
+ end
+
+ it 'should show the builds list when stage is clicked' do
+ first('.mini-pipeline-graph-dropdown-toggle').click
+
+ wait_for_ajax
+
+ page.within '.js-builds-dropdown-list' do
+ expect(page).to have_selector('.ci-status-icon-running')
+ expect(page).to have_content(build.stage)
+ end
+ end
+ end
+
+ context 'when commit does not have pipelines' do
+ before do
+ visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ end
+
+ it 'should not display a mini pipeline graph' do
+ expect(page).not_to have_selector('.mr-widget-pipeline-graph')
+ end
+ end
+end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index de3c6eceb82..e2911a37e40 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -29,7 +29,7 @@ feature 'Issue prioritization', feature: true do
issue_1.labels << label_5
login_as user
- visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
+ visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
# Ensure we are indicating that issues are sorted by priority
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
@@ -68,7 +68,7 @@ feature 'Issue prioritization', feature: true do
issue_6.labels << label_5 # 8 - No priority
login_as user
- visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
+ visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 45185f2dd1f..52196ce49bd 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -16,6 +16,15 @@ feature "New project", feature: true do
expect(find_field("project_visibility_level_#{level}")).to be_checked
end
+
+ it 'saves visibility level on validation error' do
+ visit new_project_path
+
+ choose(key)
+ click_button('Create project')
+
+ expect(find_field("project_visibility_level_#{level}")).to be_checked
+ end
end
end
diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
new file mode 100644
index 00000000000..c17e06612de
--- /dev/null
+++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User views the wiki page', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:old_page_version_id) { wiki_page.versions.last.id }
+ let(:wiki_page) do
+ WikiPages::CreateService.new(
+ project,
+ user,
+ title: 'home',
+ content: '[some link](other-page)'
+ ).execute
+ end
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+ WikiPages::UpdateService.new(
+ project,
+ user,
+ message: 'updated home',
+ content: 'updated [some link](other-page)',
+ format: :markdown
+ ).execute(wiki_page)
+ end
+
+ scenario 'Visit Wiki Page Current Commit' do
+ visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+
+ expect(page).to have_selector('a.btn', text: 'Edit')
+ end
+
+ scenario 'Visit Wiki Page Historical Commit' do
+ visit namespace_project_wiki_path(
+ project.namespace,
+ project,
+ wiki_page,
+ version_id: old_page_version_id
+ )
+
+ expect(page).not_to have_selector('a.btn', text: 'Edit')
+ end
+end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 5c2df949ac5..850020109d4 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -31,7 +31,7 @@ describe 'Dashboard Todos', feature: true do
end
it 'shows due date as today' do
- page.within first('.todo') do
+ within first('.todo') do
expect(page).to have_content 'Due today'
end
end
@@ -184,6 +184,60 @@ describe 'Dashboard Todos', feature: true do
expect(page).to have_content "You're all done!"
expect(page).not_to have_selector('.gl-pagination')
end
+
+ it 'shows "Undo mark all as done" button' do
+ expect(page).to have_selector('.js-todos-mark-all', visible: false)
+ expect(page).to have_selector('.js-todos-undo-all', visible: true)
+ end
+ end
+
+ describe 'undo mark all as done', js: true do
+ before do
+ visit dashboard_todos_path
+ end
+
+ it 'shows the restored todo list' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.todos-list .todo', count: 1)
+ expect(page).to have_selector('.gl-pagination')
+ expect(page).not_to have_content "You're all done!"
+ end
+
+ it 'updates todo count' do
+ mark_all_and_undo
+
+ expect(page).to have_content 'To do 2'
+ expect(page).to have_content 'Done 0'
+ end
+
+ it 'shows "Mark all as done" button' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.js-todos-mark-all', visible: true)
+ expect(page).to have_selector('.js-todos-undo-all', visible: false)
+ end
+
+ context 'User has deleted a todo' do
+ before do
+ within first('.todo') do
+ click_link 'Done'
+ end
+ end
+
+ it 'shows the restored todo list with the deleted todo' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1)
+ end
+ end
+
+ def mark_all_and_undo
+ click_link 'Mark all as done'
+ wait_for_ajax
+ click_link 'Undo mark all as done'
+ wait_for_ajax
+ end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 2a008427478..ee52dc65175 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -101,6 +101,41 @@ describe IssuesFinder do
end
end
+ context 'filtering by started milestone' do
+ let(:params) { { milestone_title: Milestone::Started.name } }
+
+ let(:project_no_started_milestones) { create(:empty_project, :public) }
+ let(:project_started_1_and_2) { create(:empty_project, :public) }
+ let(:project_started_8) { create(:empty_project, :public) }
+
+ let(:yesterday) { Date.today - 1.day }
+ let(:tomorrow) { Date.today + 1.day }
+ let(:two_days_ago) { Date.today - 2.days }
+
+ let(:milestones) do
+ [
+ create(:milestone, project: project_no_started_milestones, start_date: tomorrow),
+ create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago),
+ create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday),
+ create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow),
+ create(:milestone, project: project_started_8, title: '7.0'),
+ create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday),
+ create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow)
+ ]
+ end
+
+ before do
+ milestones.each do |milestone|
+ create(:issue, project: milestone.project, milestone: milestone, author: user, assignee: user)
+ end
+ end
+
+ it 'returns issues in the started milestones for each project' do
+ expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.0', '2.0', '8.0')
+ expect(issues.map { |issue| issue.milestone.start_date }).to contain_exactly(two_days_ago, yesterday, yesterday)
+ end
+ end
+
context 'filtering by label' do
let(:params) { { label_name: label.title } }
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 637b02d9388..174cc84a97b 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -16,4 +16,11 @@ describe CiStatusHelper do
helper.ci_icon_for_status(failed_commit.status)
end
end
+
+ describe "#pipeline_status_cache_key" do
+ it "builds a cache key for pipeline status" do
+ pipeline_status = Ci::PipelineStatus.new(build(:project), sha: "123abc", status: "success")
+ expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
+ end
+ end
end
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 68b20a1e4fc..77a4ba305bb 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -47,4 +47,54 @@ describe MilestonesHelper do
end
end
end
+
+ describe '#milestone_remaining_days' do
+ context 'when less than 31 days remaining' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 12.days.from_now)) }
+
+ it 'returns days remaining' do
+ expect(milestone_remaining).to eq("<strong>11</strong> days remaining")
+ end
+ end
+
+ context 'when less than 1 year and more than 30 days remaining' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 2.months.from_now)) }
+
+ it 'returns months remaining' do
+ expect(milestone_remaining).to eq("<strong>2</strong> months remaining")
+ end
+ end
+
+ context 'when more than 1 year remaining' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 1.year.from_now + 2.days)) }
+
+ it 'returns years remaining' do
+ expect(milestone_remaining).to eq("<strong>1</strong> year remaining")
+ end
+ end
+
+ context 'when milestone is expired' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 2.days.ago)) }
+
+ it 'returns "Past due"' do
+ expect(milestone_remaining).to eq("<strong>Past due</strong>")
+ end
+ end
+
+ context 'when milestone has start_date in the future' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, start_date: 2.days.from_now)) }
+
+ it 'returns "Upcoming"' do
+ expect(milestone_remaining).to eq("<strong>Upcoming</strong>")
+ end
+ end
+
+ context 'when milestone has start_date in the past' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, start_date: 2.days.ago)) }
+
+ it 'returns days elapsed' do
+ expect(milestone_remaining).to eq("<strong>2</strong> days elapsed")
+ end
+ end
+ end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index aca0bb1d794..fc6ad6419ac 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -63,6 +63,46 @@ describe ProjectsHelper do
end
end
+ describe "#project_list_cache_key" do
+ let(:project) { create(:project) }
+
+ it "includes the namespace" do
+ expect(helper.project_list_cache_key(project)).to include(project.namespace.cache_key)
+ end
+
+ it "includes the project" do
+ expect(helper.project_list_cache_key(project)).to include(project.cache_key)
+ end
+
+ it "includes the controller name" do
+ expect(helper.controller).to receive(:controller_name).and_return("testcontroller")
+
+ expect(helper.project_list_cache_key(project)).to include("testcontroller")
+ end
+
+ it "includes the controller action" do
+ expect(helper.controller).to receive(:action_name).and_return("testaction")
+
+ expect(helper.project_list_cache_key(project)).to include("testaction")
+ end
+
+ it "includes the application settings" do
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ expect(helper.project_list_cache_key(project)).to include(settings.cache_key)
+ end
+
+ it "includes a version" do
+ expect(helper.project_list_cache_key(project)).to include("v2.3")
+ end
+
+ it "includes the pipeline status when there is a status" do
+ create(:ci_pipeline, :success, project: project, sha: project.commit.sha)
+
+ expect(helper.project_list_cache_key(project)).to include("pipeline-status/#{project.commit.sha}-success")
+ end
+ end
+
describe 'link_to_member' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) }
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
new file mode 100644
index 00000000000..50060a0925d
--- /dev/null
+++ b/spec/helpers/todos_helper_spec.rb
@@ -0,0 +1,23 @@
+require "spec_helper"
+
+describe TodosHelper do
+ describe '#todo_projects_options' do
+ let(:projects) { create_list(:empty_project, 3) }
+ let(:user) { create(:user) }
+
+ it 'returns users authorised projects in json format' do
+ projects.first.add_developer(user)
+ projects.second.add_developer(user)
+
+ allow(helper).to receive(:current_user).and_return(user)
+
+ expected_results = [
+ { 'id' => '', 'text' => 'Any Project' },
+ { 'id' => projects.second.id, 'text' => projects.second.name_with_namespace },
+ { 'id' => projects.first.id, 'text' => projects.first.name_with_namespace }
+ ]
+
+ expect(JSON.parse(helper.todo_projects_options)).to match_array(expected_results)
+ end
+ end
+end
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 5c65903701b..e6538020896 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -126,7 +126,11 @@ require('~/filtered_search/filtered_search_dropdown_manager');
beforeEach(() => {
setFixtures(`
- <input type="text" id="test" />
+ <ul class="tokens-container">
+ <li class="input-token">
+ <input class="filtered-search" type="text" id="test" />
+ </li>
+ </ul>
`);
input = document.getElementById('test');
@@ -142,7 +146,7 @@ require('~/filtered_search/filtered_search_dropdown_manager');
input.value = 'o';
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
- }, 'o');
+ });
expect(updatedItem.droplab_hidden).toBe(true);
});
@@ -150,6 +154,29 @@ require('~/filtered_search/filtered_search_dropdown_manager');
const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
expect(updatedItem.droplab_hidden).toBe(false);
});
+
+ it('should allow multiple if item.type is array', () => {
+ input.value = 'label:~first la';
+ const updatedItem = gl.DropdownUtils.filterHint(input, {
+ hint: 'label',
+ type: 'array',
+ });
+ expect(updatedItem.droplab_hidden).toBe(false);
+ });
+
+ it('should prevent multiple if item.type is not array', () => {
+ input.value = 'milestone:~first mile';
+ let updatedItem = gl.DropdownUtils.filterHint(input, {
+ hint: 'milestone',
+ });
+ expect(updatedItem.droplab_hidden).toBe(true);
+
+ updatedItem = gl.DropdownUtils.filterHint(input, {
+ hint: 'milestone',
+ type: 'string',
+ });
+ expect(updatedItem.droplab_hidden).toBe(true);
+ });
});
describe('setDataValueIfSelected', () => {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index fae462561e9..c12b44cea89 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -48,10 +48,10 @@ describe('Uncovered files', function () {
'./network/branch_graph.js',
];
- const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.(js|es6)$/);
+ const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.js$/);
sourceFiles.keys().forEach(function (path) {
// ignore if there is a matching spec file
- if (testsContext.keys().indexOf(`${path.replace(/\.js(\.es6)?$/, '')}_spec`) > -1) {
+ if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) {
return;
}
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 730ca1f7c0a..90628917943 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -45,10 +45,10 @@ describe ExpandVariables do
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' }
] },
- { value: 'review/$CI_BUILD_REF_NAME',
+ { value: 'review/$CI_COMMIT_REF_NAME',
result: 'review/feature/add-review-apps',
variables: [
- { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' }
+ { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
] },
]
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 2adbed2154f..c330e609337 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -151,8 +151,8 @@ describe Gitlab::Ci::Config::Entry::Environment do
context 'when variables are used for environment' do
let(:config) do
- { name: 'review/$CI_BUILD_REF_NAME',
- url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' }
+ { name: 'review/$CI_COMMIT_REF_NAME',
+ url: 'https://$CI_COMMIT_REF_NAME.review.gitlab.com' }
end
describe '#valid?' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 9c3a4571ce4..9ed43da1116 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -90,6 +90,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(prefix).to eq("#{project_name}-test-branch-SHA")
end
+
+ it 'returns correct string for a ref containing dots' do
+ prefix = repository.archive_prefix('test.branch', 'SHA')
+
+ expect(prefix).to eq("#{project_name}-test.branch-SHA")
+ end
end
describe '#archive' do
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb
index 917c5c46db1..8b77c925705 100644
--- a/spec/lib/gitlab/redis_spec.rb
+++ b/spec/lib/gitlab/redis_spec.rb
@@ -3,8 +3,16 @@ require 'spec_helper'
describe Gitlab::Redis do
include StubENV
- before(:each) { clear_raw_config }
- after(:each) { clear_raw_config }
+ let(:config) { 'config/resque.yml' }
+
+ before(:each) do
+ stub_env('GITLAB_REDIS_CONFIG_FILE', Rails.root.join(config).to_s)
+ clear_raw_config
+ end
+
+ after(:each) do
+ clear_raw_config
+ end
describe '.params' do
subject { described_class.params }
@@ -18,22 +26,22 @@ describe Gitlab::Redis do
end
context 'when url contains unix socket reference' do
- let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
- let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
+ let(:config_old) { 'spec/fixtures/config/redis_old_format_socket.yml' }
+ let(:config_new) { 'spec/fixtures/config/redis_new_format_socket.yml' }
context 'with old format' do
- it 'returns path key instead' do
- stub_const("#{described_class}::CONFIG_FILE", config_old)
+ let(:config) { config_old }
+ it 'returns path key instead' do
is_expected.to include(path: '/path/to/old/redis.sock')
is_expected.not_to have_key(:url)
end
end
context 'with new format' do
- it 'returns path key instead' do
- stub_const("#{described_class}::CONFIG_FILE", config_new)
+ let(:config) { config_new }
+ it 'returns path key instead' do
is_expected.to include(path: '/path/to/redis.sock')
is_expected.not_to have_key(:url)
end
@@ -41,22 +49,22 @@ describe Gitlab::Redis do
end
context 'when url is host based' do
- let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
- let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+ let(:config_old) { 'spec/fixtures/config/redis_old_format_host.yml' }
+ let(:config_new) { 'spec/fixtures/config/redis_new_format_host.yml' }
context 'with old format' do
- it 'returns hash with host, port, db, and password' do
- stub_const("#{described_class}::CONFIG_FILE", config_old)
+ let(:config) { config_old }
+ it 'returns hash with host, port, db, and password' do
is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
end
end
context 'with new format' do
- it 'returns hash with host, port, db, and password' do
- stub_const("#{described_class}::CONFIG_FILE", config_new)
+ let(:config) { config_new }
+ it 'returns hash with host, port, db, and password' do
is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
end
@@ -74,15 +82,13 @@ describe Gitlab::Redis do
end
context 'when yml file with env variable' do
- let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') }
+ let(:config) { 'spec/fixtures/config/redis_config_with_env.yml' }
before do
stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379')
end
it 'reads redis url from env variable' do
- stub_const("#{described_class}::CONFIG_FILE", redis_config)
-
expect(described_class.url).to eq 'redis://redishost:6379'
end
end
@@ -90,14 +96,13 @@ describe Gitlab::Redis do
describe '._raw_config' do
subject { described_class._raw_config }
+ let(:config) { '/var/empty/doesnotexist' }
it 'should be frozen' do
expect(subject).to be_frozen
end
it 'returns false when the file does not exist' do
- stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist')
-
expect(subject).to eq(false)
end
end
@@ -134,22 +139,18 @@ describe Gitlab::Redis do
subject { described_class.new(Rails.env).sentinels }
context 'when sentinels are defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' }
it 'returns an array of hashes with host and port keys' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to include(host: 'localhost', port: 26380)
is_expected.to include(host: 'slave2', port: 26381)
end
end
context 'when sentinels are not defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' }
it 'returns nil' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to be_nil
end
end
@@ -159,21 +160,17 @@ describe Gitlab::Redis do
subject { described_class.new(Rails.env).sentinels? }
context 'when sentinels are defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' }
it 'returns true' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to be_truthy
end
end
context 'when sentinels are not defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' }
it 'returns false' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to be_falsey
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 9962c987110..4a664e4fae2 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1018,6 +1018,19 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#update_status' do
+ let(:pipeline) { create(:ci_pipeline, sha: '123456') }
+
+ it 'updates the cached status' do
+ fake_status = double
+ # after updating the status, the status is set to `skipped` for this pipeline's builds
+ expect(Ci::PipelineStatus).to receive(:new).with(pipeline.project, sha: '123456', status: 'skipped').and_return(fake_status)
+ expect(fake_status).to receive(:store_in_cache_if_needed)
+
+ pipeline.update_status
+ end
+ end
+
describe 'notifications when pipeline success or failed' do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/ci/pipeline_status_spec.rb b/spec/models/ci/pipeline_status_spec.rb
new file mode 100644
index 00000000000..bc5b71666c2
--- /dev/null
+++ b/spec/models/ci/pipeline_status_spec.rb
@@ -0,0 +1,173 @@
+require 'spec_helper'
+
+describe Ci::PipelineStatus do
+ let(:project) { create(:project) }
+ let(:pipeline_status) { described_class.new(project) }
+
+ describe '.load_for_project' do
+ it "loads the status" do
+ expect_any_instance_of(described_class).to receive(:load_status)
+
+ described_class.load_for_project(project)
+ end
+ end
+
+ describe '#has_status?' do
+ it "is false when the status wasn't loaded yet" do
+ expect(pipeline_status.has_status?).to be_falsy
+ end
+
+ it 'is true when all status information was loaded' do
+ fake_commit = double
+ allow(fake_commit).to receive(:status).and_return('failed')
+ allow(fake_commit).to receive(:sha).and_return('failed424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6')
+ allow(pipeline_status).to receive(:commit).and_return(fake_commit)
+ allow(pipeline_status).to receive(:has_cache?).and_return(false)
+
+ pipeline_status.load_status
+
+ expect(pipeline_status.has_status?).to be_truthy
+ end
+ end
+
+ describe '#load_status' do
+ it 'loads the status from the cache when there is one' do
+ expect(pipeline_status).to receive(:has_cache?).and_return(true)
+ expect(pipeline_status).to receive(:load_from_cache)
+
+ pipeline_status.load_status
+ end
+
+ it 'loads the status from the project commit when there is no cache' do
+ allow(pipeline_status).to receive(:has_cache?).and_return(false)
+
+ expect(pipeline_status).to receive(:load_from_commit)
+
+ pipeline_status.load_status
+ end
+
+ it 'stores the status in the cache when it loading it from the project' do
+ allow(pipeline_status).to receive(:has_cache?).and_return(false)
+ allow(pipeline_status).to receive(:load_from_commit)
+
+ expect(pipeline_status).to receive(:store_in_cache)
+
+ pipeline_status.load_status
+ end
+
+ it 'sets the state to loaded' do
+ pipeline_status.load_status
+
+ expect(pipeline_status).to be_loaded
+ end
+
+ it 'only loads the status once' do
+ expect(pipeline_status).to receive(:has_cache?).and_return(true).exactly(1)
+ expect(pipeline_status).to receive(:load_from_cache).exactly(1)
+
+ pipeline_status.load_status
+ pipeline_status.load_status
+ end
+ end
+
+ describe "#load_from_commit" do
+ let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.sha) }
+
+ it 'reads the status from the pipeline for the commit' do
+ pipeline_status.load_from_commit
+
+ expect(pipeline_status.status).to eq('success')
+ expect(pipeline_status.sha).to eq(project.commit.sha)
+ end
+
+ it "doesn't fail for an empty project" do
+ status_for_empty_commit = described_class.new(create(:empty_project))
+
+ status_for_empty_commit.load_status
+
+ expect(status_for_empty_commit).to be_loaded
+ end
+ end
+
+ describe "#store_in_cache", :redis do
+ it "sets the object in redis" do
+ pipeline_status.sha = '123456'
+ pipeline_status.status = 'failed'
+
+ pipeline_status.store_in_cache
+ read_sha, read_status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{project.id}/build_status", :sha, :status) }
+
+ expect(read_sha).to eq('123456')
+ expect(read_status).to eq('failed')
+ end
+ end
+
+ describe '#store_in_cache_if_needed', :redis do
+ it 'stores the state in the cache when the sha is the HEAD of the project' do
+ create(:ci_pipeline, :success, project: project, sha: project.commit.sha)
+ build_status = described_class.load_for_project(project)
+
+ build_status.store_in_cache_if_needed
+ sha, status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{project.id}/build_status", :sha, :status) }
+
+ expect(sha).not_to be_nil
+ expect(status).not_to be_nil
+ end
+
+ it "doesn't store the status in redis when the sha is not the head of the project" do
+ other_status = described_class.new(project, sha: "123456", status: "failed")
+
+ other_status.store_in_cache_if_needed
+ sha, status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{project.id}/build_status", :sha, :status) }
+
+ expect(sha).to be_nil
+ expect(status).to be_nil
+ end
+
+ it "deletes the cache if the repository doesn't have a head commit" do
+ empty_project = create(:empty_project)
+ Gitlab::Redis.with { |redis| redis.mapped_hmset("projects/#{empty_project.id}/build_status", { sha: "sha", status: "pending" }) }
+ other_status = described_class.new(empty_project, sha: "123456", status: "failed")
+
+ other_status.store_in_cache_if_needed
+ sha, status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{empty_project.id}/build_status", :sha, :status) }
+
+ expect(sha).to be_nil
+ expect(status).to be_nil
+ end
+ end
+
+ describe "with a status in redis", :redis do
+ let(:status) { 'success' }
+ let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' }
+
+ before do
+ Gitlab::Redis.with { |redis| redis.mapped_hmset("projects/#{project.id}/build_status", { sha: sha, status: status }) }
+ end
+
+ describe '#load_from_cache' do
+ it 'reads the status from redis' do
+ pipeline_status.load_from_cache
+
+ expect(pipeline_status.sha).to eq(sha)
+ expect(pipeline_status.status).to eq(status)
+ end
+ end
+
+ describe '#has_cache?' do
+ it 'knows the status is cached' do
+ expect(pipeline_status.has_cache?).to be_truthy
+ end
+ end
+
+ describe '#delete_from_cache' do
+ it 'deletes values from redis' do
+ pipeline_status.delete_from_cache
+
+ key_exists = Gitlab::Redis.with { |redis| redis.exists("projects/#{project.id}/build_status") }
+
+ expect(key_exists).to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 32f9366a14c..4b449546a30 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -212,6 +212,25 @@ eos
end
end
+ describe '#latest_pipeline' do
+ let!(:first_pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ status: 'success')
+ end
+ let!(:second_pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ status: 'success')
+ end
+
+ it 'returns latest pipeline' do
+ expect(commit.latest_pipeline).to eq second_pipeline
+ end
+ end
+
describe '#status' do
context 'without ref argument' do
before do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 545a11912e3..31ae0dce140 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -344,6 +344,46 @@ describe Issue, "Issuable" do
end
end
+ describe '.order_due_date_and_labels_priority' do
+ let(:project) { create(:empty_project) }
+
+ def create_issue(milestone, labels)
+ create(:labeled_issue, milestone: milestone, labels: labels, project: project)
+ end
+
+ it 'sorts issues in order of milestone due date, then label priority' do
+ first_priority = create(:label, project: project, priority: 1)
+ second_priority = create(:label, project: project, priority: 2)
+ no_priority = create(:label, project: project)
+
+ first_milestone = create(:milestone, project: project, due_date: Time.now)
+ second_milestone = create(:milestone, project: project, due_date: Time.now + 1.month)
+ third_milestone = create(:milestone, project: project)
+
+ # The issues here are ordered by label priority, to ensure that we don't
+ # accidentally just sort by creation date.
+ second_milestone_first_priority = create_issue(second_milestone, [first_priority, second_priority, no_priority])
+ third_milestone_first_priority = create_issue(third_milestone, [first_priority, second_priority, no_priority])
+ first_milestone_second_priority = create_issue(first_milestone, [second_priority, no_priority])
+ second_milestone_second_priority = create_issue(second_milestone, [second_priority, no_priority])
+ no_milestone_second_priority = create_issue(nil, [second_priority, no_priority])
+ first_milestone_no_priority = create_issue(first_milestone, [no_priority])
+ second_milestone_no_labels = create_issue(second_milestone, [])
+ third_milestone_no_priority = create_issue(third_milestone, [no_priority])
+
+ result = Issue.order_due_date_and_labels_priority
+
+ expect(result).to eq([first_milestone_second_priority,
+ first_milestone_no_priority,
+ second_milestone_first_priority,
+ second_milestone_second_priority,
+ second_milestone_no_labels,
+ third_milestone_first_priority,
+ no_milestone_second_priority,
+ third_milestone_no_priority])
+ end
+ end
+
describe '.order_labels_priority' do
let(:label_1) { create(:label, title: 'label_1', project: issue.project, priority: 1) }
let(:label_2) { create(:label, title: 'label_2', project: issue.project, priority: 2) }
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index ad703a6c8bb..68e4c0a522b 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -116,21 +116,41 @@ describe Milestone, 'Milestoneish' do
end
end
+ describe '#remaining_days' do
+ it 'shows 0 if no due date' do
+ milestone = build_stubbed(:milestone)
+
+ expect(milestone.remaining_days).to eq(0)
+ end
+
+ it 'shows 0 if expired' do
+ milestone = build_stubbed(:milestone, due_date: 2.days.ago)
+
+ expect(milestone.remaining_days).to eq(0)
+ end
+
+ it 'shows correct remaining days' do
+ milestone = build_stubbed(:milestone, due_date: 2.days.from_now)
+
+ expect(milestone.remaining_days).to eq(2)
+ end
+ end
+
describe '#elapsed_days' do
it 'shows 0 if no start_date set' do
- milestone = build(:milestone)
+ milestone = build_stubbed(:milestone)
expect(milestone.elapsed_days).to eq(0)
end
it 'shows 0 if start_date is a future' do
- milestone = build(:milestone, start_date: Time.now + 2.days)
+ milestone = build_stubbed(:milestone, start_date: Time.now + 2.days)
expect(milestone.elapsed_days).to eq(0)
end
it 'shows correct amount of days' do
- milestone = build(:milestone, start_date: Time.now - 2.days)
+ milestone = build_stubbed(:milestone, start_date: Time.now - 2.days)
expect(milestone.elapsed_days).to eq(2)
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index b4305e92812..9f0e7fbbe26 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -239,7 +239,7 @@ describe Environment, models: true do
describe '#actions_for' do
let(:deployment) { create(:deployment, environment: environment) }
let(:pipeline) { deployment.deployable.pipeline }
- let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_BUILD_REF_NAME' )}
+ let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_COMMIT_REF_NAME' )}
let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )}
it 'returns a list of actions with matching environment' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e120e21af06..ff1defcd32d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1916,4 +1916,15 @@ describe Project, models: true do
end
end
end
+
+ describe '#pipeline_status' do
+ let(:project) { create(:project) }
+ it 'builds a pipeline status' do
+ expect(project.pipeline_status).to be_a(Ci::PipelineStatus)
+ end
+
+ it 'hase a loaded pipeline status' do
+ expect(project.pipeline_status).to be_loaded
+ end
+ end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 585449e62b6..a10d876ffad 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -178,7 +178,7 @@ describe API::Commits, api: true do
end
end
- describe "Create a commit with multiple files and actions" do
+ describe "POST /projects/:id/repository/commits" do
let!(:url) { "/projects/#{project.id}/repository/commits" }
it 'returns a 403 unauthorized for user without permissions' do
@@ -193,7 +193,7 @@ describe API::Commits, api: true do
expect(response).to have_http_status(400)
end
- context :create do
+ describe 'create' do
let(:message) { 'Created file' }
let!(:invalid_c_params) do
{
@@ -237,8 +237,8 @@ describe API::Commits, api: true do
expect(response).to have_http_status(400)
end
- context 'with project path in URL' do
- let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
+ context 'with project path containing a dot in URL' do
+ let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" }
it 'a new file in project repo' do
post api(url, user), valid_c_params
@@ -248,7 +248,7 @@ describe API::Commits, api: true do
end
end
- context :delete do
+ describe 'delete' do
let(:message) { 'Deleted file' }
let!(:invalid_d_params) do
{
@@ -289,7 +289,7 @@ describe API::Commits, api: true do
end
end
- context :move do
+ describe 'move' do
let(:message) { 'Moved file' }
let!(:invalid_m_params) do
{
@@ -334,7 +334,7 @@ describe API::Commits, api: true do
end
end
- context :update do
+ describe 'update' do
let(:message) { 'Updated file' }
let!(:invalid_u_params) do
{
@@ -377,7 +377,7 @@ describe API::Commits, api: true do
end
end
- context "multiple operations" do
+ describe 'multiple operations' do
let(:message) { 'Multiple actions' }
let!(:invalid_mo_params) do
{
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index de7dbca0b22..e7738ca3034 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -153,6 +153,16 @@ describe API::Issues, api: true do
expect(json_response.first['state']).to eq('opened')
end
+ it 'returns unlabeled issues for "No Label" label' do
+ get api("/issues", user), labels: 'No Label'
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to be_empty
+ end
+
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index d50fe80b36a..442b2df1952 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -312,8 +312,8 @@ describe API::Runner do
end
let(:expected_variables) do
- [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true },
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
{ 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }]
end
@@ -437,9 +437,9 @@ describe API::Runner do
context 'when triggered job is available' do
let(:expected_variables) do
- [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true },
- { 'key' => 'CI_BUILD_TRIGGERED', 'value' => 'true', 'public' => true },
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
+ { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true },
{ 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true },
{ 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false },
{ 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index e298ef055e1..adba3a787aa 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -88,7 +88,7 @@ describe API::V3::Commits, api: true do
end
end
- describe "Create a commit with multiple files and actions" do
+ describe "POST /projects/:id/repository/commits" do
let!(:url) { "/projects/#{project.id}/repository/commits" }
it 'returns a 403 unauthorized for user without permissions' do
@@ -103,7 +103,7 @@ describe API::V3::Commits, api: true do
expect(response).to have_http_status(400)
end
- context :create do
+ describe 'create' do
let(:message) { 'Created file' }
let!(:invalid_c_params) do
{
@@ -147,8 +147,9 @@ describe API::V3::Commits, api: true do
expect(response).to have_http_status(400)
end
- context 'with project path in URL' do
- let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
+ context 'with project path containing a dot in URL' do
+ let!(:user) { create(:user, username: 'foo.bar') }
+ let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" }
it 'a new file in project repo' do
post v3_api(url, user), valid_c_params
@@ -158,7 +159,7 @@ describe API::V3::Commits, api: true do
end
end
- context :delete do
+ describe 'delete' do
let(:message) { 'Deleted file' }
let!(:invalid_d_params) do
{
@@ -199,7 +200,7 @@ describe API::V3::Commits, api: true do
end
end
- context :move do
+ describe 'move' do
let(:message) { 'Moved file' }
let!(:invalid_m_params) do
{
@@ -244,7 +245,7 @@ describe API::V3::Commits, api: true do
end
end
- context :update do
+ describe 'update' do
let(:message) { 'Updated file' }
let!(:invalid_u_params) do
{
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 9948d1a9ea0..c879f37f50d 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -81,8 +81,8 @@ describe Ci::API::Builds do
expect(runner.reload.platform).to eq("darwin")
expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
expect(json_response["variables"]).to include(
- { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }
)
end
@@ -182,9 +182,9 @@ describe Ci::API::Builds do
expect(response).to have_http_status(201)
expect(json_response["variables"]).to include(
- { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
- { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
+ { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
{ "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false },
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 8459a3d8cfb..a969829a63e 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -200,7 +200,7 @@ describe Ci::CreatePipelineService, services: true do
context 'with environment' do
before do
- config = YAML.dump(deploy: { environment: { name: "review/$CI_BUILD_REF_NAME" }, script: 'ls' })
+ config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
stub_ci_pipeline_yaml_file(config)
end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 18b964e2453..a883705bd45 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -104,16 +104,16 @@ describe CreateDeploymentService, services: true do
context 'when variables are used' do
let(:params) do
{
- environment: 'review-apps/$CI_BUILD_REF_NAME',
+ environment: 'review-apps/$CI_COMMIT_REF_NAME',
ref: 'master',
tag: false,
sha: '97de212e80737a608d939f648d959671fb0a0142',
options: {
- name: 'review-apps/$CI_BUILD_REF_NAME',
- url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com'
+ name: 'review-apps/$CI_COMMIT_REF_NAME',
+ url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com'
},
variables: [
- { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' }
+ { key: 'CI_COMMIT_REF_NAME', value: 'feature-review-apps' }
]
}
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index ebbaea4e59a..82a4ec3f581 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -146,16 +146,6 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
- it "emails the note author if they've opted into notifications about their activity" do
- add_users_with_subscription(note.project, issue)
- note.author.notified_of_own_activity = true
- reset_delivered_emails!
-
- notification.new_note(note)
-
- should_email(note.author)
- end
-
it 'filters out "mentioned in" notes' do
mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
@@ -486,20 +476,6 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
- it "emails the author if they've opted into notifications about their activity" do
- issue.author.notified_of_own_activity = true
-
- notification.new_issue(issue, issue.author)
-
- should_email(issue.author)
- end
-
- it "doesn't email the author if they haven't opted into notifications about their activity" do
- notification.new_issue(issue, issue.author)
-
- should_not_email(issue.author)
- end
-
it "emails subscribers of the issue's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -689,19 +665,6 @@ describe NotificationService, services: true do
should_email(subscriber_to_label_2)
end
- it "emails the current user if they've opted into notifications about their activity" do
- subscriber_to_label_2.notified_of_own_activity = true
- notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
-
- should_email(subscriber_to_label_2)
- end
-
- it "doesn't email the current user if they haven't opted into notifications about their activity" do
- notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
-
- should_not_email(subscriber_to_label_2)
- end
-
it "doesn't send email to anyone but subscribers of the given labels" do
notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled)
@@ -855,20 +818,6 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
- it "emails the author if they've opted into notifications about their activity" do
- merge_request.author.notified_of_own_activity = true
-
- notification.new_merge_request(merge_request, merge_request.author)
-
- should_email(merge_request.author)
- end
-
- it "doesn't email the author if they haven't opted into notifications about their activity" do
- notification.new_merge_request(merge_request, merge_request.author)
-
- should_not_email(merge_request.author)
- end
-
it "emails subscribers of the merge request's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -1064,14 +1013,6 @@ describe NotificationService, services: true do
should_not_email(@u_watcher)
end
- it "notifies the merger when the pipeline succeeds is false but they've opted into notifications about their activity" do
- merge_request.merge_when_pipeline_succeeds = false
- @u_watcher.notified_of_own_activity = true
- notification.merge_mr(merge_request, @u_watcher)
-
- should_email(@u_watcher)
- end
-
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index a8395cb48ea..3645b73b039 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -298,6 +298,10 @@ describe TodoService, services: true do
expect(second_todo.reload.state?(new_state)).to be true
end
+ it 'returns the updated ids' do
+ expect(service.send(meth, collection, john_doe)).to match_array([first_todo.id, second_todo.id])
+ end
+
describe 'cached counts' do
it 'updates when todos change' do
expect(john_doe.todos.where(state: new_state).count).to eq(0)
@@ -706,7 +710,7 @@ describe TodoService, services: true do
should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE)
end
end
-
+
describe '#mark_todo' do
it 'creates a todo from a merge request' do
service.mark_todo(mr_unassigned, author)
@@ -779,29 +783,27 @@ describe TodoService, services: true do
.to change { todo.reload.state }.from('pending').to('done')
end
- it 'returns the number of updated todos' do # Needed on API
+ it 'returns the ids of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1)
+ expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq([todo.id])
end
context 'when some of the todos are done already' do
- before do
- create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- create(:todo, :mentioned, user: john_doe, target: another_issue, project: project)
- end
+ let!(:first_todo) { create(:todo, :mentioned, user: john_doe, target: issue, project: project) }
+ let!(:second_todo) { create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) }
- it 'returns the number of those still pending' do
+ it 'returns the ids of those still pending' do
TodoService.new.mark_pending_todos_as_done(issue, john_doe)
- expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(1)
+ expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq([second_todo.id])
end
- it 'returns 0 if all are done' do
+ it 'returns an empty array if all are done' do
TodoService.new.mark_pending_todos_as_done(issue, john_doe)
TodoService.new.mark_pending_todos_as_done(another_issue, john_doe)
- expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(0)
+ expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq([])
end
end
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index b507d38f472..ac2c89b3ff9 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -15,9 +15,9 @@ module SimpleCovEnv
def configure_job
SimpleCov.configure do
- if ENV['CI_BUILD_NAME']
- coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}"
- command_name ENV['CI_BUILD_NAME']
+ if ENV['CI_JOB_NAME']
+ coverage_dir "coverage/#{ENV['CI_JOB_NAME']}"
+ command_name ENV['CI_JOB_NAME']
end
if ENV['CI']
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index f2919f20e85..8bc344bfbf6 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -25,7 +25,7 @@ describe 'projects/commit/_commit_box.html.haml' do
render
- expect(rendered).to have_text("Pipeline ##{third_pipeline.id} for #{Commit.truncate_sha(project.commit.sha)} failed")
+ expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed")
end
context 'viewing a commit' do