summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml9
-rw-r--r--.rubocop_todo.yml1
-rw-r--r--CHANGELOG.md14
-rw-r--r--Dangerfile2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock5
-rw-r--r--Gemfile.rails5.lock5
-rw-r--r--app/assets/javascripts/diffs/components/app.vue55
-rw-r--r--app/assets/javascripts/diffs/components/changed_files.vue171
-rw-r--r--app/assets/javascripts/diffs/components/changed_files_dropdown.vue126
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue128
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue10
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue23
-rw-r--r--app/assets/javascripts/diffs/components/file_row_stats.vue30
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue101
-rw-r--r--app/assets/javascripts/diffs/constants.js2
-rw-r--r--app/assets/javascripts/diffs/mixins/changed_files.js38
-rw-r--r--app/assets/javascripts/diffs/store/actions.js19
-rw-r--r--app/assets/javascripts/diffs/store/getters.js4
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js7
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js14
-rw-r--r--app/assets/javascripts/diffs/store/utils.js46
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js6
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js17
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js40
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js27
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js54
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_finder/item.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue2
-rw-r--r--app/assets/javascripts/job.js23
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue68
-rw-r--r--app/assets/javascripts/jobs/components/header.vue95
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue99
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue30
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue297
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue276
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue116
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js33
-rw-r--r--app/assets/javascripts/jobs/store/actions.js28
-rw-r--r--app/assets/javascripts/jobs/store/getters.js42
-rw-r--r--app/assets/javascripts/jobs/store/index.js2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js12
-rw-r--r--app/assets/javascripts/merge_request_tabs.js16
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue3
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js3
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue8
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue46
-rw-r--r--app/assets/javascripts/sidebar/mount_milestone_sidebar.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue (renamed from app/assets/javascripts/ide/components/changed_file_icon.vue)30
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue29
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss11
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss23
-rw-r--r--app/assets/stylesheets/pages/builds.scss23
-rw-r--r--app/assets/stylesheets/pages/commits.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss62
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss11
-rw-r--r--app/finders/issuable_finder.rb6
-rw-r--r--app/finders/merge_requests_finder.rb27
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/wiki_page.rb7
-rw-r--r--app/uploaders/gitlab_uploader.rb10
-rw-r--r--app/uploaders/job_artifact_uploader.rb2
-rw-r--r--app/uploaders/legacy_artifact_uploader.rb2
-rw-r--r--app/uploaders/lfs_object_uploader.rb2
-rw-r--r--app/views/admin/applications/show.html.haml21
-rw-r--r--app/views/doorkeeper/applications/show.html.haml19
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml38
-rw-r--r--app/views/projects/jobs/show.html.haml50
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml28
-rw-r--r--changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml5
-rw-r--r--changelogs/unreleased/48004-db-initialize-migrate.yml5
-rw-r--r--changelogs/unreleased/50904-vuex-show-block.yml5
-rw-r--r--changelogs/unreleased/51522-add-new-project-via-import-by-url-auto-populates-slug-but-not-project-name.yml5
-rw-r--r--changelogs/unreleased/51549-runners-table.yml5
-rw-r--r--changelogs/unreleased/51747-gitlab-com-unable-to-import-a-project-that-was-just-exported.yml5
-rw-r--r--changelogs/unreleased/51782-fix_rename_login_namespace_migration.yml5
-rw-r--r--changelogs/unreleased/_acet-fix-placeholder-note.yml5
-rw-r--r--changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml5
-rw-r--r--changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml5
-rw-r--r--changelogs/unreleased/ccr-wip_filter.yml5
-rw-r--r--changelogs/unreleased/feature-flags-mvc.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-data.yml5
-rw-r--r--changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml5
-rw-r--r--config/initializers/8_metrics.rb12
-rw-r--r--config/initializers/gollum.rb28
-rw-r--r--danger/eslint/Dangerfile29
-rw-r--r--danger/prettier/Dangerfile39
-rw-r--r--doc/api/merge_requests.md1
-rw-r--r--doc/ci/review_apps/img/continuous-delivery-review-apps.svg48
-rw-r--r--doc/ci/review_apps/img/review_apps_preview_in_mr.pngbin11664 -> 29800 bytes
-rw-r--r--doc/ci/review_apps/index.md163
-rw-r--r--doc/development/gitaly.md57
-rw-r--r--doc/development/instrumentation.md4
-rw-r--r--doc/topics/autodevops/index.md27
-rw-r--r--doc/user/project/integrations/jira.md236
-rw-r--r--doc/user/project/merge_requests/img/filter_wip_merge_requests.pngbin0 -> 17346 bytes
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md11
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md4
-rw-r--r--lib/api/commits.rb3
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml73
-rw-r--r--lib/gitlab/git/commit.rb12
-rw-r--r--lib/gitlab/git/ref.rb9
-rw-r--r--lib/gitlab/git/repository.rb39
-rw-r--r--lib/gitlab/git/wiki.rb29
-rw-r--r--lib/gitlab/git/wiki_file.rb17
-rw-r--r--lib/gitlab/git/wiki_page.rb28
-rw-r--r--lib/gitlab/git/wiki_page_version.rb5
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb2
-rw-r--r--lib/gitlab/usage_data.rb7
-rw-r--r--lib/gitlab/web_ide_commits_counter.rb17
-rw-r--r--locale/gitlab.pot49
-rwxr-xr-xscripts/lint-rugged14
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb18
-rw-r--r--spec/features/markdown/markdown_spec.rb6
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb18
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb10
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb2
-rw-r--r--spec/features/profiles/user_manages_applications_spec.rb4
-rw-r--r--spec/features/projects/environments/environment_spec.rb12
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb14
-rw-r--r--spec/features/projects/jobs_spec.rb166
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb13
-rw-r--r--spec/finders/merge_requests_finder_spec.rb56
-rw-r--r--spec/javascripts/diffs/components/app_spec.js18
-rw-r--r--spec/javascripts/diffs/components/changed_files_spec.js105
-rw-r--r--spec/javascripts/diffs/components/file_row_stats_spec.js33
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js120
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js87
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js27
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js41
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js109
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js5
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js8
-rw-r--r--spec/javascripts/ide/components/file_row_extra_spec.js8
-rw-r--r--spec/javascripts/ide/components/repo_tab_spec.js4
-rw-r--r--spec/javascripts/job_spec.js19
-rw-r--r--spec/javascripts/jobs/components/environments_block_spec.js59
-rw-r--r--spec/javascripts/jobs/components/header_spec.js98
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js185
-rw-r--r--spec/javascripts/jobs/components/jobs_container_spec.js23
-rw-r--r--spec/javascripts/jobs/components/sidebar_details_block_spec.js139
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js196
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js32
-rw-r--r--spec/javascripts/jobs/mock_data.js1047
-rw-r--r--spec/javascripts/jobs/store/actions_spec.js54
-rw-r--r--spec/javascripts/jobs/store/getters_spec.js121
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js25
-rw-r--r--spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js33
-rw-r--r--spec/javascripts/vue_shared/components/changed_file_icon_spec.js (renamed from spec/javascripts/ide/components/changed_file_icon_spec.js)12
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb8
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb4
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb17
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb8
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb31
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb61
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb214
-rw-r--r--spec/lib/gitlab/git_access_spec.rb29
-rw-r--r--spec/lib/gitlab/gitaly_client/wiki_service_spec.rb4
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb1
-rw-r--r--spec/lib/gitlab/web_ide_commits_counter_spec.rb19
-rw-r--r--spec/models/merge_request_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb5
-rw-r--r--spec/models/project_spec.rb5
-rw-r--r--spec/models/remote_mirror_spec.rb6
-rw-r--r--spec/models/repository_spec.rb24
-rw-r--r--spec/models/wiki_page_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb6
-rw-r--r--spec/requests/api/merge_requests_spec.rb38
-rw-r--r--spec/services/git_tag_push_service_spec.rb5
-rw-r--r--spec/services/merge_requests/squash_service_spec.rb6
-rw-r--r--spec/services/projects/after_import_service_spec.rb4
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb6
-rw-r--r--spec/services/projects/transfer_service_spec.rb6
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb13
-rw-r--r--spec/support/helpers/git_helpers.rb6
-rw-r--r--spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb8
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb155
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb6
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb5
-rw-r--r--vendor/licenses.csv1
191 files changed, 4617 insertions, 2653 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9c5b40dce21..2a299ea79ef 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -67,10 +67,7 @@ stages:
.use-pg: &use-pg
services:
- # As of Jan 2018, we don't have a strong reason to upgrade to 9.6 for CI yet,
- # so using the least common denominator ensures backwards compatibility
- # (as many users are still using 9.2).
- - postgres:9.2
+ - postgres:9.6
- redis:alpine
.use-mysql: &use-mysql
@@ -444,10 +441,10 @@ setup-test-env:
- vendor/gitaly-ruby
danger-review:
+ <<: *pull-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test
allow_failure: true
- cache: {}
dependencies: []
before_script: []
only:
@@ -461,6 +458,8 @@ danger-review:
- $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
script:
- git version
+ - node --version
+ - yarn install --frozen-lockfile --cache-folder .yarn-cache
- danger --fail-on-errors=true
rspec-pg 0 30: *rspec-metadata-pg
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index d8c4e965190..d4f7615c80e 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -445,7 +445,6 @@ Style/Dir:
# Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
- - 'config/initializers/gollum.rb'
- 'lib/expand_variables.rb'
- 'lib/gitlab/ci/ansi2html.rb'
- 'lib/gitlab/ee_compat_check.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9ab8599d99..be204a76645 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,20 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.3.2 (2018-10-03)
+
+### Fixed (4 changes)
+
+- Fix NULL pipeline import problem and pipeline user mapping issue. !21875
+- Fix migration to avoid an exception during upgrade. !22055
+- Fixes admin runners table not wrapping content.
+- Fix Error 500 when forking projects with Gravatar disabled.
+
+### Other (1 change)
+
+- Removes the 'required' attribute from the 'project name' field. !21770
+
+
## 11.3.1 (2018-09-26)
### Security (6 changes)
diff --git a/Dangerfile b/Dangerfile
index 46e53edcac4..10caacff4c4 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -7,3 +7,5 @@ danger.import_dangerfile(path: 'danger/database')
danger.import_dangerfile(path: 'danger/documentation')
danger.import_dangerfile(path: 'danger/frozen_string')
danger.import_dangerfile(path: 'danger/commit_messages')
+danger.import_dangerfile(path: 'danger/prettier')
+danger.import_dangerfile(path: 'danger/eslint')
diff --git a/Gemfile b/Gemfile
index ecf5e6392c0..52de588deb3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -80,11 +80,9 @@ gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap'
gem 'net-ldap'
# Git Wiki
-# Required manually in config/initializers/gollum.rb to control load order
+# Only used to compute wiki page slugs
gem 'gitlab-gollum-lib', '~> 4.2', require: false
-gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
-
# Language detection
gem 'github-linguist', '~> 5.3.3', require: 'linguist'
@@ -134,6 +132,7 @@ gem 'seed-fu', '~> 2.3.7'
gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.6.4'
+gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 69a1c899cc5..30c666932d1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -295,9 +295,6 @@ GEM
rouge (~> 3.1)
sanitize (~> 4.6.4)
stringex (~> 2.6)
- gitlab-gollum-rugged_adapter (0.4.4.1)
- mime-types (>= 1.15)
- rugged (~> 0.25)
gitlab-grit (2.8.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
@@ -1030,9 +1027,9 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
github-linguist (~> 5.3.3)
+ github-markup (~> 1.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
- gitlab-gollum-rugged_adapter (~> 0.4.4)
gitlab-markup (~> 1.6.4)
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 30605e460e6..f465a77d0c6 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -298,9 +298,6 @@ GEM
rouge (~> 3.1)
sanitize (~> 4.6.4)
stringex (~> 2.6)
- gitlab-gollum-rugged_adapter (0.4.4.1)
- mime-types (>= 1.15)
- rugged (~> 0.25)
gitlab-grit (2.8.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
@@ -1039,9 +1036,9 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
github-linguist (~> 5.3.3)
+ github-markup (~> 1.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
- gitlab-gollum-rugged_adapter (~> 0.4.4)
gitlab-markup (~> 1.6.4)
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index fc41ee4b777..e60c53338fe 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -5,22 +5,22 @@ import { __ } from '~/locale';
import createFlash from '~/flash';
import eventHub from '../../notes/event_hub';
import CompareVersions from './compare_versions.vue';
-import ChangedFiles from './changed_files.vue';
import DiffFile from './diff_file.vue';
import NoChanges from './no_changes.vue';
import HiddenFilesWarning from './hidden_files_warning.vue';
import CommitWidget from './commit_widget.vue';
+import TreeList from './tree_list.vue';
export default {
name: 'DiffsApp',
components: {
Icon,
CompareVersions,
- ChangedFiles,
DiffFile,
NoChanges,
HiddenFilesWarning,
CommitWidget,
+ TreeList,
},
props: {
endpoint: {
@@ -58,6 +58,7 @@ export default {
plainDiffPath: state => state.diffs.plainDiffPath,
emailPatchPath: state => state.diffs.emailPatchPath,
}),
+ ...mapState('diffs', ['showTreeList']),
...mapGetters('diffs', ['isParallelView']),
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
targetBranch() {
@@ -88,6 +89,9 @@ export default {
canCurrentUserFork() {
return this.currentUser.canFork === true && this.currentUser.canCreateMergeRequest;
},
+ showCompareVersions() {
+ return this.mergeRequestDiffs && this.mergeRequestDiff;
+ },
},
watch: {
diffViewType() {
@@ -102,6 +106,8 @@ export default {
this.adjustView();
},
+ isLoading: 'adjustView',
+ showTreeList: 'adjustView',
},
mounted() {
this.setBaseConfig({ endpoint: this.endpoint, projectPath: this.projectPath });
@@ -152,10 +158,11 @@ export default {
}
},
adjustView() {
- if (this.shouldShow && this.isParallelView) {
- window.mrTabs.expandViewContainer();
- } else {
- window.mrTabs.resetViewContainer();
+ if (this.shouldShow) {
+ this.$nextTick(() => {
+ window.mrTabs.resetViewContainer();
+ window.mrTabs.expandViewContainer(this.showTreeList);
+ });
}
},
},
@@ -177,7 +184,7 @@ export default {
class="diffs tab-pane"
>
<compare-versions
- v-if="!commit && mergeRequestDiffs.length > 1"
+ v-if="showCompareVersions"
:merge-request-diffs="mergeRequestDiffs"
:merge-request-diff="mergeRequestDiff"
:start-version="startVersion"
@@ -215,22 +222,26 @@ export default {
:commit="commit"
/>
- <changed-files
- :diff-files="diffFiles"
- />
-
- <div
- v-if="diffFiles.length > 0"
- class="files"
- >
- <diff-file
- v-for="file in diffFiles"
- :key="file.newPath"
- :file="file"
- :can-current-user-fork="canCurrentUserFork"
- />
+ <div class="files d-flex prepend-top-default">
+ <div
+ v-show="showTreeList"
+ class="diff-tree-list"
+ >
+ <tree-list />
+ </div>
+ <div
+ v-if="diffFiles.length > 0"
+ class="diff-files-holder"
+ >
+ <diff-file
+ v-for="file in diffFiles"
+ :key="file.newPath"
+ :file="file"
+ :can-current-user-fork="canCurrentUserFork"
+ />
+ </div>
+ <no-changes v-else />
</div>
- <no-changes v-else />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/changed_files.vue b/app/assets/javascripts/diffs/components/changed_files.vue
deleted file mode 100644
index 97751db1254..00000000000
--- a/app/assets/javascripts/diffs/components/changed_files.vue
+++ /dev/null
@@ -1,171 +0,0 @@
-<script>
-import { mapGetters, mapActions } from 'vuex';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-import { pluralize } from '~/lib/utils/text_utility';
-import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
-import { contentTop } from '~/lib/utils/common_utils';
-import { __ } from '~/locale';
-import ChangedFilesDropdown from './changed_files_dropdown.vue';
-import changedFilesMixin from '../mixins/changed_files';
-
-export default {
- components: {
- Icon,
- ChangedFilesDropdown,
- ClipboardButton,
- },
- mixins: [changedFilesMixin],
- data() {
- return {
- isStuck: false,
- maxWidth: 'auto',
- offsetTop: 0,
- };
- },
- computed: {
- ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'areAllFilesCollapsed']),
- sumAddedLines() {
- return this.sumValues('addedLines');
- },
- sumRemovedLines() {
- return this.sumValues('removedLines');
- },
- whitespaceVisible() {
- return !getParameterValues('w')[0];
- },
- toggleWhitespaceText() {
- if (this.whitespaceVisible) {
- return __('Hide whitespace changes');
- }
- return __('Show whitespace changes');
- },
- toggleWhitespacePath() {
- if (this.whitespaceVisible) {
- return mergeUrlParams({ w: 1 }, window.location.href);
- }
-
- return mergeUrlParams({ w: 0 }, window.location.href);
- },
- top() {
- return `${this.offsetTop}px`;
- },
- },
- created() {
- document.addEventListener('scroll', this.handleScroll);
- this.offsetTop = contentTop();
- },
- beforeDestroy() {
- document.removeEventListener('scroll', this.handleScroll);
- },
- methods: {
- ...mapActions('diffs', ['setInlineDiffViewType', 'setParallelDiffViewType', 'expandAllFiles']),
- pluralize,
- handleScroll() {
- if (!this.updating) {
- this.$nextTick(this.updateIsStuck);
- this.updating = true;
- }
- },
- updateIsStuck() {
- if (!this.$refs.wrapper) {
- return;
- }
-
- const scrollPosition = window.scrollY;
-
- this.isStuck = scrollPosition + this.offsetTop >= this.$refs.placeholder.offsetTop;
- this.updating = false;
- },
- sumValues(key) {
- return this.diffFiles.reduce((total, file) => total + file[key], 0);
- },
- },
-};
-</script>
-
-<template>
- <span>
- <div ref="placeholder"></div>
- <div
- ref="wrapper"
- :style="{ top }"
- :class="{'is-stuck': isStuck}"
- class="content-block oneline-block diff-files-changed diff-files-changed-merge-request
- files-changed js-diff-files-changed"
- >
- <div class="files-changed-inner">
- <div
- class="inline-parallel-buttons d-none d-md-block"
- >
- <a
- v-if="areAllFilesCollapsed"
- class="btn btn-default"
- @click="expandAllFiles"
- >
- {{ __('Expand all') }}
- </a>
- <a
- :href="toggleWhitespacePath"
- class="btn btn-default"
- >
- {{ toggleWhitespaceText }}
- </a>
- <div class="btn-group">
- <button
- id="inline-diff-btn"
- :class="{ active: isInlineView }"
- type="button"
- class="btn js-inline-diff-button"
- data-view-type="inline"
- @click="setInlineDiffViewType"
- >
- {{ __('Inline') }}
- </button>
- <button
- id="parallel-diff-btn"
- :class="{ active: isParallelView }"
- type="button"
- class="btn js-parallel-diff-button"
- data-view-type="parallel"
- @click="setParallelDiffViewType"
- >
- {{ __('Side-by-side') }}
- </button>
- </div>
- </div>
-
- <div class="commit-stat-summary dropdown">
- <changed-files-dropdown
- :diff-files="diffFiles"
- />
-
- <span
- class="js-diff-stats-additions-deletions-expanded
- diff-stats-additions-deletions-expanded"
- >
- with
- <strong class="cgreen">
- {{ pluralize(`${sumAddedLines} addition`, sumAddedLines) }}
- </strong>
- and
- <strong class="cred">
- {{ pluralize(`${sumRemovedLines} deletion`, sumRemovedLines) }}
- </strong>
- </span>
- <div
- class="js-diff-stats-additions-deletions-collapsed
- diff-stats-additions-deletions-collapsed float-right d-sm-none"
- >
- <strong class="cgreen">
- +{{ sumAddedLines }}
- </strong>
- <strong class="cred">
- -{{ sumRemovedLines }}
- </strong>
- </div>
- </div>
- </div>
- </div>
- </span>
-</template>
diff --git a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
deleted file mode 100644
index 0ec6b8b7f21..00000000000
--- a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-<script>
-import Icon from '~/vue_shared/components/icon.vue';
-import changedFilesMixin from '../mixins/changed_files';
-
-export default {
- components: {
- Icon,
- },
- mixins: [changedFilesMixin],
- data() {
- return {
- searchText: '',
- };
- },
- computed: {
- filteredDiffFiles() {
- return this.diffFiles.filter(file =>
- file.filePath.toLowerCase().includes(this.searchText.toLowerCase()),
- );
- },
- },
- methods: {
- clearSearch() {
- this.searchText = '';
- },
- },
-};
-</script>
-
-<template>
- <span>
- Showing
- <button
- class="diff-stats-summary-toggler"
- data-toggle="dropdown"
- type="button"
- aria-expanded="false"
- >
- <span>
- {{ n__('%d changed file', '%d changed files', diffFiles.length) }}
- </span>
- <icon
- class="caret-icon"
- name="chevron-down"
- />
- </button>
- <div class="dropdown-menu diff-file-changes">
- <div class="dropdown-input">
- <input
- v-model="searchText"
- type="search"
- class="dropdown-input-field"
- placeholder="Search files"
- autocomplete="off"
- />
- <i
- v-if="searchText.length === 0"
- aria-hidden="true"
- data-hidden="true"
- class="fa fa-search dropdown-input-search">
- </i>
- <i
- v-else
- role="button"
- class="fa fa-times dropdown-input-search"
- @click.stop.prevent="clearSearch"
- ></i>
- </div>
- <div class="dropdown-content">
- <ul>
- <li
- v-for="diffFile in filteredDiffFiles"
- :key="diffFile.name"
- >
- <a
- :href="`#${diffFile.fileHash}`"
- :title="diffFile.newPath"
- class="diff-changed-file"
- >
- <icon
- :name="fileChangedIcon(diffFile)"
- :size="16"
- :class="fileChangedClass(diffFile)"
- class="diff-file-changed-icon append-right-8"
- />
- <span class="diff-changed-file-content append-right-8">
- <strong
- v-if="diffFile.blob && diffFile.blob.name"
- class="diff-changed-file-name"
- >
- {{ diffFile.blob.name }}
- </strong>
- <strong
- v-else
- class="diff-changed-blank-file-name"
- >
- {{ s__('Diffs|No file name available') }}
- </strong>
- <span class="diff-changed-file-path prepend-top-5">
- {{ truncatedDiffPath(diffFile.blob.path) }}
- </span>
- </span>
- <span class="diff-changed-stats">
- <span class="cgreen">
- +{{ diffFile.addedLines }}
- </span>
- <span class="cred">
- -{{ diffFile.removedLines }}
- </span>
- </span>
- </a>
- </li>
-
- <li
- v-show="filteredDiffFiles.length === 0"
- class="dropdown-menu-empty-item"
- >
- <a>
- {{ __('No files found') }}
- </a>
- </li>
- </ul>
- </div>
- </div>
- </span>
-</template>
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 1c9ad8e77f1..9bbf62c0eb6 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -1,9 +1,18 @@
<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import Tooltip from '@gitlab-org/gitlab-ui/dist/directives/tooltip';
+import { __ } from '~/locale';
+import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
+import Icon from '~/vue_shared/components/icon.vue';
import CompareVersionsDropdown from './compare_versions_dropdown.vue';
export default {
components: {
CompareVersionsDropdown,
+ Icon,
+ },
+ directives: {
+ Tooltip,
},
props: {
mergeRequestDiffs: {
@@ -26,30 +35,119 @@ export default {
},
},
computed: {
+ ...mapState('diffs', ['commit', 'showTreeList']),
+ ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'areAllFilesCollapsed']),
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
+ isWhitespaceVisible() {
+ return !getParameterValues('w')[0];
+ },
+ toggleWhitespaceText() {
+ if (this.isWhitespaceVisible) {
+ return __('Hide whitespace changes');
+ }
+ return __('Show whitespace changes');
+ },
+ toggleWhitespacePath() {
+ if (this.isWhitespaceVisible) {
+ return mergeUrlParams({ w: 1 }, window.location.href);
+ }
+
+ return mergeUrlParams({ w: 0 }, window.location.href);
+ },
+ showDropdowns() {
+ return !this.commit && this.mergeRequestDiffs.length;
+ },
+ },
+ methods: {
+ ...mapActions('diffs', [
+ 'setInlineDiffViewType',
+ 'setParallelDiffViewType',
+ 'expandAllFiles',
+ 'toggleShowTreeList',
+ ]),
},
};
</script>
<template>
<div class="mr-version-controls">
- <div class="mr-version-menus-container content-block">
- Changes between
- <compare-versions-dropdown
- :other-versions="mergeRequestDiffs"
- :merge-request-version="mergeRequestDiff"
- :show-commit-count="true"
- class="mr-version-dropdown"
- />
- and
- <compare-versions-dropdown
- :other-versions="comparableDiffs"
- :start-version="startVersion"
- :target-branch="targetBranch"
- class="mr-version-compare-dropdown"
- />
+ <div
+ class="mr-version-menus-container content-block"
+ >
+ <button
+ v-tooltip.hover
+ type="button"
+ class="btn btn-default append-right-8 js-toggle-tree-list"
+ :class="{
+ active: showTreeList
+ }"
+ :title="__('Toggle file browser')"
+ @click="toggleShowTreeList"
+ >
+ <icon
+ name="hamburger"
+ />
+ </button>
+ <div
+ v-if="showDropdowns"
+ class="d-flex align-items-center compare-versions-container"
+ >
+ Changes between
+ <compare-versions-dropdown
+ :other-versions="mergeRequestDiffs"
+ :merge-request-version="mergeRequestDiff"
+ :show-commit-count="true"
+ class="mr-version-dropdown"
+ />
+ and
+ <compare-versions-dropdown
+ :other-versions="comparableDiffs"
+ :start-version="startVersion"
+ :target-branch="targetBranch"
+ class="mr-version-compare-dropdown"
+ />
+ </div>
+ <div
+ class="inline-parallel-buttons d-none d-md-flex ml-auto"
+ >
+ <a
+ v-if="areAllFilesCollapsed"
+ class="btn btn-default"
+ @click="expandAllFiles"
+ >
+ {{ __('Expand all') }}
+ </a>
+ <a
+ :href="toggleWhitespacePath"
+ class="btn btn-default"
+ >
+ {{ toggleWhitespaceText }}
+ </a>
+ <div class="btn-group prepend-left-8">
+ <button
+ id="inline-diff-btn"
+ :class="{ active: isInlineView }"
+ type="button"
+ class="btn js-inline-diff-button"
+ data-view-type="inline"
+ @click="setInlineDiffViewType"
+ >
+ {{ __('Inline') }}
+ </button>
+ <button
+ id="parallel-diff-btn"
+ :class="{ active: isParallelView }"
+ type="button"
+ class="btn js-parallel-diff-button"
+ data-view-type="parallel"
+ @click="setParallelDiffViewType"
+ >
+ {{ __('Side-by-side') }}
+ </button>
+ </div>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index 96cccb49378..c3acc352d5e 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -108,7 +108,7 @@ export default {
<template>
<span class="dropdown inline">
<a
- class="dropdown-toggle btn btn-default"
+ class="dropdown-menu-toggle btn btn-default w-100"
data-toggle="dropdown"
aria-expanded="false"
>
@@ -118,6 +118,7 @@ export default {
<Icon
:size="12"
name="angle-down"
+ class="position-absolute"
/>
</a>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable">
@@ -163,3 +164,10 @@ export default {
</div>
</span>
</template>
+
+<style>
+.dropdown {
+ min-width: 0;
+ max-height: 170px;
+}
+</style>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index bcbe374a90c..4e04e50c52a 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -1,5 +1,5 @@
<script>
-import { mapActions, mapGetters } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { __, sprintf } from '~/locale';
import createFlash from '~/flash';
@@ -28,6 +28,7 @@ export default {
};
},
computed: {
+ ...mapState('diffs', ['currentDiffFileId']),
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
isCollapsed() {
return this.file.collapsed || false;
@@ -101,6 +102,9 @@ export default {
<template>
<div
:id="file.fileHash"
+ :class="{
+ 'is-active': currentDiffFileId === file.fileHash
+ }"
class="diff-file file-holder"
>
<diff-file-header
@@ -168,3 +172,20 @@ export default {
</div>
</div>
</template>
+
+<style>
+@keyframes shadow-fade {
+ from {
+ box-shadow: 0 0 4px #919191;
+ }
+
+ to {
+ box-shadow: 0 0 0 #dfdfdf;
+ }
+}
+
+.diff-file.is-active {
+ box-shadow: 0 0 0 #dfdfdf;
+ animation: shadow-fade 1.2s 0.1s 1;
+}
+</style>
diff --git a/app/assets/javascripts/diffs/components/file_row_stats.vue b/app/assets/javascripts/diffs/components/file_row_stats.vue
new file mode 100644
index 00000000000..105f7ebdbed
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/file_row_stats.vue
@@ -0,0 +1,30 @@
+<script>
+export default {
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <span
+ v-once
+ class="file-row-stats"
+ >
+ <span class="cgreen">
+ +{{ file.addedLines }}
+ </span>
+ <span class="cred">
+ -{{ file.removedLines }}
+ </span>
+ </span>
+</template>
+
+<style>
+.file-row-stats {
+ font-size: 12px;
+}
+</style>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
new file mode 100644
index 00000000000..cfe4273742f
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -0,0 +1,101 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import FileRow from '~/vue_shared/components/file_row.vue';
+import FileRowStats from './file_row_stats.vue';
+
+export default {
+ components: {
+ Icon,
+ FileRow,
+ },
+ data() {
+ return {
+ search: '',
+ };
+ },
+ computed: {
+ ...mapState('diffs', ['tree', 'addedLines', 'removedLines']),
+ ...mapGetters('diffs', ['allBlobs', 'diffFilesLength']),
+ filteredTreeList() {
+ const search = this.search.toLowerCase().trim();
+
+ if (search === '') return this.tree;
+
+ return this.allBlobs.filter(f => f.name.toLowerCase().indexOf(search) >= 0);
+ },
+ },
+ methods: {
+ ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
+ clearSearch() {
+ this.search = '';
+ },
+ },
+ FileRowStats,
+};
+</script>
+
+<template>
+ <div class="tree-list-holder d-flex flex-column">
+ <div class="append-bottom-8 position-relative tree-list-search">
+ <icon
+ name="search"
+ class="position-absolute tree-list-icon"
+ />
+ <input
+ v-model="search"
+ :placeholder="s__('MergeRequest|Filter files')"
+ type="search"
+ class="form-control"
+ />
+ <button
+ v-show="search"
+ :aria-label="__('Clear search')"
+ type="button"
+ class="position-absolute tree-list-icon tree-list-clear-icon border-0 p-0"
+ @click="clearSearch"
+ >
+ <icon
+ name="close"
+ />
+ </button>
+ </div>
+ <div
+ class="tree-list-scroll"
+ >
+ <template v-if="filteredTreeList.length">
+ <file-row
+ v-for="file in filteredTreeList"
+ :key="file.key"
+ :file="file"
+ :level="0"
+ :hide-extra-on-tree="true"
+ :extra-component="$options.FileRowStats"
+ :show-changed-icon="true"
+ @toggleTreeOpen="toggleTreeOpen"
+ @clickFile="scrollToFile"
+ />
+ </template>
+ <p
+ v-else
+ class="prepend-top-20 append-bottom-20 text-center"
+ >
+ {{ s__('MergeRequest|No files found') }}
+ </p>
+ </div>
+ <div
+ v-once
+ class="pt-3 pb-3 text-center"
+ >
+ {{ n__('%d changed file', '%d changed files', diffFilesLength) }}
+ <div>
+ <span class="cgreen">
+ {{ n__('%d addition', '%d additions', addedLines) }}
+ </span>
+ <span class="cred">
+ {{ n__('%d deleted', '%d deletions', removedLines) }}
+ </span>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 2795dddfc48..6a50d2c1426 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -29,3 +29,5 @@ export const LENGTH_OF_AVATAR_TOOLTIP = 17;
export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
export const MAX_LINES_TO_BE_RENDERED = 2000;
+
+export const MR_TREE_SHOW_KEY = 'mr_tree_show';
diff --git a/app/assets/javascripts/diffs/mixins/changed_files.js b/app/assets/javascripts/diffs/mixins/changed_files.js
deleted file mode 100644
index da1339f0ffa..00000000000
--- a/app/assets/javascripts/diffs/mixins/changed_files.js
+++ /dev/null
@@ -1,38 +0,0 @@
-export default {
- props: {
- diffFiles: {
- type: Array,
- required: true,
- },
- },
- methods: {
- fileChangedIcon(diffFile) {
- if (diffFile.deletedFile) {
- return 'file-deletion';
- } else if (diffFile.newFile) {
- return 'file-addition';
- }
- return 'file-modified';
- },
- fileChangedClass(diffFile) {
- if (diffFile.deletedFile) {
- return 'cred';
- } else if (diffFile.newFile) {
- return 'cgreen';
- }
-
- return '';
- },
- truncatedDiffPath(path) {
- const maxLength = 60;
-
- if (path.length > maxLength) {
- const start = path.length - maxLength;
- const end = start + maxLength;
- return `...${path.slice(start, end)}`;
- }
-
- return path;
- },
- },
-};
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 98d8d5943f9..1e0b27b538d 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -12,6 +12,7 @@ import {
PARALLEL_DIFF_VIEW_TYPE,
INLINE_DIFF_VIEW_TYPE,
DIFF_VIEW_COOKIE_NAME,
+ MR_TREE_SHOW_KEY,
} from '../constants';
export const setBaseConfig = ({ commit }, options) => {
@@ -195,5 +196,23 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
.catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
};
+export const toggleTreeOpen = ({ commit }, path) => {
+ commit(types.TOGGLE_FOLDER_OPEN, path);
+};
+
+export const scrollToFile = ({ state, commit }, path) => {
+ const { fileHash } = state.treeEntries[path];
+ document.location.hash = fileHash;
+
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
+
+ setTimeout(() => commit(types.UPDATE_CURRENT_DIFF_FILE_ID, ''), 1000);
+};
+
+export const toggleShowTreeList = ({ commit, state }) => {
+ commit(types.TOGGLE_SHOW_TREE_LIST);
+ localStorage.setItem(MR_TREE_SHOW_KEY, state.showTreeList);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index 968ba3c5e13..d4c205882ff 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -110,5 +110,9 @@ export const shouldRenderInlineCommentRow = state => line => {
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.fileHash === fileHash);
+export const allBlobs = state => Object.values(state.treeEntries).filter(f => f.type === 'blob');
+
+export const diffFilesLength = state => state.diffFiles.length;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index eb596b251c1..ae8930c8968 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,10 +1,11 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
-import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants';
+import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
+const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
export default () => ({
isLoading: true,
@@ -17,4 +18,8 @@ export default () => ({
mergeRequestDiff: null,
diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
+ tree: [],
+ treeEntries: {},
+ showTreeList: storedTreeShow === null ? true : storedTreeShow === 'true',
+ currentDiffFileId: '',
});
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index f61efbe6e1e..6474ee628e2 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -11,3 +11,6 @@ export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
export const RENDER_FILE = 'RENDER_FILE';
export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE';
export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE';
+export const TOGGLE_FOLDER_OPEN = 'TOGGLE_FOLDER_OPEN';
+export const TOGGLE_SHOW_TREE_LIST = 'TOGGLE_SHOW_TREE_LIST';
+export const UPDATE_CURRENT_DIFF_FILE_ID = 'UPDATE_CURRENT_DIFF_FILE_ID';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 59a2c09e54f..0b4485ecdb5 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { sortTree } from '~/ide/stores/utils';
import {
findDiffFile,
addLineReferences,
@@ -7,6 +8,7 @@ import {
addContextLines,
prepareDiffData,
isDiscussionApplicableToLine,
+ generateTreeList,
} from './utils';
import * as types from './mutation_types';
@@ -23,9 +25,12 @@ export default {
[types.SET_DIFF_DATA](state, data) {
const diffData = convertObjectPropsToCamelCase(data, { deep: true });
prepareDiffData(diffData);
+ const { tree, treeEntries } = generateTreeList(diffData.diffFiles);
Object.assign(state, {
...diffData,
+ tree: sortTree(tree),
+ treeEntries,
});
},
@@ -163,4 +168,13 @@ export default {
}
}
},
+ [types.TOGGLE_FOLDER_OPEN](state, path) {
+ state.treeEntries[path].opened = !state.treeEntries[path].opened;
+ },
+ [types.TOGGLE_SHOW_TREE_LIST](state) {
+ state.showTreeList = !state.showTreeList;
+ },
+ [types.UPDATE_CURRENT_DIFF_FILE_ID](state, fileId) {
+ state.currentDiffFileId = fileId;
+ },
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 631e3de311e..4ae588042e4 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -267,3 +267,49 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
return latestDiff && discussion.active && lineCode === discussion.line_code;
}
+
+export const generateTreeList = files =>
+ files.reduce(
+ (acc, file) => {
+ const { fileHash, addedLines, removedLines, newFile, deletedFile, newPath } = file;
+ const split = newPath.split('/');
+
+ split.forEach((name, i) => {
+ const parent = acc.treeEntries[split.slice(0, i).join('/')];
+ const path = `${parent ? `${parent.path}/` : ''}${name}`;
+
+ if (!acc.treeEntries[path]) {
+ const type = path === newPath ? 'blob' : 'tree';
+ acc.treeEntries[path] = {
+ key: path,
+ path,
+ name,
+ type,
+ tree: [],
+ };
+
+ const entry = acc.treeEntries[path];
+
+ if (type === 'blob') {
+ Object.assign(entry, {
+ changed: true,
+ tempFile: newFile,
+ deleted: deletedFile,
+ fileHash,
+ addedLines,
+ removedLines,
+ });
+ } else {
+ Object.assign(entry, {
+ opened: true,
+ });
+ }
+
+ (parent ? parent.tree : acc.tree).push(entry);
+ }
+ });
+
+ return acc;
+ },
+ { treeEntries: {}, tree: [] },
+ );
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 8aecf9725e6..c568f4e4ebf 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -51,7 +51,11 @@ export default class DropdownHint extends FilteredSearchDropdown {
FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
- FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
+ const key = token.replace(':', '');
+ const { uppercaseTokenName } = this.tokenKeys.searchByKey(key);
+ FilteredSearchDropdownManager.addWordToInput(key, '', false, {
+ uppercaseTokenName,
+ });
}
this.dismissDropdown();
this.dispatchInputEvent();
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 27fff488603..6da6ca10008 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -143,7 +143,9 @@ export default class DropdownUtils {
const dataValue = selected.getAttribute('data-value');
if (dataValue) {
- FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
+ FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true, {
+ capitalizeTokenValue: selected.hasAttribute('data-capitalize'),
+ });
}
// Return boolean based on whether it was set
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 207616b9de2..cd3d532c958 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -91,6 +91,11 @@ export default class FilteredSearchDropdownManager {
gl: DropdownEmoji,
element: this.container.querySelector('#js-dropdown-my-reaction'),
},
+ wip: {
+ reference: null,
+ gl: DropdownNonUser,
+ element: this.container.querySelector('#js-dropdown-wip'),
+ },
status: {
reference: null,
gl: NullDropdown,
@@ -136,10 +141,16 @@ export default class FilteredSearchDropdownManager {
return endpoint;
}
- static addWordToInput(tokenName, tokenValue = '', clicked = false) {
+ static addWordToInput(tokenName, tokenValue = '', clicked = false, options = {}) {
+ const {
+ uppercaseTokenName = false,
+ capitalizeTokenValue = false,
+ } = options;
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
-
- FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
+ FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue, {
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ });
input.value = '';
if (clicked) {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index d25f6f95b22..54533ebb70d 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -405,7 +405,10 @@ export default class FilteredSearchManager {
if (isLastVisualTokenValid) {
tokens.forEach((t) => {
input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
- FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
+ FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`, {
+ uppercaseTokenName: this.filteredSearchTokenKeys.shouldUppercaseTokenName(t.key),
+ capitalizeTokenValue: this.filteredSearchTokenKeys.shouldCapitalizeTokenValue(t.key),
+ });
});
const fragments = searchToken.split(':');
@@ -421,7 +424,10 @@ export default class FilteredSearchManager {
FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
}
- FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
+ FilteredSearchVisualTokens.addFilterVisualToken(tokenKey, null, {
+ uppercaseTokenName: this.filteredSearchTokenKeys.shouldUppercaseTokenName(tokenKey),
+ capitalizeTokenValue: this.filteredSearchTokenKeys.shouldCapitalizeTokenValue(tokenKey),
+ });
input.value = input.value.replace(`${tokenKey}:`, '');
}
} else {
@@ -429,7 +435,10 @@ export default class FilteredSearchManager {
const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
- FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
+ const tokenKey = FilteredSearchVisualTokens.getLastTokenPartial();
+ FilteredSearchVisualTokens.addFilterVisualToken(searchToken, null, {
+ capitalizeTokenValue: this.filteredSearchTokenKeys.shouldCapitalizeTokenValue(tokenKey),
+ });
// Trim the last space as seen in the if statement above
input.value = input.value.replace(searchToken, '').trim();
@@ -480,7 +489,7 @@ export default class FilteredSearchManager {
FilteredSearchVisualTokens.addFilterVisualToken(
condition.tokenKey,
condition.value,
- canEdit,
+ { canEdit },
);
} else {
// Sanitize value since URL converts spaces into +
@@ -506,10 +515,15 @@ export default class FilteredSearchManager {
hasFilteredSearch = true;
const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
+ const { uppercaseTokenName, capitalizeTokenValue } = match;
FilteredSearchVisualTokens.addFilterVisualToken(
sanitizedKey,
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
- canEdit,
+ {
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ },
);
} else if (!match && keyParam === 'assignee_id') {
const id = parseInt(value, 10);
@@ -517,7 +531,7 @@ export default class FilteredSearchManager {
hasFilteredSearch = true;
const tokenName = 'assignee';
const canEdit = this.canEdit && this.canEdit(tokenName);
- FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
+ FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, { canEdit });
}
} else if (!match && keyParam === 'author_id') {
const id = parseInt(value, 10);
@@ -525,7 +539,7 @@ export default class FilteredSearchManager {
hasFilteredSearch = true;
const tokenName = 'author';
const canEdit = this.canEdit && this.canEdit(tokenName);
- FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
+ FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, { canEdit });
}
} else if (!match && keyParam === 'search') {
hasFilteredSearch = true;
@@ -561,15 +575,17 @@ export default class FilteredSearchManager {
this.saveCurrentSearchQuery();
- const { tokens, searchToken }
- = this.tokenizer.processTokens(searchQuery, this.filteredSearchTokenKeys.getKeys());
+ const tokenKeys = this.filteredSearchTokenKeys.getKeys();
+ const { tokens, searchToken } = this.tokenizer.processTokens(searchQuery, tokenKeys);
const currentState = state || getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
tokens.forEach((token) => {
const condition = this.filteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
- const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
+ const tokenConfig = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
+ const { param } = tokenConfig;
+
// Replace hyphen with underscore to use as request parameter
// e.g. 'my-reaction' => 'my_reaction'
const underscoredKey = token.key.replace('-', '_');
@@ -581,6 +597,10 @@ export default class FilteredSearchManager {
} else {
let tokenValue = token.value;
+ if (tokenConfig.lowercaseValueOnSubmit) {
+ tokenValue = tokenValue.toLowerCase();
+ }
+
if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') ||
(tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) {
tokenValue = tokenValue.slice(1, tokenValue.length - 1);
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 5d131b396a0..a09ad3e4758 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -23,6 +23,16 @@ export default class FilteredSearchTokenKeys {
return this.conditions;
}
+ shouldUppercaseTokenName(tokenKey) {
+ const token = this.searchByKey(tokenKey.toLowerCase());
+ return token && token.uppercaseTokenName;
+ }
+
+ shouldCapitalizeTokenValue(tokenKey) {
+ const token = this.searchByKey(tokenKey.toLowerCase());
+ return token && token.capitalizeTokenValue;
+ }
+
searchByKey(key) {
return this.tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
@@ -55,4 +65,21 @@ export default class FilteredSearchTokenKeys {
return this.conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
+
+ addExtraTokensForMergeRequests() {
+ const wipToken = {
+ key: 'wip',
+ type: 'string',
+ param: '',
+ symbol: '',
+ icon: 'admin',
+ tag: 'Yes or No',
+ lowercaseValueOnSubmit: true,
+ uppercaseTokenName: true,
+ capitalizeTokenValue: true,
+ };
+
+ this.tokenKeys.push(wipToken);
+ this.tokenKeysWithAlternative.push(wipToken);
+ }
}
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 56fe1ab4e90..0854c1822fb 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -55,12 +55,18 @@ export default class FilteredSearchVisualTokens {
}
}
- static createVisualTokenElementHTML(canEdit = true) {
+ static createVisualTokenElementHTML(options = {}) {
+ const {
+ canEdit = true,
+ uppercaseTokenName = false,
+ capitalizeTokenValue = false,
+ } = options;
+
return `
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
- <div class="name"></div>
+ <div class="${uppercaseTokenName ? 'text-uppercase' : ''} name"></div>
<div class="value-container">
- <div class="value"></div>
+ <div class="${capitalizeTokenValue ? 'text-capitalize' : ''} value"></div>
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
@@ -182,16 +188,26 @@ export default class FilteredSearchVisualTokens {
}
}
- static addVisualTokenElement(name, value, isSearchTerm, canEdit) {
+ static addVisualTokenElement(name, value, options = {}) {
+ const {
+ isSearchTerm = false,
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ } = options;
const li = document.createElement('li');
li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
if (value) {
- li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML(canEdit);
+ li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML({
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ });
FilteredSearchVisualTokens.renderVisualTokenValue(li, name, value);
} else {
- li.innerHTML = '<div class="name"></div>';
+ li.innerHTML = `<div class="${uppercaseTokenName ? 'text-uppercase' : ''} name"></div>`;
}
li.querySelector('.name').innerText = name;
@@ -212,20 +228,32 @@ export default class FilteredSearchVisualTokens {
}
}
- static addFilterVisualToken(tokenName, tokenValue, canEdit) {
+ static addFilterVisualToken(tokenName, tokenValue, {
+ canEdit,
+ uppercaseTokenName = false,
+ capitalizeTokenValue = false,
+ } = {}) {
const { lastVisualToken, isLastVisualTokenValid }
= FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const { addVisualTokenElement } = FilteredSearchVisualTokens;
if (isLastVisualTokenValid) {
- addVisualTokenElement(tokenName, tokenValue, false, canEdit);
+ addVisualTokenElement(tokenName, tokenValue, {
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ });
} else {
const previousTokenName = lastVisualToken.querySelector('.name').innerText;
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
tokensContainer.removeChild(lastVisualToken);
const value = tokenValue || tokenName;
- addVisualTokenElement(previousTokenName, value, false, canEdit);
+ addVisualTokenElement(previousTokenName, value, {
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ });
}
}
@@ -235,7 +263,9 @@ export default class FilteredSearchVisualTokens {
if (lastVisualToken && lastVisualToken.classList.contains('filtered-search-term')) {
lastVisualToken.querySelector('.name').innerText += ` ${searchTerm}`;
} else {
- FilteredSearchVisualTokens.addVisualTokenElement(searchTerm, null, true);
+ FilteredSearchVisualTokens.addVisualTokenElement(searchTerm, null, {
+ isSearchTerm: true,
+ });
}
}
@@ -306,7 +336,9 @@ export default class FilteredSearchVisualTokens {
let value;
if (token.classList.contains('filtered-search-token')) {
- FilteredSearchVisualTokens.addFilterVisualToken(nameElement.innerText);
+ FilteredSearchVisualTokens.addFilterVisualToken(nameElement.innerText, null, {
+ uppercaseTokenName: nameElement.classList.contains('text-uppercase'),
+ });
const valueContainerElement = token.querySelector('.value-container');
value = valueContainerElement.dataset.originalValue;
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
index 3aca38399fb..b0e60edcbe5 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
@@ -3,7 +3,7 @@ import $ from 'jquery';
import { mapActions } from 'vuex';
import { __ } from '~/locale';
import FileIcon from '~/vue_shared/components/file_icon.vue';
-import ChangedFileIcon from '../changed_file_icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/file_finder/item.vue b/app/assets/javascripts/ide/components/file_finder/item.vue
index a612739d641..72ce37be63a 100644
--- a/app/assets/javascripts/ide/components/file_finder/item.vue
+++ b/app/assets/javascripts/ide/components/file_finder/item.vue
@@ -1,7 +1,7 @@
<script>
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import FileIcon from '../../../vue_shared/components/file_icon.vue';
-import ChangedFileIcon from '../changed_file_icon.vue';
+import ChangedFileIcon from '../../../vue_shared/components/changed_file_icon.vue';
const MAX_PATH_LENGTH = 60;
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 44a360ab909..2ad14b88410 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -3,8 +3,8 @@ import { mapGetters } from 'vuex';
import { n__, __, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import NewDropdown from './new_dropdown/index.vue';
-import ChangedFileIcon from './changed_file_icon.vue';
import MrFileIcon from './mr_file_icon.vue';
export default {
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index db47b75ec5c..d621653d6fd 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -3,8 +3,8 @@ import { mapActions } from 'vuex';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import FileStatusIcon from './repo_file_status_icon.vue';
-import ChangedFileIcon from './changed_file_icon.vue';
export default {
components: {
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index 0e71e705c13..854445bd2a4 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -24,7 +24,6 @@ export default class Job extends LogOutputBehaviours {
this.$document = $(document);
this.$window = $(window);
this.logBytes = 0;
- this.updateDropdown = this.updateDropdown.bind(this);
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
@@ -35,18 +34,12 @@ export default class Job extends LogOutputBehaviours {
clearTimeout(this.timeout);
this.initSidebar();
- this.populateJobs(this.buildStage);
- this.updateStageDropdownText(this.buildStage);
this.sidebarOnResize();
this.$document
.off('click', '.js-sidebar-build-toggle')
.on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
- this.$document
- .off('click', '.stage-item')
- .on('click', '.stage-item', this.updateDropdown);
-
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$window
@@ -194,20 +187,4 @@ export default class Job extends LogOutputBehaviours {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
}
- // eslint-disable-next-line class-methods-use-this
- populateJobs(stage) {
- $('.build-job').hide();
- $(`.build-job[data-stage="${stage}"]`).show();
- }
- // eslint-disable-next-line class-methods-use-this
- updateStageDropdownText(stage) {
- $('.stage-selection').text(stage);
- }
-
- updateDropdown(e) {
- e.preventDefault();
- const stage = e.currentTarget.text;
- this.updateStageDropdownText(stage);
- this.populateJobs(stage);
- }
}
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index ca6386595c7..e6e1d418194 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -12,12 +12,16 @@
type: Object,
required: true,
},
+ iconStatus: {
+ type: Object,
+ required: true,
+ },
},
computed: {
environment() {
let environmentText;
switch (this.deploymentStatus.status) {
- case 'latest':
+ case 'last':
environmentText = sprintf(
__('This job is the most recent deployment to %{link}.'),
{ link: this.environmentLink },
@@ -32,7 +36,7 @@
),
{
environmentLink: this.environmentLink,
- deploymentLink: this.deploymentLink,
+ deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
},
false,
);
@@ -56,11 +60,11 @@
if (this.hasLastDeployment) {
environmentText = sprintf(
__(
- 'This job is creating a deployment to %{environmentLink} and will overwrite the last %{deploymentLink}.',
+ 'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
),
{
environmentLink: this.environmentLink,
- deploymentLink: this.deploymentLink,
+ deploymentLink: this.deploymentLink(__('latest deployment')),
},
false,
);
@@ -78,41 +82,57 @@
return environmentText;
},
environmentLink() {
- return sprintf(
- '%{startLink}%{name}%{endLink}',
- {
- startLink: `<a href="${this.deploymentStatus.environment.path}">`,
- name: _.escape(this.deploymentStatus.environment.name),
- endLink: '</a>',
- },
- false,
- );
+ if (this.hasEnvironment) {
+ return sprintf(
+ '%{startLink}%{name}%{endLink}',
+ {
+ startLink: `<a href="${
+ this.deploymentStatus.environment.environment_path
+ }" class="js-environment-link">`,
+ name: _.escape(this.deploymentStatus.environment.name),
+ endLink: '</a>',
+ },
+ false,
+ );
+ }
+ return '';
},
- deploymentLink() {
+ hasLastDeployment() {
+ return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
+ },
+ lastDeployment() {
+ return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
+ },
+ hasEnvironment() {
+ return !_.isEmpty(this.deploymentStatus.environment);
+ },
+ lastDeploymentPath() {
+ return !_.isEmpty(this.lastDeployment.deployable) ? this.lastDeployment.deployable.build_path : '';
+ },
+ },
+ methods: {
+ deploymentLink(name) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
- startLink: `<a href="${this.lastDeployment.path}">`,
- name: _.escape(this.lastDeployment.name),
+ startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
+ name,
endLink: '</a>',
},
false,
);
},
- hasLastDeployment() {
- return this.deploymentStatus.environment.last_deployment;
- },
- lastDeployment() {
- return this.deploymentStatus.environment.last_deployment;
- },
},
};
</script>
<template>
<div class="prepend-top-default js-environment-container">
<div class="environment-information">
- <ci-icon :status="deploymentStatus.icon" />
- <p v-html="environment"></p>
+ <ci-icon :status="iconStatus"/>
+ <p
+ class="inline append-bottom-0"
+ v-html="environment"
+ ></p>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
deleted file mode 100644
index 63324e68d68..00000000000
--- a/app/assets/javascripts/jobs/components/header.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-<script>
-import ciHeader from '../../vue_shared/components/header_ci_component.vue';
-import callout from '../../vue_shared/components/callout.vue';
-
-export default {
- name: 'JobHeaderSection',
- components: {
- ciHeader,
- callout,
- },
- props: {
- job: {
- type: Object,
- required: true,
- },
- isLoading: {
- type: Boolean,
- required: true,
- },
- },
- data() {
- return {
- actions: this.getActions(),
- };
- },
- computed: {
- status() {
- return this.job && this.job.status;
- },
- shouldRenderContent() {
- return !this.isLoading && Object.keys(this.job).length;
- },
- shouldRenderReason() {
- return !!(this.job.status && this.job.callout_message);
- },
- /**
- * When job has not started the key will be `false`
- * When job started the key will be a string with a date.
- */
- jobStarted() {
- return !this.job.started === false;
- },
- headerTime() {
- return this.jobStarted ? this.job.started : this.job.created_at;
- },
- },
- watch: {
- job() {
- this.actions = this.getActions();
- },
- },
- methods: {
- getActions() {
- const actions = [];
-
- if (this.job.new_issue_path) {
- actions.push({
- label: 'New issue',
- path: this.job.new_issue_path,
- cssClass: 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
- type: 'link',
- });
- }
- return actions;
- },
- },
-};
-</script>
-<template>
- <header>
- <div class="js-build-header build-header top-area">
- <ci-header
- v-if="shouldRenderContent"
- :status="status"
- :item-id="job.id"
- :time="headerTime"
- :user="job.user"
- :actions="actions"
- :has-sidebar-button="true"
- :should-render-triggered-label="jobStarted"
- item-name="Job"
- />
- <gl-loading-icon
- v-if="isLoading"
- :size="2"
- class="prepend-top-default append-bottom-default"
- />
- </div>
-
- <callout
- v-if="shouldRenderReason"
- :message="job.callout_message"
- />
- </header>
-</template>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
new file mode 100644
index 00000000000..bac8bd71d64
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -0,0 +1,99 @@
+<script>
+ import { mapGetters, mapState } from 'vuex';
+ import CiHeader from '~/vue_shared/components/header_ci_component.vue';
+ import Callout from '~/vue_shared/components/callout.vue';
+ import EnvironmentsBlock from './environments_block.vue';
+ import ErasedBlock from './erased_block.vue';
+ import StuckBlock from './stuck_block.vue';
+
+ export default {
+ name: 'JobPageApp',
+ components: {
+ CiHeader,
+ Callout,
+ EnvironmentsBlock,
+ ErasedBlock,
+ StuckBlock,
+ },
+ props: {
+ runnerHelpUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ ...mapState(['isLoading', 'job']),
+ ...mapGetters([
+ 'headerActions',
+ 'headerTime',
+ 'shouldRenderCalloutMessage',
+ 'jobHasStarted',
+ 'hasEnvironment',
+ 'isJobStuck',
+ ]),
+ },
+ };
+</script>
+<template>
+ <div>
+ <gl-loading-icon
+ v-if="isLoading"
+ :size="2"
+ class="prepend-top-20"
+ />
+
+ <template v-else>
+ <!-- Header Section -->
+ <header>
+ <div class="js-build-header build-header top-area">
+ <ci-header
+ :status="job.status"
+ :item-id="job.id"
+ :time="headerTime"
+ :user="job.user"
+ :actions="headerActions"
+ :has-sidebar-button="true"
+ :should-render-triggered-label="jobHasStarted"
+ :item-name="__('Job')"
+ />
+ </div>
+
+ <callout
+ v-if="shouldRenderCalloutMessage"
+ :message="job.callout_message"
+ />
+ </header>
+ <!-- EO Header Section -->
+
+ <!-- Body Section -->
+ <stuck-block
+ v-if="isJobStuck"
+ class="js-job-stuck"
+ :has-no-runners-for-project="job.runners.available"
+ :tags="job.tags"
+ :runners-path="runnerHelpUrl"
+ />
+
+ <environments-block
+ v-if="hasEnvironment"
+ :deployment-status="job.deployment_status"
+ :icon-status="job.status"
+ />
+
+ <erased-block
+ v-if="job.erased"
+ :user="job.erased_by"
+ :erased-at="job.erased_at"
+ />
+
+ <!--job log -->
+ <!-- EO job log -->
+
+ <!--empty state -->
+ <!-- EO empty state -->
+
+ <!-- EO Body Section -->
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
index 93e2292ff84..271b7790d75 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -1,4 +1,5 @@
<script>
+ import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
@@ -16,26 +17,39 @@
type: Array,
required: true,
},
+ jobId: {
+ type: Number,
+ required: true,
+ },
+ },
+ methods: {
+ isJobActive(currentJobId) {
+ return this.jobId === currentJobId;
+ },
+ tooltipText(job) {
+ return `${_.escape(job.name)} - ${job.status.tooltip}`;
+ },
},
};
</script>
<template>
- <div class="builds-container">
+ <div class="js-jobs-container builds-container">
<div
+ v-for="job in jobs"
+ :key="job.id"
class="build-job"
+ :class="{ retried: job.retried, active: isJobActive(job.id) }"
>
<a
- v-for="job in jobs"
- :key="job.id"
v-tooltip
- :href="job.path"
- :title="job.tooltip"
- :class="{ active: job.active, retried: job.retried }"
+ :href="job.status.details_path"
+ :title="tooltipText(job)"
+ data-container="body"
>
<icon
- v-if="job.active"
+ v-if="isJobActive(job.id)"
name="arrow-right"
- class="js-arrow-right"
+ class="js-arrow-right icon-arrow-right"
/>
<ci-icon :status="job.status" />
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
new file mode 100644
index 00000000000..22bcd402e72
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -0,0 +1,297 @@
+<script>
+ import _ from 'underscore';
+ import { mapActions, mapState } from 'vuex';
+ import timeagoMixin from '~/vue_shared/mixins/timeago';
+ import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+ import Icon from '~/vue_shared/components/icon.vue';
+ import DetailRow from './sidebar_detail_row.vue';
+ import ArtifactsBlock from './artifacts_block.vue';
+ import TriggerBlock from './trigger_block.vue';
+ import CommitBlock from './commit_block.vue';
+ import StagesDropdown from './stages_dropdown.vue';
+ import JobsContainer from './jobs_container.vue';
+
+ export default {
+ name: 'JobSidebar',
+ components: {
+ ArtifactsBlock,
+ CommitBlock,
+ DetailRow,
+ Icon,
+ TriggerBlock,
+ StagesDropdown,
+ JobsContainer,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ runnerHelpUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ terminalPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ ...mapState(['job', 'isLoading', 'stages', 'jobs']),
+ coverage() {
+ return `${this.job.coverage}%`;
+ },
+ duration() {
+ return timeIntervalInWords(this.job.duration);
+ },
+ queued() {
+ return timeIntervalInWords(this.job.queued);
+ },
+ runnerId() {
+ return `${this.job.runner.description} (#${this.job.runner.id})`;
+ },
+ retryButtonClass() {
+ let className =
+ 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
+ className +=
+ this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
+ return className;
+ },
+ hasTimeout() {
+ return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
+ },
+ timeout() {
+ if (this.job.metadata == null) {
+ return '';
+ }
+
+ let t = this.job.metadata.timeout_human_readable;
+ if (this.job.metadata.timeout_source !== '') {
+ t += ` (from ${this.job.metadata.timeout_source})`;
+ }
+
+ return t;
+ },
+ renderBlock() {
+ return (
+ this.job.merge_request ||
+ this.job.duration ||
+ this.job.finished_data ||
+ this.job.erased_at ||
+ this.job.queued ||
+ this.job.runner ||
+ this.job.coverage ||
+ this.job.tags.length ||
+ this.job.cancel_path
+ );
+ },
+ hasArtifact() {
+ return !_.isEmpty(this.job.artifact);
+ },
+ hasTriggers() {
+ return !_.isEmpty(this.job.trigger);
+ },
+ hasStages() {
+ return (
+ (this.job &&
+ this.job.pipeline &&
+ this.job.pipeline.stages &&
+ this.job.pipeline.stages.length > 0) ||
+ false
+ );
+ },
+ commit() {
+ return this.job.pipeline.commit || {};
+ },
+ },
+ methods: {
+ ...mapActions(['fetchJobsForStage']),
+ },
+ };
+</script>
+<template>
+ <aside
+ class="right-sidebar right-sidebar-expanded build-sidebar"
+ data-offset-top="101"
+ data-spy="affix"
+ >
+ <div class="sidebar-container">
+ <div class="blocks-container">
+ <template v-if="!isLoading">
+ <div class="block">
+ <strong class="inline prepend-top-8">
+ {{ job.name }}
+ </strong>
+ <a
+ v-if="job.retry_path"
+ :class="retryButtonClass"
+ :href="job.retry_path"
+ data-method="post"
+ rel="nofollow"
+ >
+ {{ __('Retry') }}
+ </a>
+ <a
+ v-if="terminalPath"
+ :href="terminalPath"
+ class="js-terminal-link pull-right btn btn-primary
+ btn-inverted visible-md-block visible-lg-block"
+ target="_blank"
+ >
+ {{ __('Debug') }}
+ <icon name="external-link" />
+ </a>
+ <button
+ :aria-label="__('Toggle Sidebar')"
+ type="button"
+ class="btn btn-blank gutter-toggle
+ float-right d-block d-md-none js-sidebar-build-toggle"
+ >
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-angle-double-right"
+ ></i>
+ </button>
+ </div>
+ <div
+ v-if="job.retry_path || job.new_issue_path"
+ class="block retry-link"
+ >
+ <a
+ v-if="job.new_issue_path"
+ :href="job.new_issue_path"
+ class="js-new-issue btn btn-success btn-inverted"
+ >
+ {{ __('New issue') }}
+ </a>
+ <a
+ v-if="job.retry_path"
+ :href="job.retry_path"
+ class="js-retry-job btn btn-inverted-secondary"
+ data-method="post"
+ rel="nofollow"
+ >
+ {{ __('Retry') }}
+ </a>
+ </div>
+ <div :class="{ block : renderBlock }">
+ <p
+ v-if="job.merge_request"
+ class="build-detail-row js-job-mr"
+ >
+ <span class="build-light-text">
+ {{ __('Merge Request:') }}
+ </span>
+ <a :href="job.merge_request.path">
+ !{{ job.merge_request.iid }}
+ </a>
+ </p>
+
+ <detail-row
+ v-if="job.duration"
+ :value="duration"
+ class="js-job-duration"
+ title="Duration"
+ />
+ <detail-row
+ v-if="job.finished_at"
+ :value="timeFormated(job.finished_at)"
+ class="js-job-finished"
+ title="Finished"
+ />
+ <detail-row
+ v-if="job.erased_at"
+ :value="timeFormated(job.erased_at)"
+ class="js-job-erased"
+ title="Erased"
+ />
+ <detail-row
+ v-if="job.queued"
+ :value="queued"
+ class="js-job-queued"
+ title="Queued"
+ />
+ <detail-row
+ v-if="hasTimeout"
+ :help-url="runnerHelpUrl"
+ :value="timeout"
+ class="js-job-timeout"
+ title="Timeout"
+ />
+ <detail-row
+ v-if="job.runner"
+ :value="runnerId"
+ class="js-job-runner"
+ title="Runner"
+ />
+ <detail-row
+ v-if="job.coverage"
+ :value="coverage"
+ class="js-job-coverage"
+ title="Coverage"
+ />
+ <p
+ v-if="job.tags.length"
+ class="build-detail-row js-job-tags"
+ >
+ <span class="build-light-text">
+ {{ __('Tags:') }}
+ </span>
+ <span
+ v-for="(tag, i) in job.tags"
+ :key="i"
+ class="label label-primary">
+ {{ tag }}
+ </span>
+ </p>
+
+ <div
+ v-if="job.cancel_path"
+ class="btn-group prepend-top-5"
+ role="group">
+ <a
+ :href="job.cancel_path"
+ class="js-cancel-job btn btn-sm btn-default"
+ data-method="post"
+ rel="nofollow"
+ >
+ {{ __('Cancel') }}
+ </a>
+ </div>
+ </div>
+ <artifacts-block
+ v-if="hasArtifact"
+ :artifact="job.artifact"
+ />
+ <trigger-block
+ v-if="hasTriggers"
+ :trigger="job.trigger"
+ />
+ <commit-block
+ :is-last-block="hasStages"
+ :commit="commit"
+ :merge-request="job.merge_request"
+ />
+
+ <stages-dropdown
+ :stages="stages"
+ :pipeline="job.pipeline"
+ @requestSidebarStageDropdown="fetchJobsForStage"
+ />
+
+ </template>
+ <gl-loading-icon
+ v-else
+ :size="2"
+ class="prepend-top-10"
+ />
+ </div>
+
+ <jobs-container
+ v-if="!isLoading && jobs.length"
+ :jobs="jobs"
+ :job-id="job.id"
+ />
+ </div>
+ </aside>
+</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
deleted file mode 100644
index a591fcfb482..00000000000
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ /dev/null
@@ -1,276 +0,0 @@
-<script>
- import _ from 'underscore';
- import timeagoMixin from '~/vue_shared/mixins/timeago';
- import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
- import Icon from '~/vue_shared/components/icon.vue';
- import DetailRow from './sidebar_detail_row.vue';
- import ArtifactsBlock from './artifacts_block.vue';
- import TriggerBlock from './trigger_block.vue';
- import CommitBlock from './commit_block.vue';
-
- export default {
- name: 'SidebarDetailsBlock',
- components: {
- ArtifactsBlock,
- CommitBlock,
- DetailRow,
- Icon,
- TriggerBlock,
- },
- mixins: [timeagoMixin],
- props: {
- job: {
- type: Object,
- required: true,
- },
- isLoading: {
- type: Boolean,
- required: true,
- },
- runnerHelpUrl: {
- type: String,
- required: false,
- default: '',
- },
- terminalPath: {
- type: String,
- required: false,
- default: null,
- },
- },
- computed: {
- shouldRenderContent() {
- return !this.isLoading && Object.keys(this.job).length > 0;
- },
- coverage() {
- return `${this.job.coverage}%`;
- },
- duration() {
- return timeIntervalInWords(this.job.duration);
- },
- queued() {
- return timeIntervalInWords(this.job.queued);
- },
- runnerId() {
- return `${this.job.runner.description} (#${this.job.runner.id})`;
- },
- retryButtonClass() {
- let className =
- 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
- className +=
- this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
- return className;
- },
- hasTimeout() {
- return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
- },
- timeout() {
- if (this.job.metadata == null) {
- return '';
- }
-
- let t = this.job.metadata.timeout_human_readable;
- if (this.job.metadata.timeout_source !== '') {
- t += ` (from ${this.job.metadata.timeout_source})`;
- }
-
- return t;
- },
- renderBlock() {
- return (
- this.job.merge_request ||
- this.job.duration ||
- this.job.finished_data ||
- this.job.erased_at ||
- this.job.queued ||
- this.job.runner ||
- this.job.coverage ||
- this.job.tags.length ||
- this.job.cancel_path
- );
- },
- hasArtifact() {
- return !_.isEmpty(this.job.artifact);
- },
- hasTriggers() {
- return !_.isEmpty(this.job.trigger);
- },
- hasStages() {
- return (
- this.job &&
- this.job.pipeline &&
- this.job.pipeline.stages &&
- this.job.pipeline.stages.length > 0
- ) || false;
- },
- commit() {
- return this.job.pipeline.commit || {};
- },
- },
- };
-</script>
-<template>
- <div>
- <div class="block">
- <strong class="inline prepend-top-8">
- {{ job.name }}
- </strong>
- <a
- v-if="job.retry_path"
- :class="retryButtonClass"
- :href="job.retry_path"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Retry') }}
- </a>
- <a
- v-if="terminalPath"
- :href="terminalPath"
- class="js-terminal-link pull-right btn btn-primary
- btn-inverted visible-md-block visible-lg-block"
- target="_blank"
- >
- {{ __('Debug') }}
- <icon name="external-link" />
- </a>
- <button
- :aria-label="__('Toggle Sidebar')"
- type="button"
- class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle"
- >
- <i
- aria-hidden="true"
- data-hidden="true"
- class="fa fa-angle-double-right"
- ></i>
- </button>
- </div>
- <template v-if="shouldRenderContent">
- <div
- v-if="job.retry_path || job.new_issue_path"
- class="block retry-link"
- >
- <a
- v-if="job.new_issue_path"
- :href="job.new_issue_path"
- class="js-new-issue btn btn-success btn-inverted"
- >
- {{ __('New issue') }}
- </a>
- <a
- v-if="job.retry_path"
- :href="job.retry_path"
- class="js-retry-job btn btn-inverted-secondary"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Retry') }}
- </a>
- </div>
- <div :class="{block : renderBlock }">
- <p
- v-if="job.merge_request"
- class="build-detail-row js-job-mr"
- >
- <span class="build-light-text">
- {{ __('Merge Request:') }}
- </span>
- <a :href="job.merge_request.path">
- !{{ job.merge_request.iid }}
- </a>
- </p>
-
- <detail-row
- v-if="job.duration"
- :value="duration"
- class="js-job-duration"
- title="Duration"
- />
- <detail-row
- v-if="job.finished_at"
- :value="timeFormated(job.finished_at)"
- class="js-job-finished"
- title="Finished"
- />
- <detail-row
- v-if="job.erased_at"
- :value="timeFormated(job.erased_at)"
- class="js-job-erased"
- title="Erased"
- />
- <detail-row
- v-if="job.queued"
- :value="queued"
- class="js-job-queued"
- title="Queued"
- />
- <detail-row
- v-if="hasTimeout"
- :help-url="runnerHelpUrl"
- :value="timeout"
- class="js-job-timeout"
- title="Timeout"
- />
- <detail-row
- v-if="job.runner"
- :value="runnerId"
- class="js-job-runner"
- title="Runner"
- />
- <detail-row
- v-if="job.coverage"
- :value="coverage"
- class="js-job-coverage"
- title="Coverage"
- />
- <p
- v-if="job.tags.length"
- class="build-detail-row js-job-tags"
- >
- <span class="build-light-text">
- {{ __('Tags:') }}
- </span>
- <span
- v-for="(tag, i) in job.tags"
- :key="i"
- class="label label-primary">
- {{ tag }}
- </span>
- </p>
-
- <div
- v-if="job.cancel_path"
- class="btn-group prepend-top-5"
- role="group">
- <a
- :href="job.cancel_path"
- class="js-cancel-job btn btn-sm btn-default"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Cancel') }}
- </a>
- </div>
- </div>
- <artifacts-block
- v-if="hasArtifact"
- :artifact="job.artifact"
- />
- <trigger-block
- v-if="hasTriggers"
- :trigger="job.trigger"
- />
- <commit-block
- :is-last-block="hasStages"
- :commit="commit"
- :merge-request="job.merge_request"
- />
- </template>
- <gl-loading-icon
- v-if="isLoading"
- :size="2"
- class="prepend-top-10"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index d6d64fa32f7..1c15af55a8b 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,8 +1,8 @@
<script>
+ import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
-
- import { sprintf, __ } from '~/locale';
+ import { __ } from '~/locale';
export default {
components: {
@@ -10,30 +10,14 @@
Icon,
},
props: {
- pipelineId: {
- type: Number,
- required: true,
- },
- pipelinePath: {
- type: String,
- required: true,
- },
- pipelineRef: {
- type: String,
- required: true,
- },
- pipelineRefPath: {
- type: String,
+ pipeline: {
+ type: Object,
required: true,
},
stages: {
type: Array,
required: true,
},
- pipelineStatus: {
- type: Object,
- required: true,
- },
},
data() {
return {
@@ -41,57 +25,73 @@
};
},
computed: {
- pipelineLink() {
- return sprintf(__('Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}'), {
- pipelineLinkStart: `<a href=${this.pipelinePath} class="js-pipeline-path link-commit">`,
- pipelineId: this.pipelineId,
- pipelineLinkEnd: '</a>',
- pipelineLinkRefStart: `<a href=${this.pipelineRefPath} class="link-commit ref-name">`,
- pipelineRef: this.pipelineRef,
- pipelineLinkRefEnd: '</a>',
- }, false);
+ hasRef() {
+ return !_.isEmpty(this.pipeline.ref);
+ },
+ },
+ watch: {
+ // When the component is initially mounted it may start with an empty stages array.
+ // Once the prop is updated, we set the first stage as the selected one
+ stages(newVal) {
+ if (newVal.length) {
+ this.selectedStage = newVal[0].name;
+ }
},
},
methods: {
onStageClick(stage) {
- // todo: consider moving into store
- this.selectedStage = stage.name;
-
- // update dropdown with jobs
- // jobs container is a new component.
this.$emit('requestSidebarStageDropdown', stage);
+ this.selectedStage = stage.name;
},
},
};
</script>
<template>
- <div class="block-last">
- <ci-icon :status="pipelineStatus" />
+ <div class="block-last dropdown">
+ <ci-icon
+ :status="pipeline.details.status"
+ class="vertical-align-middle"
+ />
+
+ {{ __('Pipeline') }}
+ <a
+ :href="pipeline.path"
+ class="js-pipeline-path link-commit"
+ >
+ #{{ pipeline.id }}
+ </a>
+ <template v-if="hasRef">
+ {{ __('from') }}
+ <a
+ :href="pipeline.ref.path"
+ class="link-commit ref-name"
+ >
+ {{ pipeline.ref.name }}
+ </a>
+ </template>
- <p v-html="pipelineLink"></p>
+ <button
+ type="button"
+ data-toggle="dropdown"
+ class="js-selected-stage dropdown-menu-toggle prepend-top-8"
+ >
+ {{ selectedStage }}
+ <i class="fa fa-chevron-down" ></i>
+ </button>
- <div class="dropdown">
- <button
- type="button"
- data-toggle="dropdown"
+ <ul class="dropdown-menu">
+ <li
+ v-for="stage in stages"
+ :key="stage.name"
>
- {{ selectedStage }}
- <icon name="chevron-down" />
- </button>
- <ul class="dropdown-menu">
- <li
- v-for="(stage, index) in stages"
- :key="index"
+ <button
+ type="button"
+ class="js-stage-item stage-item"
+ @click="onStageClick(stage)"
>
- <button
- type="button"
- class="stage-item"
- @click="onStageClick(stage)"
- >
- {{ stage.name }}
- </button>
- </li>
- </ul>
- </div>
+ {{ stage.name }}
+ </button>
+ </li>
+ </ul>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 0136ec4d194..3eb75e72506 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -1,8 +1,9 @@
-import { mapState } from 'vuex';
+import _ from 'underscore';
+import { mapState, mapActions } from 'vuex';
import Vue from 'vue';
import Job from '../job';
-import JobHeader from './components/header.vue';
-import DetailsBlock from './components/sidebar_details_block.vue';
+import JobApp from './components/job_app.vue';
+import Sidebar from './components/sidebar.vue';
import createStore from './store';
export default () => {
@@ -13,6 +14,7 @@ export default () => {
const store = createStore();
store.dispatch('setJobEndpoint', dataset.endpoint);
+
store.dispatch('fetchJob');
// Header
@@ -20,17 +22,18 @@ export default () => {
new Vue({
el: '#js-build-header-vue',
components: {
- JobHeader,
+ JobApp,
},
store,
computed: {
...mapState(['job', 'isLoading']),
},
render(createElement) {
- return createElement('job-header', {
+ return createElement('job-app', {
props: {
isLoading: this.isLoading,
job: this.job,
+ runnerHelpUrl: dataset.runnerHelpUrl,
},
});
},
@@ -43,17 +46,25 @@ export default () => {
new Vue({
el: detailsBlockElement,
components: {
- DetailsBlock,
+ Sidebar,
},
- store,
computed: {
- ...mapState(['job', 'isLoading']),
+ ...mapState(['job']),
+ },
+ watch: {
+ job(newVal, oldVal) {
+ if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
+ this.fetchStages();
+ }
+ },
},
+ methods: {
+ ...mapActions(['fetchStages']),
+ },
+ store,
render(createElement) {
- return createElement('details-block', {
+ return createElement('sidebar', {
props: {
- isLoading: this.isLoading,
- job: this.job,
runnerHelpUrl: dataset.runnerHelpUrl,
terminalPath: detailsBlockDataset.terminalPath,
},
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 7f5406d6f43..298367c9342 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -62,7 +62,9 @@ export const fetchJob = ({ state, dispatch }) => {
});
};
-export const receiveJobSuccess = ({ commit }, data) => commit(types.RECEIVE_JOB_SUCCESS, data);
+export const receiveJobSuccess = ({ commit }, data) => {
+ commit(types.RECEIVE_JOB_SUCCESS, data);
+};
export const receiveJobError = ({ commit }) => {
commit(types.RECEIVE_JOB_ERROR);
flash(__('An error occurred while fetching the job.'));
@@ -137,8 +139,11 @@ export const fetchStages = ({ state, dispatch }) => {
dispatch('requestStages');
axios
- .get(state.stagesEndpoint)
- .then(({ data }) => dispatch('receiveStagesSuccess', data))
+ .get(state.job.pipeline.path)
+ .then(({ data }) => {
+ dispatch('receiveStagesSuccess', data.details.stages);
+ dispatch('fetchJobsForStage', data.details.stages[0]);
+ })
.catch(() => dispatch('receiveStagesError'));
};
export const receiveStagesSuccess = ({ commit }, data) =>
@@ -152,16 +157,23 @@ export const receiveStagesError = ({ commit }) => {
* Jobs list on sidebar - depend on stages dropdown
*/
export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
-export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
// On stage click, set selected stage + fetch job
-export const fetchJobsForStage = ({ state, dispatch }, stage) => {
- dispatch('setSelectedStage', stage);
+export const fetchJobsForStage = ({ dispatch }, stage) => {
dispatch('requestJobsForStage');
axios
- .get(state.stageJobsEndpoint)
- .then(({ data }) => dispatch('receiveJobsForStageSuccess', data))
+ .get(stage.dropdown_path, {
+ params: {
+ retried: 1,
+ },
+ })
+ .then(({ data }) => {
+ const retriedJobs = data.retried.map(job => Object.assign({}, job, { retried: true }));
+ const jobs = data.latest_statuses.concat(retriedJobs);
+
+ dispatch('receiveJobsForStageSuccess', jobs);
+ })
.catch(() => dispatch('receiveJobsForStageError'));
};
export const receiveJobsForStageSuccess = ({ commit }, data) =>
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
new file mode 100644
index 00000000000..62d154ff584
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -0,0 +1,42 @@
+import _ from 'underscore';
+import { __ } from '~/locale';
+
+export const headerActions = state => {
+ if (state.job.new_issue_path) {
+ return [
+ {
+ label: __('New issue'),
+ path: state.job.new_issue_path,
+ cssClass:
+ 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
+ type: 'link',
+ },
+ ];
+ }
+ return [];
+};
+
+export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
+
+export const shouldRenderCalloutMessage = state =>
+ !_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
+
+/**
+ * When job has not started the key will be `false`
+ * When job started the key will be a string with a date.
+ */
+export const jobHasStarted = state => !(state.job.started === false);
+
+export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
+
+/**
+ * When the job is pending and there are no available runners
+ * we need to render the stuck block;
+ *
+ * @returns {Boolean}
+ */
+export const isJobStuck = state =>
+ state.job.status.group === 'pending' && state.job.runners && state.job.runners.available === false;
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/jobs/store/index.js b/app/assets/javascripts/jobs/store/index.js
index d8f6f56ce61..96e38f9a2fa 100644
--- a/app/assets/javascripts/jobs/store/index.js
+++ b/app/assets/javascripts/jobs/store/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import * as actions from './actions';
+import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
@@ -9,5 +10,6 @@ Vue.use(Vuex);
export default () => new Vuex.Store({
actions,
mutations,
+ getters,
state: state(),
});
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 31faa11ea72..e14fff7a610 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -88,6 +88,7 @@ export const handleLocationHash = () => {
const fixedDiffStats = document.querySelector('.js-diff-files-changed');
const fixedNav = document.querySelector('.navbar-gitlab');
const performanceBar = document.querySelector('#js-peek');
+ const topPadding = 8;
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
@@ -108,6 +109,10 @@ export const handleLocationHash = () => {
adjustment -= performanceBar.offsetHeight;
}
+ if (isInMRPage()) {
+ adjustment -= topPadding;
+ }
+
window.scrollBy(0, adjustment);
};
@@ -381,8 +386,11 @@ export const objectToQueryString = (params = {}) =>
.map(param => `${param}=${params[param]}`)
.join('&');
-export const buildUrlWithCurrentLocation = param =>
- (param ? `${window.location.pathname}${param}` : window.location.pathname);
+export const buildUrlWithCurrentLocation = param => {
+ if (param) return `${window.location.pathname}${param}`;
+
+ return window.location.pathname;
+};
/**
* Based on the current location and the string parameters provided
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 763429d7242..78f56ab57ff 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -194,9 +194,7 @@ export default class MergeRequestTabs {
if (bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
}
- if (this.diffViewType() === 'parallel') {
- this.expandViewContainer();
- }
+ this.expandViewContainer();
this.destroyPipelinesView();
this.commitsTab.classList.remove('active');
} else if (action === 'pipelines') {
@@ -355,7 +353,7 @@ export default class MergeRequestTabs {
localTimeAgo($('.js-timeago', 'div#diffs'));
syntaxHighlight($('#diffs .js-syntax-highlight'));
- if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
+ if (this.isDiffAction(this.currentAction)) {
this.expandViewContainer();
}
this.diffsLoaded = true;
@@ -408,19 +406,23 @@ export default class MergeRequestTabs {
}
diffViewType() {
- return $('.inline-parallel-buttons a.active').data('viewType');
+ return $('.inline-parallel-buttons button.active').data('viewType');
}
isDiffAction(action) {
return action === 'diffs' || action === 'new/diffs';
}
- expandViewContainer() {
+ expandViewContainer(removeLimited = true) {
const $wrapper = $('.content-wrapper .container-fluid').not('.breadcrumbs');
if (this.fixedLayoutPref === null) {
this.fixedLayoutPref = $wrapper.hasClass('container-limited');
}
- $wrapper.removeClass('container-limited');
+ if (this.diffViewType() === 'parallel' || removeLimited) {
+ $wrapper.removeClass('container-limited');
+ } else {
+ $wrapper.addClass('container-limited');
+ }
}
resetViewContainer() {
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 6ede7562edf..e9218723149 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -191,6 +191,7 @@ export default {
if (note.placeholderType === SYSTEM_NOTE) {
return placeholderSystemNote;
}
+
return placeholderNote;
}
@@ -201,7 +202,7 @@ export default {
return noteableNote;
},
componentData(note) {
- return note.isPlaceholderNote ? this.discussion.notes[0] : note;
+ return note.isPlaceholderNote ? note.notes[0] : note;
},
toggleDiscussionHandler() {
this.toggleDiscussion({ discussionId: this.discussion.id });
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index b798a254459..339ce67438a 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -4,6 +4,8 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
+ IssuableFilteredSearchTokenKeys.addExtraTokensForMergeRequests();
+
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
isGroupDecendent: true,
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index 3647048a872..ec39db12e74 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -7,10 +7,13 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
+ IssuableFilteredSearchTokenKeys.addExtraTokensForMergeRequests();
+
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
+
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
index 2e1d6e9643a..8660b0546cf 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
@@ -51,10 +51,10 @@ export default {
<template>
<div class="block">
<issuable-time-tracker
- :time_estimate="store.timeEstimate"
- :time_spent="store.totalTimeSpent"
- :human_time_estimate="store.humanTimeEstimate"
- :human_time_spent="store.humanTotalTimeSpent"
+ :time-estimate="store.timeEstimate"
+ :time-spent="store.totalTimeSpent"
+ :human-time-estimate="store.humanTimeEstimate"
+ :human-time-spent="store.humanTotalTimeSpent"
:root-path="store.rootPath"
/>
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 2ee3e1f322e..ef76dc13ce9 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -19,24 +19,20 @@ export default {
TimeTrackingHelpState,
},
props: {
- // eslint-disable-next-line vue/prop-name-casing
- time_estimate: {
+ timeEstimate: {
type: Number,
required: true,
},
- // eslint-disable-next-line vue/prop-name-casing
- time_spent: {
+ timeSpent: {
type: Number,
required: true,
},
- // eslint-disable-next-line vue/prop-name-casing
- human_time_estimate: {
+ humanTimeEstimate: {
type: String,
required: false,
default: '',
},
- // eslint-disable-next-line vue/prop-name-casing
- human_time_spent: {
+ humanTimeSpent: {
type: String,
required: false,
default: '',
@@ -52,18 +48,6 @@ export default {
};
},
computed: {
- timeSpent() {
- return this.time_spent;
- },
- timeEstimate() {
- return this.time_estimate;
- },
- timeEstimateHumanReadable() {
- return this.human_time_estimate;
- },
- timeSpentHumanReadable() {
- return this.human_time_spent;
- },
hasTimeSpent() {
return !!this.timeSpent;
},
@@ -94,10 +78,12 @@ export default {
this.showHelp = show;
},
update(data) {
- this.time_estimate = data.time_estimate;
- this.time_spent = data.time_spent;
- this.human_time_estimate = data.human_time_estimate;
- this.human_time_spent = data.human_time_spent;
+ const { timeEstimate, timeSpent, humanTimeEstimate, humanTimeSpent } = data;
+
+ this.timeEstimate = timeEstimate;
+ this.timeSpent = timeSpent;
+ this.humanTimeEstimate = humanTimeEstimate;
+ this.humanTimeSpent = humanTimeSpent;
},
},
};
@@ -114,8 +100,8 @@ export default {
:show-help-state="showHelpState"
:show-spent-only-state="showSpentOnlyState"
:show-estimate-only-state="showEstimateOnlyState"
- :time-spent-human-readable="timeSpentHumanReadable"
- :time-estimate-human-readable="timeEstimateHumanReadable"
+ :time-spent-human-readable="humanTimeSpent"
+ :time-estimate-human-readable="humanTimeEstimate"
/>
<div class="title hide-collapsed">
{{ __('Time tracking') }}
@@ -145,11 +131,11 @@ export default {
<div class="time-tracking-content hide-collapsed">
<time-tracking-estimate-only-pane
v-if="showEstimateOnlyState"
- :time-estimate-human-readable="timeEstimateHumanReadable"
+ :time-estimate-human-readable="humanTimeEstimate"
/>
<time-tracking-spent-only-pane
v-if="showSpentOnlyState"
- :time-spent-human-readable="timeSpentHumanReadable"
+ :time-spent-human-readable="humanTimeSpent"
/>
<time-tracking-no-tracking-pane
v-if="showNoTimeTrackingState"
@@ -158,8 +144,8 @@ export default {
v-if="showComparisonState"
:time-estimate="timeEstimate"
:time-spent="timeSpent"
- :time-spent-human-readable="timeSpentHumanReadable"
- :time-estimate-human-readable="timeEstimateHumanReadable"
+ :time-spent-human-readable="humanTimeSpent"
+ :time-estimate-human-readable="humanTimeEstimate"
/>
<transition name="help-state-toggle">
<time-tracking-help-state
diff --git a/app/assets/javascripts/sidebar/mount_milestone_sidebar.js b/app/assets/javascripts/sidebar/mount_milestone_sidebar.js
index b15ad0e5586..87da65a1b1f 100644
--- a/app/assets/javascripts/sidebar/mount_milestone_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_milestone_sidebar.js
@@ -7,6 +7,8 @@ export default class SidebarMilestone {
if (!el) return;
+ const { timeEstimate, timeSpent, humanTimeEstimate, humanTimeSpent } = el.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el,
@@ -15,10 +17,10 @@ export default class SidebarMilestone {
},
render: createElement => createElement('timeTracker', {
props: {
- time_estimate: parseInt(el.dataset.timeEstimate, 10),
- time_spent: parseInt(el.dataset.timeSpent, 10),
- human_time_estimate: el.dataset.humanTimeEstimate,
- human_time_spent: el.dataset.humanTimeSpent,
+ timeEstimate: parseInt(timeEstimate, 10),
+ timeSpent: parseInt(timeSpent, 10),
+ humanTimeEstimate,
+ humanTimeSpent,
rootPath: '/',
},
}),
diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index 720ae11aaa6..8684005e0fb 100644
--- a/app/assets/javascripts/ide/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -3,7 +3,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import { pluralize } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
-import { getCommitIconMap } from '../utils';
+import { getCommitIconMap } from '~/ide/utils';
export default {
components: {
@@ -32,6 +32,11 @@ export default {
required: false,
default: false,
},
+ size: {
+ type: Number,
+ required: false,
+ default: 12,
+ },
},
computed: {
changedIcon() {
@@ -42,7 +47,7 @@ export default {
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
changedIconClass() {
- return `ide-${this.changedIcon} float-left`;
+ return `${this.changedIcon} float-left d-block`;
},
tooltipTitle() {
if (!this.showTooltip) return undefined;
@@ -78,13 +83,30 @@ export default {
:title="tooltipTitle"
data-container="body"
data-placement="right"
- class="ide-file-changed-icon"
+ class="file-changed-icon ml-auto"
>
<icon
v-if="showIcon"
:name="changedIcon"
- :size="12"
+ :size="size"
:css-classes="changedIconClass"
/>
</span>
</template>
+
+<style>
+.file-addition,
+.file-addition-solid {
+ color: #1aaa55;
+}
+
+.file-modified,
+.file-modified-solid {
+ color: #fc9403;
+}
+
+.file-deletion,
+.file-deletion-solid {
+ color: #db3b21;
+}
+</style>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index c797ad62a5d..36a345130c0 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -1,12 +1,14 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
export default {
name: 'FileRow',
components: {
FileIcon,
Icon,
+ ChangedFileIcon,
},
props: {
file: {
@@ -22,6 +24,16 @@ export default {
required: false,
default: null,
},
+ hideExtraOnTree: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ showChangedIcon: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -65,6 +77,9 @@ export default {
toggleTreeOpen(path) {
this.$emit('toggleTreeOpen', path);
},
+ clickedFile(path) {
+ this.$emit('clickFile', path);
+ },
clickFile() {
// Manual Action if a tree is selected/opened
if (this.isTree && this.hasUrlAtCurrentRoute()) {
@@ -72,6 +87,8 @@ export default {
}
if (this.$router) this.$router.push(`/project${this.file.url}`);
+
+ if (this.isBlob) this.clickedFile(this.file.path);
},
scrollIntoView(isInit = false) {
const block = isInit && this.isTree ? 'center' : 'nearest';
@@ -126,17 +143,24 @@ export default {
class="file-row-name str-truncated"
>
<file-icon
+ v-if="!showChangedIcon || file.type === 'tree'"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
/>
+ <changed-file-icon
+ v-else
+ :file="file"
+ :size="16"
+ class="append-right-5"
+ />
{{ file.name }}
</span>
<component
:is="extraComponent"
- v-if="extraComponent"
+ v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')"
:file="file"
:mouse-over="mouseOver"
/>
@@ -148,8 +172,11 @@ export default {
:key="childFile.key"
:file="childFile"
:level="level + 1"
+ :hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
+ :show-changed-icon="showChangedIcon"
@toggleTreeOpen="toggleTreeOpen"
+ @clickFile="clickedFile"
/>
</template>
</div>
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index e2bbcc67a67..2193e8e8de3 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -113,7 +113,7 @@
}
.avatar-container {
- margin-right: 0;
+ margin: 0 auto;
}
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index f10eaedcc04..7e30747963a 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -19,6 +19,17 @@
}
}
+ // leave enough space for the close icon
+ .modal-title {
+ &.mw-100,
+ &.w-100 {
+ // after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here
+ // https://github.com/twbs/bootstrap/pull/26976
+ margin-right: -2rem;
+ padding-right: 2rem;
+ }
+ }
+
.page-title {
margin-top: 0;
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 65f0a0d18e2..07d82e984ba 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -517,21 +517,6 @@ $ide-commit-header-height: 48px;
}
}
-.ide-file-addition,
-.ide-file-addition-solid {
- color: $green-500;
-}
-
-.ide-file-modified,
-.ide-file-modified-solid {
- color: $orange-500;
-}
-
-.ide-file-deletion,
-.ide-file-deletion-solid {
- color: $red-500;
-}
-
.multi-file-commit-list-collapsed {
display: flex;
flex-direction: column;
@@ -1399,14 +1384,6 @@ $ide-commit-header-height: 48px;
color: $theme-gray-700;
}
-.ide-file-changed-icon {
- margin-left: auto;
-
- > svg {
- display: block;
- }
-}
-
.file-row:hover,
.file-row:focus {
.ide-new-btn {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 14ba8b1df83..ed877f625b5 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -328,23 +328,6 @@
}
}
- .build-dropdown {
- margin: $gl-padding 0;
- padding: 0;
-
- .dropdown-menu-toggle {
- margin-top: #{$gl-padding / 2};
- }
-
- svg {
- position: relative;
- top: 3px;
- margin-right: 3px;
- width: 14px;
- height: 14px;
- }
- }
-
.builds-container {
background-color: $white-light;
border-top: 1px solid $border-color;
@@ -381,15 +364,11 @@
position: absolute;
left: 15px;
top: 20px;
- display: none;
+ display: block;
}
&.active {
font-weight: $gl-font-weight-bold;
-
- .icon-arrow-right {
- display: block;
- }
}
&.retried {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 10764e0f3df..628a4ca38da 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -223,6 +223,7 @@
}
}
+.clipboard-group,
.commit-sha-group {
display: inline-flex;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 987dcd32e3a..5035714b95f 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -571,8 +571,6 @@
}
.files {
- margin-top: 1px;
-
.diff-file:last-child {
margin-bottom: 0;
}
@@ -987,3 +985,63 @@
.discussion-body .image .frame {
position: relative;
}
+
+.diff-tree-list {
+ width: 320px;
+}
+
+.diff-files-holder {
+ flex: 1;
+ min-width: 0;
+}
+
+.compare-versions-container {
+ min-width: 0;
+}
+
+.tree-list-holder {
+ position: sticky;
+ top: 100px;
+ max-height: calc(100vh - 100px);
+ padding-right: $gl-padding;
+
+ .file-row {
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .with-performance-bar & {
+ top: 135px;
+ }
+}
+
+.tree-list-scroll {
+ max-height: 100%;
+ padding-top: $grid-size;
+ padding-bottom: $grid-size;
+ border-top: 1px solid $border-color;
+ border-bottom: 1px solid $border-color;
+ overflow-y: scroll;
+ overflow-x: auto;
+}
+
+.tree-list-search .form-control {
+ padding-left: 30px;
+}
+
+.tree-list-icon {
+ top: 50%;
+ left: 10px;
+ transform: translateY(-50%);
+
+ &,
+ svg {
+ fill: $gl-text-color-tertiary;
+ }
+}
+
+.tree-list-clear-icon {
+ right: 10px;
+ left: auto;
+ line-height: 0;
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 97b131687d3..45382d4ea43 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -723,6 +723,17 @@
align-items: center;
padding: 16px;
z-index: 199;
+ white-space: nowrap;
+
+ .dropdown-menu-toggle {
+ width: auto;
+ max-width: 170px;
+
+ svg {
+ top: 10px;
+ right: 8px;
+ }
+ }
}
.content-block {
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 0209a1397b9..9e24154e4b6 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -128,7 +128,7 @@ class IssuableFinder
labels_count = 1 if use_cte_for_search?
finder.execute.reorder(nil).group(:state).count.each do |key, value|
- counts[Array(key).last.to_sym] += value / labels_count
+ counts[count_key(key)] += value / labels_count
end
counts[:all] = counts.values.sum
@@ -297,6 +297,10 @@ class IssuableFinder
klass.all
end
+ def count_key(value)
+ Array(value).last.to_sym
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def by_scope(items)
return items.none if current_user_related? && !current_user
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index b698a3c7b09..50c051c3aa1 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -27,13 +27,17 @@
# updated_before: datetime
#
class MergeRequestsFinder < IssuableFinder
+ def self.scalar_params
+ @scalar_params ||= super + [:wip]
+ end
+
def klass
MergeRequest
end
def filter_items(_items)
items = by_source_branch(super)
-
+ items = by_wip(items)
by_target_branch(items)
end
@@ -61,5 +65,24 @@ class MergeRequestsFinder < IssuableFinder
items.where(target_branch: target_branch)
end
- # rubocop: enable CodeReuse/ActiveRecord
+
+ def item_project_ids(items)
+ items&.reorder(nil)&.select(:target_project_id)
+ end
+
+ def by_wip(items)
+ if params[:wip] == 'yes'
+ items.where(wip_match(items.arel_table))
+ elsif params[:wip] == 'no'
+ items.where.not(wip_match(items.arel_table))
+ else
+ items
+ end
+ end
+
+ def wip_match(table)
+ table[:title].matches('WIP:%')
+ .or(table[:title].matches('WIP %'))
+ .or(table[:title].matches('[WIP]%'))
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 0481a4a3d28..6559f94a696 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -261,7 +261,7 @@ class MergeRequest < ActiveRecord::Base
end
end
- WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
+ WIP_REGEX = /\A*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def self.work_in_progress?(title)
!!(title =~ WIP_REGEX)
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 102907a8bd3..42fd213d03b 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -58,7 +58,7 @@ class WikiPage
attr_reader :page
# The attributes Hash used for storing and validating
- # new Page values before writing to the Gollum repository.
+ # new Page values before writing to the raw repository.
attr_accessor :attributes
def hook_attrs
@@ -111,10 +111,7 @@ class WikiPage
# The processed/formatted content of this page.
def formatted_content
- # Assuming @page exists, nil formatted_data means we didn't load it
- # before hand (i.e. page was fetched by Gitaly), so we fetch it separately.
- # If the page was fetched by Gollum, formatted_data would've been a String.
- @attributes[:formatted_content] ||= @page&.formatted_data || @wiki.page_formatted_data(@page)
+ @attributes[:formatted_content] ||= @wiki.page_formatted_data(@page)
end
# The markup format for the page.
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 719bd6ef418..cefcd3d3f5a 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -63,6 +63,12 @@ class GitlabUploader < CarrierWave::Uploader::Base
super || file&.filename
end
+ def relative_path
+ return path if pathname.relative?
+
+ pathname.relative_path_from(Pathname.new(root))
+ end
+
def model_valid?
!!model
end
@@ -115,4 +121,8 @@ class GitlabUploader < CarrierWave::Uploader::Base
# the cache directory.
File.join(work_dir, cache_id, version_name.to_s, for_file)
end
+
+ def pathname
+ @pathname ||= Pathname.new(path)
+ end
end
diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb
index 557b13a8bd6..400f0b3dcc6 100644
--- a/app/uploaders/job_artifact_uploader.rb
+++ b/app/uploaders/job_artifact_uploader.rb
@@ -9,6 +9,8 @@ class JobArtifactUploader < GitlabUploader
storage_options Gitlab.config.artifacts
+ alias_method :upload, :model
+
def cached_size
return model.size if model.size.present? && !model.file_changed?
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
index b4d0d752016..a9afc104ed1 100644
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ b/app/uploaders/legacy_artifact_uploader.rb
@@ -8,6 +8,8 @@ class LegacyArtifactUploader < GitlabUploader
storage_options Gitlab.config.artifacts
+ alias_method :upload, :model
+
def store_dir
dynamic_segment
end
diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb
index f3d32e6b39d..0a966f3d44f 100644
--- a/app/uploaders/lfs_object_uploader.rb
+++ b/app/uploaders/lfs_object_uploader.rb
@@ -6,6 +6,8 @@ class LfsObjectUploader < GitlabUploader
storage_options Gitlab.config.lfs
+ alias_method :upload, :model
+
def filename
model.oid[4..-1]
end
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index 593a6d816e3..e69143abe45 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -1,4 +1,5 @@
- page_title @application.name, "Applications"
+
%h3.page-title
Application: #{@application.name}
@@ -6,23 +7,29 @@
%table.table
%tr
%td
- Application Id
+ = _('Application ID')
%td
- %code#application_id= @application.uid
+ .clipboard-group
+ .input-group
+ %input.label.label-monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
+ .input-group-append
+ = clipboard_button(target: '#application_id', title: _("Copy ID to clipboard"), class: "btn btn btn-default")
%tr
%td
- Secret:
+ = _('Secret')
%td
- %code#secret= @application.secret
-
+ .clipboard-group
+ .input-group
+ %input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
+ .input-group-append
+ = clipboard_button(target: '#application_id', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
%td
- Callback url
+ = _('Callback URL')
%td
- @application.redirect_uri.split.each do |uri|
%div
%span.monospace= uri
-
%tr
%td
Trusted
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index bb76ac6d5f6..776bbc36ec2 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -10,18 +10,25 @@
%table.table
%tr
%td
- = _('Application Id')
+ = _('Application ID')
%td
- %code#application_id= @application.uid
+ .clipboard-group
+ .input-group
+ %input.label.label-monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
+ .input-group-append
+ = clipboard_button(target: '#application_id', title: _("Copy ID to clipboard"), class: "btn btn btn-default")
%tr
%td
- = _('Secret:')
+ = _('Secret')
%td
- %code#secret= @application.secret
-
+ .clipboard-group
+ .input-group
+ %input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
+ .input-group-append
+ = clipboard_button(target: '#application_id', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
%td
- = _('Callback url')
+ = _('Callback URL')
%td
- @application.redirect_uri.split.each do |uri|
%div
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
deleted file mode 100644
index 66a3b8b8fd1..00000000000
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ /dev/null
@@ -1,38 +0,0 @@
-%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
- .sidebar-container
- .blocks-container
- #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
-
- - if @build.pipeline.stages_count > 1
- .block-last.dropdown.build-dropdown
- %div
- %span{ class: "ci-status-icon-#{@build.pipeline.status}" }
- = ci_icon_for_status(@build.pipeline.status)
- Pipeline
- = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
- from
- = link_to "#{@build.pipeline.ref}", project_ref_path(@project, @build.pipeline.ref), class: 'link-commit ref-name'
- %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.stage-selection More
- = icon('chevron-down')
- %ul.dropdown-menu
- - @build.pipeline.legacy_stages.each do |stage|
- %li
- %a.stage-item= stage.name
-
- .builds-container
- - HasStatus::ORDERED_STATUSES.each do |build_status|
- - builds.select{|build| build.status == build_status}.each do |build|
- .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- - tooltip = sanitize(build.tooltip_message.dup)
- = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', title: tooltip, container: 'body' }) do
- = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
- %span{ class: "ci-status-icon-#{build.status}" }
- = ci_icon_for_status(build.status)
- %span
- - if build.name
- = build.name
- - else
- = build.id
- - if build.retried?
- = sprite_icon('retry', size:16, css_class: 'icon-retry')
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 5321bc46e73..ab7963737ca 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -9,54 +9,6 @@
%div{ class: container_class }
.build-page.js-build-page
#js-build-header-vue
- - if @build.stuck?
- - unless @build.any_runners_online?
- .bs-callout.bs-callout-warning.js-build-stuck
- %p
- - if @project.any_runners?
- This job is stuck, because the project doesn't have any runners online assigned to it.
- - elsif @build.tags.any?
- This job is stuck, because you don't have any active runners online with any of these tags assigned to them:
- - @build.tags.each do |tag|
- %span.badge.badge-primary
- = tag
- - else
- This job is stuck, because you don't have any active runners that can run this job.
-
- %br
- Go to
- = link_to project_runners_path(@build.project, anchor: 'js-runners-settings') do
- Runners page
-
- - if @build.starts_environment?
- .prepend-top-default.js-environment-container
- .environment-information
- - if @build.outdated_deployment?
- = ci_icon_for_status('success_with_warnings')
- - else
- = ci_icon_for_status(@build.status)
-
- - environment = environment_for_build(@build.project, @build)
- - if @build.success? && @build.last_deployment.present?
- - if @build.last_deployment.last?
- This job is the most recent deployment to #{environment_link_for_build(@build.project, @build)}.
- - else
- This job is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}.
- View the most recent deployment #{deployment_link(environment.last_deployment)}.
- - elsif @build.complete? && !@build.success?
- The deployment of this job to #{environment_link_for_build(@build.project, @build)} did not succeed.
- - else
- This job is creating a deployment to #{environment_link_for_build(@build.project, @build)}
- - if environment.try(:last_deployment)
- and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
-
- - if @build.erased?
- .prepend-top-default.js-build-erased
- .erased.alert.alert-warning
- - if @build.erased_by_user?
- Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
- - else
- Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- if @build.running? || @build.has_trace?
.build-trace-container.prepend-top-default
@@ -93,7 +45,7 @@
- else
= render "empty_states"
- = render "sidebar", builds: @builds
+ #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
.js-build-options{ data: javascript_build_options }
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 659e03fd67d..c4d177361e7 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -33,13 +33,13 @@
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { action: 'submit' } }
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
= sprite_icon('search')
%span
Press Enter or click to search
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
-# haml lint's ClassAttributeWithStaticValue
%svg
@@ -60,7 +60,7 @@
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
No Assignee
%li.divider.droplab-item-ignore
- if current_user
@@ -73,38 +73,46 @@
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
No Milestone
%li.filter-dropdown-item{ data: { value: 'upcoming' } }
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
Upcoming
%li.filter-dropdown-item{ 'data-value' => 'started' }
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
Started
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
- %button.btn.btn-link.js-data-value
+ %button.btn.btn-link.js-data-value{ type: 'button' }
{{title}}
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
No Label
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
%span.dropdown-label-box{ style: 'background: {{color}}' }
%span.label-title.js-data-value
{{title}}
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
- %button.btn.btn-link
+ %button.btn.btn-link{ type: 'button' }
%gl-emoji
%span.js-data-value.prepend-left-10
{{name}}
+ #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('No')
= render_if_exists 'shared/issuable/filter_weight', type: type
diff --git a/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml b/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml
new file mode 100644
index 00000000000..a7f24742588
--- /dev/null
+++ b/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Adds Web IDE commits to usage ping
+merge_request: 22007
+author:
+type: added
diff --git a/changelogs/unreleased/48004-db-initialize-migrate.yml b/changelogs/unreleased/48004-db-initialize-migrate.yml
new file mode 100644
index 00000000000..0d691babeca
--- /dev/null
+++ b/changelogs/unreleased/48004-db-initialize-migrate.yml
@@ -0,0 +1,5 @@
+---
+title: Support db migration and initialization for Auto DevOps
+merge_request: 21955
+author:
+type: added
diff --git a/changelogs/unreleased/50904-vuex-show-block.yml b/changelogs/unreleased/50904-vuex-show-block.yml
new file mode 100644
index 00000000000..5607ba3216f
--- /dev/null
+++ b/changelogs/unreleased/50904-vuex-show-block.yml
@@ -0,0 +1,5 @@
+---
+title: Renders Job show page in new Vue app
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/51522-add-new-project-via-import-by-url-auto-populates-slug-but-not-project-name.yml b/changelogs/unreleased/51522-add-new-project-via-import-by-url-auto-populates-slug-but-not-project-name.yml
deleted file mode 100644
index 06b7c9c7b34..00000000000
--- a/changelogs/unreleased/51522-add-new-project-via-import-by-url-auto-populates-slug-but-not-project-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes the 'required' attribute from the 'project name' field
-merge_request: 21770
-author:
-type: other
diff --git a/changelogs/unreleased/51549-runners-table.yml b/changelogs/unreleased/51549-runners-table.yml
deleted file mode 100644
index fe36bfc1b30..00000000000
--- a/changelogs/unreleased/51549-runners-table.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes admin runners table not wrapping content
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/51747-gitlab-com-unable-to-import-a-project-that-was-just-exported.yml b/changelogs/unreleased/51747-gitlab-com-unable-to-import-a-project-that-was-just-exported.yml
deleted file mode 100644
index 29f7fd872bc..00000000000
--- a/changelogs/unreleased/51747-gitlab-com-unable-to-import-a-project-that-was-just-exported.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix NULL pipeline import problem and pipeline user mapping issue
-merge_request: 21875
-author:
-type: fixed
diff --git a/changelogs/unreleased/51782-fix_rename_login_namespace_migration.yml b/changelogs/unreleased/51782-fix_rename_login_namespace_migration.yml
deleted file mode 100644
index 692edb5ded4..00000000000
--- a/changelogs/unreleased/51782-fix_rename_login_namespace_migration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix migration to avoid an exception during upgrade
-merge_request: 22055
-author:
-type: fixed
diff --git a/changelogs/unreleased/_acet-fix-placeholder-note.yml b/changelogs/unreleased/_acet-fix-placeholder-note.yml
new file mode 100644
index 00000000000..68f3d0085c9
--- /dev/null
+++ b/changelogs/unreleased/_acet-fix-placeholder-note.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rendering placeholder notes
+merge_request: 22078
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml b/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml
new file mode 100644
index 00000000000..7c707cfe5a0
--- /dev/null
+++ b/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml
@@ -0,0 +1,5 @@
+---
+title: Add copy to clipboard button for application id and secret
+merge_request: 21978
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml b/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml
new file mode 100644
index 00000000000..db68ed10a7e
--- /dev/null
+++ b/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml
@@ -0,0 +1,5 @@
+---
+title: Align collapsed sidebar avatar container
+merge_request: 22044
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/ccr-wip_filter.yml b/changelogs/unreleased/ccr-wip_filter.yml
new file mode 100644
index 00000000000..07d85ec02ae
--- /dev/null
+++ b/changelogs/unreleased/ccr-wip_filter.yml
@@ -0,0 +1,5 @@
+---
+title: Added search functionality for Work In Progress (WIP) merge requests
+merge_request: 18119
+author: Chantal Rollison
+type: added
diff --git a/changelogs/unreleased/feature-flags-mvc.yml b/changelogs/unreleased/feature-flags-mvc.yml
new file mode 100644
index 00000000000..6a709f7cf07
--- /dev/null
+++ b/changelogs/unreleased/feature-flags-mvc.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid close icon leaving the modal header
+merge_request: 21904
+author:
+type: changed
diff --git a/changelogs/unreleased/mr-file-tree-data.yml b/changelogs/unreleased/mr-file-tree-data.yml
new file mode 100644
index 00000000000..a82087ea148
--- /dev/null
+++ b/changelogs/unreleased/mr-file-tree-data.yml
@@ -0,0 +1,5 @@
+---
+title: Added tree of changed files to merge request diffs
+merge_request: 21833
+author:
+type: added
diff --git a/changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml b/changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml
deleted file mode 100644
index f18e6207b87..00000000000
--- a/changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Error 500 when forking projects with Gravatar disabled
-merge_request:
-author:
-type: fixed
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index 4d8d35bf6cf..eccf82ab8dc 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -3,7 +3,6 @@
# that we can stub it for testing, as it is only called when metrics are
# enabled.
#
-# rubocop:disable Metrics/AbcSize
def instrument_classes(instrumentation)
instrumentation.instrument_instance_methods(Gitlab::Shell)
@@ -48,16 +47,6 @@ def instrument_classes(instrumentation)
instrumentation.instrument_methods(Premailer::Adapter::Nokogiri)
instrumentation.instrument_instance_methods(Premailer::Adapter::Nokogiri)
- [
- :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
- :Tag, :TagCollection, :Tree
- ].each do |name|
- const = Rugged.const_get(name)
-
- instrumentation.instrument_methods(const)
- instrumentation.instrument_instance_methods(const)
- end
-
instrumentation.instrument_methods(Banzai::Renderer)
instrumentation.instrument_methods(Banzai::Querying)
@@ -101,7 +90,6 @@ def instrument_classes(instrumentation)
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
end
-# rubocop:enable Metrics/AbcSize
# With prometheus enabled by default this breaks all specs
# that stubs methods using `any_instance_of` for the models reloaded here.
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
deleted file mode 100644
index ea9cc151a57..00000000000
--- a/config/initializers/gollum.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# WARNING changes in this file must be manually propagated to gitaly-ruby.
-#
-# https://gitlab.com/gitlab-org/gitaly/blob/master/ruby/lib/gitlab/gollum.rb
-
-module Gollum
- GIT_ADAPTER = "rugged".freeze
-end
-require "gollum-lib"
-
-module Gollum
- class Page
- def text_data(encoding = nil)
- data = if raw_data.respond_to?(:encoding)
- raw_data.force_encoding(encoding || Encoding::UTF_8)
- else
- raw_data
- end
-
- Gitlab::EncodingHelper.encode!(data)
- end
- end
-end
-
-Rails.application.configure do
- config.after_initialize do
- Gollum::Page.per_page = Kaminari.config.default_per_page
- end
-end
diff --git a/danger/eslint/Dangerfile b/danger/eslint/Dangerfile
new file mode 100644
index 00000000000..f78488cfd0a
--- /dev/null
+++ b/danger/eslint/Dangerfile
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+def get_eslint_files(files)
+ files.select do |file|
+ file.end_with?('.js', '.vue') &&
+ File.read(file).include?('/* eslint-disable')
+ end
+end
+
+eslint_candidates = get_eslint_files(git.added_files + git.modified_files)
+
+return if eslint_candidates.empty?
+
+warn 'This merge request changed files with disabled eslint rules. Please consider fixing them.'
+
+markdown(<<~MARKDOWN)
+ ## Disabled eslint rules
+
+ The following files have disabled `eslint` rules. Please consider fixing them:
+
+ * #{eslint_candidates.map { |path| "`#{path}`" }.join("\n* ")}
+
+ Run the following command for more details
+
+ ```
+ node_modules/.bin/eslint --report-unused-disable-directives --no-inline-config \\
+ #{eslint_candidates.map { |path| " '#{path}'" }.join(" \\\n")}
+ ```
+MARKDOWN
diff --git a/danger/prettier/Dangerfile b/danger/prettier/Dangerfile
new file mode 100644
index 00000000000..86f9f6af475
--- /dev/null
+++ b/danger/prettier/Dangerfile
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+def get_prettier_files(files)
+ files.select do |file|
+ file.end_with?('.js', '.scss', '.vue')
+ end
+end
+
+prettier_candidates = get_prettier_files(git.added_files + git.modified_files)
+
+return if prettier_candidates.empty?
+
+unpretty = `node_modules/prettier/bin-prettier.js --list-different #{prettier_candidates.join(" ")}`
+ .split(/$/)
+ .map(&:strip)
+ .reject(&:empty?)
+
+return if unpretty.empty?
+
+warn 'This merge request changed frontend files without pretty printing them.'
+
+markdown(<<~MARKDOWN)
+ ## Pretty print Frontend files
+
+ The following files should have been pretty printed with `prettier`:
+
+ * #{unpretty.map { |path| "`#{path}`" }.join("\n* ")}
+
+ Please run
+
+ ```
+ node_modules/.bin/prettier --write \\
+ #{unpretty.map { |path| " '#{path}'" }.join(" \\\n")}
+ ```
+
+ Also consider auto-formatting [on-save].
+
+ [on-save]: https://docs.gitlab.com/ee/development/new_fe_guide/style/prettier.html
+MARKDOWN
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 4c099581f07..b37e7698ab4 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -47,6 +47,7 @@ Parameters:
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
| `search` | string | no | Search merge requests against their `title` and `description` |
+| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
```json
[
diff --git a/doc/ci/review_apps/img/continuous-delivery-review-apps.svg b/doc/ci/review_apps/img/continuous-delivery-review-apps.svg
new file mode 100644
index 00000000000..90ac763a01e
--- /dev/null
+++ b/doc/ci/review_apps/img/continuous-delivery-review-apps.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="1170px" height="638px" viewBox="0 0 1170 638" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 41 (35326) - http://www.bohemiancoding.com/sketch -->
+ <title>review-apps-CD-outlined</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="review-apps-CD-outlined">
+ <g id="Group-4-Copy" transform="translate(22.000000, 43.000000)">
+ <polygon id="Line-Copy-25" fill="#E0513E" points="163.545966 259 858.690432 259 858.690432 255 163.545966 255"></polygon>
+ <path d="M870.470919,266.702602 C875.287562,266.702602 879.19137,262.80085 879.19137,257.988848 C879.19137,253.176845 875.287562,249.275093 870.470919,249.275093 C865.654276,249.275093 861.750469,253.176845 861.750469,257.988848 C861.750469,262.80085 865.654276,266.702602 870.470919,266.702602 Z M870.470919,270.702602 C863.445609,270.702602 857.750469,265.01046 857.750469,257.988848 C857.750469,250.967235 863.445609,245.275093 870.470919,245.275093 C877.49623,245.275093 883.19137,250.967235 883.19137,257.988848 C883.19137,265.01046 877.49623,270.702602 870.470919,270.702602 Z" id="Oval-Copy-20" fill="#E04733"></path>
+ <path d="M991.315197,145.921933 C996.13184,145.921933 1000.03565,142.020181 1000.03565,137.208178 C1000.03565,132.396176 996.13184,128.494424 991.315197,128.494424 C986.498554,128.494424 982.594747,132.396176 982.594747,137.208178 C982.594747,142.020181 986.498554,145.921933 991.315197,145.921933 Z M991.315197,149.921933 C984.289886,149.921933 978.594747,144.229791 978.594747,137.208178 C978.594747,130.186566 984.289886,124.494424 991.315197,124.494424 C998.340508,124.494424 1004.03565,130.186566 1004.03565,137.208178 C1004.03565,144.229791 998.340508,149.921933 991.315197,149.921933 Z" id="Oval-Copy-22" fill="#E04733"></path>
+ <g id="Group-7" transform="translate(0.000000, 236.799257)" fill="#E04733">
+ <path d="M151.765478,29.9033457 C156.582121,29.9033457 160.485929,26.0015933 160.485929,21.1895911 C160.485929,16.3775889 156.582121,12.4758364 151.765478,12.4758364 C146.948835,12.4758364 143.045028,16.3775889 143.045028,21.1895911 C143.045028,26.0015933 146.948835,29.9033457 151.765478,29.9033457 Z M151.765478,33.9033457 C144.740168,33.9033457 139.045028,28.2112039 139.045028,21.1895911 C139.045028,14.1679783 144.740168,8.47583643 151.765478,8.47583643 C158.790789,8.47583643 164.485929,14.1679783 164.485929,21.1895911 C164.485929,28.2112039 158.790789,33.9033457 151.765478,33.9033457 Z" id="Oval-Copy-10"></path>
+ <path d="M0.187705647,10.1392011 L3.30410548,10.1392011 L7.34270526,21.3328005 L8.86910518,25.5940003 L8.99630517,25.5940003 L10.4591051,21.3328005 L14.4659049,10.1392011 L17.5823047,10.1392011 L17.5823047,31 L15.0701048,31 L15.0701048,19.5202006 C15.0701048,18.587396 15.1072045,17.5592063 15.1814048,16.4356008 C15.2556052,15.3119952 15.3351044,14.2838056 15.4199048,13.351001 L15.2927048,13.351001 L13.6073049,17.9938007 L9.60050514,28.9012001 L8.10590522,28.9012001 L4.06730544,17.9938007 L2.41370553,13.351001 L2.28650553,13.351001 C2.35010585,14.2838056 2.4243051,15.3119952 2.50910552,16.4356008 C2.59390594,17.5592063 2.63630552,18.587396 2.63630552,19.5202006 L2.63630552,31 L0.187705647,31 L0.187705647,10.1392011 Z M32.1785039,22.5094005 L31.192704,19.3294006 C30.8111021,18.1633949 30.4507057,17.0027065 30.111504,15.8473008 C29.7723024,14.6918951 29.4331058,13.510007 29.0939041,12.301601 L28.9667041,12.301601 C28.6487025,13.510007 28.3201058,14.6918951 27.9809041,15.8473008 C27.6417025,17.0027065 27.2813061,18.1633949 26.8997042,19.3294006 L25.9139043,22.5094005 L32.1785039,22.5094005 Z M32.8463039,24.6400003 L25.2461043,24.6400003 L23.2427044,31 L20.5397045,31 L27.5993042,10.1392011 L30.588504,10.1392011 L37.6481036,31 L34.8179038,31 L32.8463039,24.6400003 Z M40.6691035,26.4526002 C41.4111071,27.2370041 42.2855983,27.8676978 43.2926033,28.3447001 C44.2996083,28.8217025 45.3436978,29.0602001 46.4249032,29.0602001 C47.80291,29.0602001 48.8734992,28.7475032 49.636703,28.1221002 C50.3999068,27.4966971 50.7815029,26.6752053 50.7815029,25.6576003 C50.7815029,25.1275977 50.6914038,24.6771022 50.5112029,24.3061004 C50.331002,23.9350985 50.0872045,23.6171017 49.779803,23.3521004 C49.4724014,23.0870991 49.1067051,22.8486015 48.682703,22.6366005 C48.2587009,22.4245994 47.8029055,22.2020016 47.3153031,21.9688005 L44.3261033,20.6650006 C43.8385009,20.4529995 43.3403059,20.1986021 42.8315033,19.9018006 C42.3227008,19.6049991 41.8669054,19.2446028 41.4641034,18.8206007 C41.0613014,18.3965986 40.7327047,17.8931036 40.4783035,17.3101007 C40.2239022,16.7270979 40.0967035,16.0540046 40.0967035,15.2908008 C40.0967035,14.506397 40.2610018,13.7750043 40.5896035,13.096601 C40.9182051,12.4181976 41.3740005,11.8299035 41.9570034,11.3317011 C42.5400063,10.8334986 43.2289993,10.4466025 44.0240033,10.1710011 C44.8190072,9.89539976 45.6934984,9.75760115 46.6475031,9.75760115 C47.8983093,9.75760115 49.0536977,9.99609875 50.113703,10.4731011 C51.1737082,10.9501035 52.0746991,11.5701972 52.8167028,12.333401 L51.3857029,14.0506009 C50.7496997,13.4357979 50.0448068,12.9535027 49.271003,12.603701 C48.4971992,12.2538993 47.622708,12.079001 46.6475031,12.079001 C45.4814974,12.079001 44.5434068,12.3492983 43.8332033,12.889901 C43.1229998,13.4305037 42.7679033,14.1777961 42.7679033,15.1318009 C42.7679033,15.6406034 42.8686023,16.0698991 43.0700033,16.4197008 C43.2714043,16.7695025 43.5417016,17.0768994 43.8809033,17.3419007 C44.220105,17.606902 44.5911012,17.8400997 44.9939032,18.0415007 C45.3967052,18.2429017 45.8101011,18.4283998 46.2341032,18.5980007 L49.191503,19.8700006 C49.7851059,20.1244019 50.3469003,20.4158989 50.8769029,20.7445006 C51.4069055,21.0731022 51.8627009,21.4546983 52.2443028,21.8893005 C52.6259047,22.3239026 52.9280017,22.8326975 53.1506028,23.4157004 C53.3732039,23.9987033 53.4845028,24.6717965 53.4845028,25.4350003 C53.4845028,26.2618044 53.3202044,27.0355966 52.9916028,27.7564002 C52.6630012,28.4772037 52.1913059,29.1078974 51.5765029,29.6485001 C50.9616998,30.1891027 50.2197073,30.6130985 49.350503,30.9205 C48.4812987,31.2279015 47.4955086,31.3816 46.3931032,31.3816 C44.9302959,31.3816 43.5735096,31.1060028 42.3227034,30.5548 C41.0718972,30.0035973 39.9907081,29.2510049 39.0791035,28.2970001 L40.6691035,26.4526002 Z M61.9115023,12.365201 L55.6151027,12.365201 L55.6151027,10.1392011 L70.8791018,10.1392011 L70.8791018,12.365201 L64.5827022,12.365201 L64.5827022,31 L61.9115023,31 L61.9115023,12.365201 Z M74.6315016,10.1392011 L86.651901,10.1392011 L86.651901,12.365201 L77.2709015,12.365201 L77.2709015,18.9160007 L85.1891011,18.9160007 L85.1891011,21.1738005 L77.2709015,21.1738005 L77.2709015,28.7422001 L86.969901,28.7422001 L86.969901,31 L74.6315016,31 L74.6315016,10.1392011 Z M94.0295006,20.0290006 L97.5275004,20.0290006 C99.1599085,20.0290006 100.410696,19.6951039 101.2799,19.0273006 C102.149104,18.3594973 102.5837,17.3472075 102.5837,15.9904008 C102.5837,14.612394 102.149104,13.6478037 101.2799,13.096601 C100.410696,12.5453982 99.1599085,12.269801 97.5275004,12.269801 L94.0295006,12.269801 L94.0295006,20.0290006 Z M102.8381,31 L97.8137004,22.1914005 L94.0295006,22.1914005 L94.0295006,31 L91.3901007,31 L91.3901007,10.1392011 L97.9091004,10.1392011 C98.9691056,10.1392011 99.9495958,10.2399001 100.8506,10.4413011 C101.751605,10.6427021 102.525397,10.9765987 103.172,11.4430011 C103.818603,11.9094034 104.322098,12.5135973 104.6825,13.255601 C105.042902,13.9976046 105.2231,14.9091955 105.2231,15.9904008 C105.2231,17.6228089 104.799104,18.9265958 103.9511,19.9018006 C103.103096,20.8770054 101.968907,21.5447987 100.5485,21.9052005 L105.8273,31 L102.8381,31 Z" id="MASTER"></path>
+ </g>
+ <path d="M829.454852,142.471188 C830.196855,143.255591 831.071347,143.886285 832.078351,144.363288 C833.085356,144.84029 834.129446,145.078787 835.210651,145.078787 C836.588658,145.078787 837.659247,144.766091 838.422451,144.140688 C839.185655,143.515284 839.567251,142.693793 839.567251,141.676188 C839.567251,141.146185 839.477152,140.69569 839.296951,140.324688 C839.11675,139.953686 838.872953,139.635689 838.565551,139.370688 C838.25815,139.105686 837.892453,138.867189 837.468451,138.655188 C837.044449,138.443187 836.588654,138.220589 836.101051,137.987388 L833.111851,136.683588 C832.624249,136.471587 832.126054,136.217189 831.617252,135.920388 C831.108449,135.623586 830.652654,135.26319 830.249852,134.839188 C829.84705,134.415186 829.518453,133.911691 829.264052,133.328688 C829.00965,132.745685 828.882452,132.072592 828.882452,131.309388 C828.882452,130.524984 829.04675,129.793592 829.375352,129.115188 C829.703953,128.436785 830.159749,127.848491 830.742752,127.350288 C831.325754,126.852086 832.014748,126.46519 832.809751,126.189588 C833.604755,125.913987 834.479247,125.776189 835.433251,125.776189 C836.684057,125.776189 837.839446,126.014686 838.899451,126.491688 C839.959456,126.968691 840.860447,127.588785 841.602451,128.351988 L840.171451,130.069188 C839.535448,129.454385 838.830555,128.97209 838.056751,128.622288 C837.282947,128.272487 836.408456,128.097588 835.433251,128.097588 C834.267246,128.097588 833.329155,128.367886 832.618951,128.908488 C831.908748,129.449091 831.553652,130.196383 831.553652,131.150388 C831.553652,131.659191 831.654351,132.088486 831.855752,132.438288 C832.057152,132.78809 832.32745,133.095487 832.666651,133.360488 C833.005853,133.625489 833.376849,133.858687 833.779651,134.060088 C834.182453,134.261489 834.595849,134.446987 835.019851,134.616588 L837.977251,135.888588 C838.570854,136.142989 839.132648,136.434486 839.662651,136.763088 C840.192654,137.09169 840.648449,137.473286 841.030051,137.907888 C841.411653,138.34249 841.71375,138.851285 841.936351,139.434288 C842.158952,140.017291 842.270251,140.690384 842.270251,141.453588 C842.270251,142.280392 842.105953,143.054184 841.777351,143.774988 C841.448749,144.495791 840.977054,145.126485 840.362251,145.667087 C839.747448,146.20769 839.005455,146.631686 838.136251,146.939087 C837.267047,147.246489 836.281257,147.400187 835.178851,147.400187 C833.716044,147.400187 832.359258,147.12459 831.108452,146.573387 C829.857645,146.022185 828.776456,145.269592 827.864852,144.315588 L829.454852,142.471188 Z M850.69725,128.383788 L844.400851,128.383788 L844.400851,126.157788 L859.66485,126.157788 L859.66485,128.383788 L853.36845,128.383788 L853.36845,147.018587 L850.69725,147.018587 L850.69725,128.383788 Z M872.289449,138.527988 L871.303649,135.347988 C870.922047,134.181982 870.561651,133.021294 870.222449,131.865888 C869.883248,130.710482 869.544051,129.528594 869.204849,128.320188 L869.077649,128.320188 C868.759648,129.528594 868.431051,130.710482 868.09185,131.865888 C867.752648,133.021294 867.392251,134.181982 867.01065,135.347988 L866.02485,138.527988 L872.289449,138.527988 Z M872.957249,140.658588 L865.35705,140.658588 L863.35365,147.018587 L860.65065,147.018587 L867.71025,126.157788 L870.699449,126.157788 L877.759049,147.018587 L874.928849,147.018587 L872.957249,140.658588 Z M879.508049,136.588188 C879.508049,134.91338 879.746547,133.408195 880.223549,132.072588 C880.700551,130.736982 881.368345,129.602793 882.226949,128.669988 C883.085553,127.737184 884.097843,127.021691 885.263849,126.523488 C886.429854,126.025286 887.712441,125.776189 889.111648,125.776189 C890.553256,125.776189 891.756343,126.046486 892.720948,126.587088 C893.685553,127.127691 894.475245,127.715985 895.090048,128.351988 L893.595448,130.037388 C893.065446,129.486186 892.455952,129.02509 891.766948,128.654088 C891.077945,128.283087 890.203454,128.097588 889.143448,128.097588 C888.083443,128.097588 887.129453,128.293686 886.281449,128.685888 C885.433444,129.07809 884.712652,129.639885 884.119049,130.371288 C883.525446,131.102692 883.06435,131.987783 882.735749,133.026588 C882.407147,134.065393 882.242849,135.231382 882.242849,136.524588 C882.242849,137.838994 882.396547,139.020883 882.703949,140.070288 C883.01135,141.119693 883.456546,142.015384 884.039549,142.757388 C884.622552,143.499391 885.343344,144.071786 886.201949,144.474587 C887.060553,144.877389 888.051643,145.078787 889.175248,145.078787 C889.917252,145.078787 890.622145,144.967489 891.289948,144.744887 C891.957752,144.522286 892.503646,144.220189 892.927648,143.838588 L892.927648,138.400788 L888.507448,138.400788 L888.507448,136.206588 L895.344448,136.206588 L895.344448,144.983387 C894.666045,145.682991 893.770354,146.260685 892.657348,146.716487 C891.544343,147.17229 890.288255,147.400187 888.889048,147.400187 C887.511042,147.400187 886.249654,147.15639 885.104849,146.668787 C883.960043,146.181185 882.974253,145.476292 882.147449,144.554087 C881.320645,143.631883 880.674051,142.497694 880.207649,141.151488 C879.741247,139.805281 879.508049,138.284196 879.508049,136.588188 Z M900.337048,126.157788 L902.976448,126.157788 L902.976448,147.018587 L900.337048,147.018587 L900.337048,126.157788 Z M908.700447,126.157788 L911.435247,126.157788 L918.971847,139.259388 L921.229647,143.584188 L921.356847,143.584188 C921.293246,142.524182 921.224347,141.427093 921.150147,140.292888 C921.075946,139.158682 921.038847,138.040393 921.038847,136.937988 L921.038847,126.157788 L923.551047,126.157788 L923.551047,147.018587 L920.816247,147.018587 L913.247847,133.885188 L910.990047,129.592188 L910.862847,129.592188 C910.947648,130.652194 911.027147,131.728083 911.101347,132.819888 C911.175548,133.911694 911.212647,135.008782 911.212647,136.111188 L911.212647,147.018587 L908.700447,147.018587 L908.700447,126.157788 Z M928.066646,136.588188 C928.066646,134.91338 928.305144,133.408195 928.782146,132.072588 C929.259149,130.736982 929.926942,129.602793 930.785546,128.669988 C931.64415,127.737184 932.65644,127.021691 933.822446,126.523488 C934.988452,126.025286 936.271039,125.776189 937.670246,125.776189 C939.111853,125.776189 940.314941,126.046486 941.279546,126.587088 C942.24415,127.127691 943.033842,127.715985 943.648645,128.351988 L942.154046,130.037388 C941.624043,129.486186 941.014549,129.02509 940.325546,128.654088 C939.636542,128.283087 938.762051,128.097588 937.702046,128.097588 C936.642041,128.097588 935.68805,128.293686 934.840046,128.685888 C933.992042,129.07809 933.271249,129.639885 932.677646,130.371288 C932.084043,131.102692 931.622948,131.987783 931.294346,133.026588 C930.965745,134.065393 930.801446,135.231382 930.801446,136.524588 C930.801446,137.838994 930.955145,139.020883 931.262546,140.070288 C931.569948,141.119693 932.015143,142.015384 932.598146,142.757388 C933.181149,143.499391 933.901942,144.071786 934.760546,144.474587 C935.61915,144.877389 936.61024,145.078787 937.733846,145.078787 C938.475849,145.078787 939.180742,144.967489 939.848546,144.744887 C940.516349,144.522286 941.062243,144.220189 941.486246,143.838588 L941.486246,138.400788 L937.066046,138.400788 L937.066046,136.206588 L943.903045,136.206588 L943.903045,144.983387 C943.224642,145.682991 942.328951,146.260685 941.215946,146.716487 C940.10294,147.17229 938.846853,147.400187 937.447646,147.400187 C936.069639,147.400187 934.808252,147.15639 933.663446,146.668787 C932.51864,146.181185 931.53285,145.476292 930.706046,144.554087 C929.879242,143.631883 929.232649,142.497694 928.766246,141.151488 C928.299844,139.805281 928.066646,138.284196 928.066646,136.588188 Z" id="STAGING" fill="#E04733"></path>
+ <path d="M888.668416,1.13920113 L895.060216,1.13920113 C896.205021,1.13920113 897.254411,1.24520006 898.208415,1.45720111 C899.16242,1.66920216 899.973312,2.01369869 900.641115,2.49070105 C901.308919,2.96770341 901.828313,3.59839707 902.199315,4.38280095 C902.570317,5.16720483 902.755815,6.13179513 902.755815,7.27660079 C902.755815,8.37900625 902.570317,9.33299666 902.199315,10.1386006 C901.828313,10.9442046 901.303619,11.6119979 900.625215,12.1420005 C899.946812,12.6720032 899.13592,13.0694992 898.192515,13.3345005 C897.249111,13.5995018 896.205021,13.7320004 895.060216,13.7320004 L891.307816,13.7320004 L891.307816,22 L888.668416,22 L888.668416,1.13920113 Z M894.742216,11.5696006 C896.565425,11.5696006 897.916911,11.225104 898.796715,10.5361006 C899.67652,9.84709721 900.116415,8.76060813 900.116415,7.27660079 C900.116415,5.77139335 899.67122,4.72730385 898.780815,4.14430096 C897.890411,3.56129808 896.544224,3.26980101 894.742216,3.26980101 L891.307816,3.26980101 L891.307816,11.5696006 L894.742216,11.5696006 Z M909.624615,11.0290006 L913.122615,11.0290006 C914.755023,11.0290006 916.00581,10.6951039 916.875014,10.0273006 C917.744219,9.35949734 918.178814,8.34720752 918.178814,6.99040081 C918.178814,5.61239399 917.744219,4.64780369 916.875014,4.09660097 C916.00581,3.54539824 914.755023,3.26980101 913.122615,3.26980101 L909.624615,3.26980101 L909.624615,11.0290006 Z M918.433214,22 L913.408815,13.1914005 L909.624615,13.1914005 L909.624615,22 L906.985215,22 L906.985215,1.13920113 L913.504215,1.13920113 C914.56422,1.13920113 915.54471,1.23990011 916.445714,1.44130111 C917.346719,1.64270211 918.120511,1.97659875 918.767114,2.44300106 C919.413717,2.90940336 919.917212,3.51359729 920.277614,4.25560096 C920.638016,4.99760463 920.818214,5.90919546 920.818214,6.99040081 C920.818214,8.62280888 920.394218,9.92659578 919.546214,10.9018006 C918.69821,11.8770054 917.564021,12.5447987 916.143614,12.9052005 L921.422414,22 L918.433214,22 Z M933.156613,22.3816 C931.842207,22.3816 930.639119,22.1272025 929.547314,21.6184 C928.455508,21.1095975 927.517418,20.3835048 926.733014,19.4401001 C925.94861,18.4966955 925.339116,17.351907 924.904514,16.0057003 C924.469912,14.6594937 924.252614,13.1490089 924.252614,11.4742006 C924.252614,9.79939228 924.469912,8.29950736 924.904514,6.97450081 C925.339116,5.64949426 925.94861,4.52590555 926.733014,3.60370099 C927.517418,2.68149643 928.455508,1.97660352 929.547314,1.48900111 C930.639119,1.0013987 931.842207,0.757601147 933.156613,0.757601147 C934.47102,0.757601147 935.674108,1.00669864 936.765913,1.50490111 C937.857719,2.00310357 938.801109,2.71329643 939.596113,3.63550099 C940.391117,4.55770555 941.005911,5.68129426 941.440513,7.00630081 C941.875115,8.33130736 942.092413,9.82059239 942.092413,11.4742006 C942.092413,13.1490089 941.875115,14.6594937 941.440513,16.0057003 C941.005911,17.351907 940.391117,18.4966955 939.596113,19.4401001 C938.801109,20.3835048 937.857719,21.1095975 936.765913,21.6184 C935.674108,22.1272025 934.47102,22.3816 933.156613,22.3816 Z M933.156613,20.0602001 C934.089418,20.0602001 934.93211,19.8588021 935.684713,19.4560001 C936.437317,19.0531981 937.083911,18.475504 937.624513,17.7229002 C938.165116,16.9702965 938.583812,16.0693056 938.880613,15.0199004 C939.177415,13.9704952 939.325813,12.7886071 939.325813,11.4742006 C939.325813,10.1809942 939.177415,9.01500589 938.880613,7.97620076 C938.583812,6.93739562 938.165116,6.05760446 937.624513,5.3368009 C937.083911,4.61599733 936.437317,4.05950293 935.684713,3.66730099 C934.93211,3.27509905 934.089418,3.07900102 933.156613,3.07900102 C932.223809,3.07900102 931.381117,3.27509905 930.628514,3.66730099 C929.87591,4.05950293 929.229316,4.61599733 928.688714,5.3368009 C928.148111,6.05760446 927.729415,6.93739562 927.432614,7.97620076 C927.135812,9.01500589 926.987414,10.1809942 926.987414,11.4742006 C926.987414,12.7886071 927.135812,13.9704952 927.432614,15.0199004 C927.729415,16.0693056 928.148111,16.9702965 928.688714,17.7229002 C929.229316,18.475504 929.87591,19.0531981 930.628514,19.4560001 C931.381117,19.8588021 932.223809,20.0602001 933.156613,20.0602001 Z M946.576213,1.13920113 L951.791412,1.13920113 C955.013828,1.13920113 957.462404,2.02959217 959.137212,3.81040098 C960.81202,5.59120979 961.649412,8.14578411 961.649412,11.4742006 C961.649412,13.1278087 961.437414,14.6064939 961.013412,15.9103003 C960.58941,17.2141068 959.964016,18.3164957 959.137212,19.2175002 C958.310408,20.1185046 957.292818,20.8074977 956.084412,21.2845 C954.876006,21.7615024 953.48742,22 951.918612,22 L946.576213,22 L946.576213,1.13920113 Z M951.600612,19.8376001 C954.038625,19.8376001 955.867106,19.1062075 957.086112,17.6434002 C958.305118,16.180593 958.914612,14.1242137 958.914612,11.4742006 C958.914612,8.82418746 958.305118,6.79960782 957.086112,5.4004009 C955.867106,4.00119398 954.038625,3.30160101 951.600612,3.30160101 L949.215613,3.30160101 L949.215613,19.8376001 L951.600612,19.8376001 Z M966.037812,1.13920113 L968.677212,1.13920113 L968.677212,13.4458005 C968.677212,14.6966066 968.80441,15.7459961 969.058812,16.5940003 C969.313213,17.4420045 969.657709,18.1203977 970.092311,18.6292002 C970.526914,19.1380027 971.041008,19.503699 971.634611,19.7263001 C972.228214,19.9489012 972.864208,20.0602001 973.542611,20.0602001 C974.242215,20.0602001 974.888808,19.9489012 975.482411,19.7263001 C976.076014,19.503699 976.595409,19.1380027 977.040611,18.6292002 C977.485813,18.1203977 977.83561,17.4420045 978.090011,16.5940003 C978.344412,15.7459961 978.471611,14.6966066 978.471611,13.4458005 L978.471611,1.13920113 L981.015611,1.13920113 L981.015611,13.3822005 C981.015611,15.0570087 980.824813,16.4667946 980.443211,17.6116002 C980.061609,18.7564059 979.536914,19.6838966 978.869111,20.3941001 C978.201308,21.1043036 977.411616,21.6130985 976.500011,21.9205 C975.588407,22.2279015 974.602617,22.3816 973.542611,22.3816 C972.482606,22.3816 971.491516,22.2279015 970.569311,21.9205 C969.647107,21.6130985 968.852115,21.1043036 968.184312,20.3941001 C967.516508,19.6838966 966.991814,18.7564059 966.610212,17.6116002 C966.22861,16.4667946 966.037812,15.0570087 966.037812,13.3822005 L966.037812,1.13920113 Z M985.435811,11.5696006 C985.435811,9.89479228 985.669008,8.38960741 986.135411,7.05400081 C986.601813,5.7183942 987.248406,4.5842056 988.07521,3.65140099 C988.902015,2.71859638 989.882505,2.00310357 991.01671,1.50490111 C992.150916,1.00669864 993.385804,0.757601147 994.72141,0.757601147 C995.993416,0.757601147 997.106405,1.01729854 998.06041,1.5367011 C999.014415,2.05610367 999.798807,2.65499765 1000.41361,3.33340101 L998.91901,5.01880092 C998.367807,4.42519798 997.758313,3.95350272 997.09051,3.60370099 C996.422707,3.25389926 995.643615,3.07900102 994.75321,3.07900102 C993.756805,3.07900102 992.855814,3.27509905 992.05021,3.66730099 C991.244606,4.05950293 990.555613,4.62129728 989.98321,5.3527009 C989.410808,6.08410452 988.965612,6.96919562 988.64761,8.00800076 C988.329609,9.04680589 988.17061,10.2127942 988.17061,11.5060006 C988.17061,12.8204071 988.324309,14.0022952 988.63171,15.0517004 C988.939112,16.1011056 989.368408,16.9967966 989.91961,17.7388002 C990.470813,18.4808039 991.143906,19.0531981 991.93891,19.4560001 C992.733914,19.8588021 993.629605,20.0602001 994.62601,20.0602001 C995.643615,20.0602001 996.528706,19.8588021 997.28131,19.4560001 C998.033914,19.0531981 998.749407,18.4808039 999.42781,17.7388002 L1000.92241,19.3606001 C1000.09561,20.3146049 999.162815,21.0565974 998.12401,21.5866 C997.085205,22.1166026 995.898017,22.3816 994.56241,22.3816 C993.248004,22.3816 992.034316,22.1378024 990.92131,21.6502 C989.808305,21.1625976 988.843715,20.4577047 988.02751,19.5355001 C987.211306,18.6132956 986.575313,17.479107 986.119511,16.1329003 C985.663708,14.7866937 985.435811,13.265609 985.435811,11.5696006 Z M1009.12681,3.36520101 L1002.83041,3.36520101 L1002.83041,1.13920113 L1018.09441,1.13920113 L1018.09441,3.36520101 L1011.79801,3.36520101 L1011.79801,22 L1009.12681,22 L1009.12681,3.36520101 Z M1021.84681,1.13920113 L1024.48621,1.13920113 L1024.48621,22 L1021.84681,22 L1021.84681,1.13920113 Z M1037.90581,22.3816 C1036.5914,22.3816 1035.38831,22.1272025 1034.29651,21.6184 C1033.2047,21.1095975 1032.26661,20.3835048 1031.48221,19.4401001 C1030.6978,18.4966955 1030.08831,17.351907 1029.65371,16.0057003 C1029.21911,14.6594937 1029.00181,13.1490089 1029.00181,11.4742006 C1029.00181,9.79939228 1029.21911,8.29950736 1029.65371,6.97450081 C1030.08831,5.64949426 1030.6978,4.52590555 1031.48221,3.60370099 C1032.26661,2.68149643 1033.2047,1.97660352 1034.29651,1.48900111 C1035.38831,1.0013987 1036.5914,0.757601147 1037.90581,0.757601147 C1039.22021,0.757601147 1040.4233,1.00669864 1041.51511,1.50490111 C1042.60691,2.00310357 1043.5503,2.71329643 1044.34531,3.63550099 C1045.14031,4.55770555 1045.75511,5.68129426 1046.18971,7.00630081 C1046.62431,8.33130736 1046.84161,9.82059239 1046.84161,11.4742006 C1046.84161,13.1490089 1046.62431,14.6594937 1046.18971,16.0057003 C1045.75511,17.351907 1045.14031,18.4966955 1044.34531,19.4401001 C1043.5503,20.3835048 1042.60691,21.1095975 1041.51511,21.6184 C1040.4233,22.1272025 1039.22021,22.3816 1037.90581,22.3816 Z M1037.90581,20.0602001 C1038.83861,20.0602001 1039.6813,19.8588021 1040.43391,19.4560001 C1041.18651,19.0531981 1041.8331,18.475504 1042.37371,17.7229002 C1042.91431,16.9702965 1043.33301,16.0693056 1043.62981,15.0199004 C1043.92661,13.9704952 1044.07501,12.7886071 1044.07501,11.4742006 C1044.07501,10.1809942 1043.92661,9.01500589 1043.62981,7.97620076 C1043.33301,6.93739562 1042.91431,6.05760446 1042.37371,5.3368009 C1041.8331,4.61599733 1041.18651,4.05950293 1040.43391,3.66730099 C1039.6813,3.27509905 1038.83861,3.07900102 1037.90581,3.07900102 C1036.973,3.07900102 1036.13031,3.27509905 1035.37771,3.66730099 C1034.6251,4.05950293 1033.97851,4.61599733 1033.43791,5.3368009 C1032.89731,6.05760446 1032.47861,6.93739562 1032.18181,7.97620076 C1031.88501,9.01500589 1031.73661,10.1809942 1031.73661,11.4742006 C1031.73661,12.7886071 1031.88501,13.9704952 1032.18181,15.0199004 C1032.47861,16.0693056 1032.89731,16.9702965 1033.43791,17.7229002 C1033.97851,18.475504 1034.6251,19.0531981 1035.37771,19.4560001 C1036.13031,19.8588021 1036.973,20.0602001 1037.90581,20.0602001 Z M1051.32541,1.13920113 L1054.06021,1.13920113 L1061.59681,14.2408004 L1063.85461,18.5656002 L1063.98181,18.5656002 C1063.91821,17.5055949 1063.84931,16.408506 1063.77511,15.2743004 C1063.70091,14.1400948 1063.66381,13.021806 1063.66381,11.9194005 L1063.66381,1.13920113 L1066.17601,1.13920113 L1066.17601,22 L1063.44121,22 L1055.87281,8.86660071 L1053.61501,4.57360094 L1053.48781,4.57360094 C1053.57261,5.63360618 1053.65211,6.70949537 1053.72631,7.80130077 C1053.80051,8.89310617 1053.83761,9.99019514 1053.83761,11.0926006 L1053.83761,22 L1051.32541,22 L1051.32541,1.13920113 Z" id="PRODUCTION-Copy-2" fill="#E04733"></path>
+ <polygon id="Line-Copy-17" fill="#E0513E" points="282.270169 382 498.277674 382 498.277674 378 282.270169 378"></polygon>
+ <path d="M277.201592,391.484016 L283.029685,397.309042 L285.857368,394.47987 L280.029274,388.654844 L277.201592,391.484016 Z M286.198357,400.476046 L292.02645,406.301071 L294.854133,403.4719 L289.026039,397.646874 L286.198357,400.476046 Z M295.195122,409.468075 L301.023216,415.293101 L303.850898,412.463929 L298.022805,406.638904 L295.195122,409.468075 Z M304.191888,418.460105 L310.019981,424.285131 L312.847663,421.455959 L307.01957,415.630933 L304.191888,418.460105 Z M313.188653,427.452135 L319.016746,433.27716 L321.844429,430.447989 L316.016335,424.622963 L313.188653,427.452135 Z M322.185418,436.444164 L328.013511,442.26919 L330.841194,439.440018 L325.0131,433.614993 L322.185418,436.444164 Z M331.182183,445.436194 L337.010277,451.26122 L339.837959,448.432048 L334.009866,442.607022 L331.182183,445.436194 Z M340.178949,454.428224 L346.007042,460.253249 L348.834724,457.424078 L343.006631,451.599052 L340.178949,454.428224 Z M349.175714,463.420253 L355.003807,469.245279 L357.83149,466.416107 L352.003396,460.591082 L349.175714,463.420253 Z M358.172479,472.412283 L364.000572,478.237309 L366.828255,475.408137 L361.000162,469.583111 L358.172479,472.412283 Z M367.169244,481.404313 L372.997338,487.229338 L375.82502,484.400167 L369.996927,478.575141 L367.169244,481.404313 Z M376.16601,490.396342 L381.994103,496.221368 L384.821785,493.392196 L378.993692,487.567171 L376.16601,490.396342 Z" id="Line-Copy-18" fill="#E0513E"></path>
+ <path d="M516.770072,391.484016 L522.598165,397.309042 L525.425848,394.47987 L519.597754,388.654844 L516.770072,391.484016 Z M525.766837,400.476046 L531.594931,406.301071 L534.422613,403.4719 L528.59452,397.646874 L525.766837,400.476046 Z M534.763603,409.468075 L540.591696,415.293101 L543.419378,412.463929 L537.591285,406.638904 L534.763603,409.468075 Z M543.760368,418.460105 L549.588461,424.285131 L552.416144,421.455959 L546.58805,415.630933 L543.760368,418.460105 Z M552.757133,427.452135 L558.585226,433.27716 L561.412909,430.447989 L555.584816,424.622963 L552.757133,427.452135 Z M561.753898,436.444164 L567.581992,442.26919 L570.409674,439.440018 L564.581581,433.614993 L561.753898,436.444164 Z M570.750664,445.436194 L576.578757,451.26122 L579.406439,448.432048 L573.578346,442.607022 L570.750664,445.436194 Z M579.747429,454.428224 L585.575522,460.253249 L588.403205,457.424078 L582.575111,451.599052 L579.747429,454.428224 Z M588.744194,463.420253 L594.572288,469.245279 L597.39997,466.416107 L591.571877,460.591082 L588.744194,463.420253 Z M597.740959,472.412283 L603.569053,478.237309 L606.396735,475.408137 L600.568642,469.583111 L597.740959,472.412283 Z M606.737725,481.404313 L612.565818,487.229338 L615.3935,484.400167 L609.565407,478.575141 L606.737725,481.404313 Z M615.73449,490.396342 L621.562583,496.221368 L624.390266,493.392196 L618.562172,487.567171 L615.73449,490.396342 Z" id="Line-Copy-22" fill="#E0513E"></path>
+ <path d="M756.338552,391.484016 L762.166646,397.309042 L764.994328,394.47987 L759.166235,388.654844 L756.338552,391.484016 Z M765.335318,400.476046 L771.163411,406.301071 L773.991093,403.4719 L768.163,397.646874 L765.335318,400.476046 Z M774.332083,409.468075 L780.160176,415.293101 L782.987859,412.463929 L777.159765,406.638904 L774.332083,409.468075 Z M783.328848,418.460105 L789.156941,424.285131 L791.984624,421.455959 L786.156531,415.630933 L783.328848,418.460105 Z M792.325613,427.452135 L798.153707,433.27716 L800.981389,430.447989 L795.153296,424.622963 L792.325613,427.452135 Z M801.322379,436.444164 L807.150472,442.26919 L809.978154,439.440018 L804.150061,433.614993 L801.322379,436.444164 Z M810.319144,445.436194 L816.147237,451.26122 L818.97492,448.432048 L813.146826,442.607022 L810.319144,445.436194 Z M819.315909,454.428224 L825.144003,460.253249 L827.971685,457.424078 L822.143592,451.599052 L819.315909,454.428224 Z M828.312674,463.420253 L834.140768,469.245279 L836.96845,466.416107 L831.140357,460.591082 L828.312674,463.420253 Z M837.30944,472.412283 L843.137533,478.237309 L845.965216,475.408137 L840.137122,469.583111 L837.30944,472.412283 Z M846.306205,481.404313 L852.134298,487.229338 L854.961981,484.400167 L849.133887,478.575141 L846.306205,481.404313 Z M855.30297,490.396342 L861.131064,496.221368 L863.958746,493.392196 L858.130653,487.567171 L855.30297,490.396342 Z" id="Line-Copy-23" fill="#E0513E"></path>
+ <path d="M880.010512,252.341438 L885.838606,246.516413 L883.010923,243.687241 L877.18283,249.512267 L880.010512,252.341438 Z M889.007278,243.349409 L894.835371,237.524383 L892.007689,234.695211 L886.179595,240.520237 L889.007278,243.349409 Z M898.004043,234.357379 L903.832136,228.532353 L901.004454,225.703182 L895.176361,231.528207 L898.004043,234.357379 Z M907.000808,225.365349 L912.828902,219.540324 L910.001219,216.711152 L904.173126,222.536178 L907.000808,225.365349 Z M915.997573,216.37332 L921.825667,210.548294 L918.997984,207.719122 L913.169891,213.544148 L915.997573,216.37332 Z M924.994339,207.38129 L930.822432,201.556264 L927.99475,198.727093 L922.166656,204.552118 L924.994339,207.38129 Z M933.991104,198.38926 L939.819197,192.564235 L936.991515,189.735063 L931.163422,195.560089 L933.991104,198.38926 Z M942.987869,189.397231 L948.815963,183.572205 L945.98828,180.743033 L940.160187,186.568059 L942.987869,189.397231 Z M951.984635,180.405201 L957.812728,174.580175 L954.985045,171.751004 L949.156952,177.576029 L951.984635,180.405201 Z M960.9814,171.413171 L966.809493,165.588146 L963.981811,162.758974 L958.153717,168.584 L960.9814,171.413171 Z M969.978165,162.421142 L975.806258,156.596116 L972.978576,153.766944 L967.150483,159.59197 L969.978165,162.421142 Z M978.97493,153.429112 L984.803024,147.604086 L981.975341,144.774915 L976.147248,150.59994 L978.97493,153.429112 Z" id="Line-Copy-27" fill="#E0513E"></path>
+ <path d="M1114.27955,23.0223048 C1119.09619,23.0223048 1123,19.1205524 1123,14.3085502 C1123,9.49654797 1119.09619,5.59479554 1114.27955,5.59479554 C1109.46291,5.59479554 1105.5591,9.49654797 1105.5591,14.3085502 C1105.5591,19.1205524 1109.46291,23.0223048 1114.27955,23.0223048 Z M1114.27955,27.0223048 C1107.25424,27.0223048 1101.5591,21.330163 1101.5591,14.3085502 C1101.5591,7.28693738 1107.25424,1.59479554 1114.27955,1.59479554 C1121.30486,1.59479554 1127,7.28693738 1127,14.3085502 C1127,21.330163 1121.30486,27.0223048 1114.27955,27.0223048 Z" id="Oval-Copy-24" fill="#E04733"></path>
+ <polygon id="Line" fill="#E04733" points="758.424239 374.304508 864.800215 266.404117 861.951738 263.595883 755.575761 371.496274"></polygon>
+ <polygon id="Line-Copy" fill="#E04733" points="1000.57718 131.539201 1109.53226 24.6788494 1106.73141 21.8231038 997.776337 128.683455"></polygon>
+ <path d="M510.058161,389.60223 C514.874804,389.60223 518.778612,385.700478 518.778612,380.888476 C518.778612,376.076474 514.874804,372.174721 510.058161,372.174721 C505.241518,372.174721 501.337711,376.076474 501.337711,380.888476 C501.337711,385.700478 505.241518,389.60223 510.058161,389.60223 Z M510.058161,393.60223 C503.032851,393.60223 497.337711,387.910089 497.337711,380.888476 C497.337711,373.866863 503.032851,368.174721 510.058161,368.174721 C517.083472,368.174721 522.778612,373.866863 522.778612,380.888476 C522.778612,387.910089 517.083472,393.60223 510.058161,393.60223 Z" id="Oval-Copy-15" fill="#E04733"></path>
+ <polygon id="Line-Copy-19" fill="#E0513E" points="521.838649 382 737.846154 382 737.846154 378 521.838649 378"></polygon>
+ <path d="M749.626642,389.60223 C754.443285,389.60223 758.347092,385.700478 758.347092,380.888476 C758.347092,376.076474 754.443285,372.174721 749.626642,372.174721 C744.809999,372.174721 740.906191,376.076474 740.906191,380.888476 C740.906191,385.700478 744.809999,389.60223 749.626642,389.60223 Z M749.626642,393.60223 C742.601331,393.60223 736.906191,387.910089 736.906191,380.888476 C736.906191,373.866863 742.601331,368.174721 749.626642,368.174721 C756.651952,368.174721 762.347092,373.866863 762.347092,380.888476 C762.347092,387.910089 756.651952,393.60223 749.626642,393.60223 Z" id="Oval-Copy-16" fill="#E04733"></path>
+ <path d="M270.489681,389.60223 C275.306324,389.60223 279.210131,385.700478 279.210131,380.888476 C279.210131,376.076474 275.306324,372.174721 270.489681,372.174721 C265.673038,372.174721 261.769231,376.076474 261.769231,380.888476 C261.769231,385.700478 265.673038,389.60223 270.489681,389.60223 Z M270.489681,393.60223 C263.46437,393.60223 257.769231,387.910089 257.769231,380.888476 C257.769231,373.866863 263.46437,368.174721 270.489681,368.174721 C277.514992,368.174721 283.210131,373.866863 283.210131,380.888476 C283.210131,387.910089 277.514992,393.60223 270.489681,393.60223 Z" id="Oval-Copy-14" fill="#E04733"></path>
+ <path d="M630.902439,510.3829 C635.719082,510.3829 639.622889,506.481147 639.622889,501.669145 C639.622889,496.857143 635.719082,492.95539 630.902439,492.95539 C626.085796,492.95539 622.181989,496.857143 622.181989,501.669145 C622.181989,506.481147 626.085796,510.3829 630.902439,510.3829 Z M630.902439,514.3829 C623.877128,514.3829 618.181989,508.690758 618.181989,501.669145 C618.181989,494.647532 623.877128,488.95539 630.902439,488.95539 C637.92775,488.95539 643.622889,494.647532 643.622889,501.669145 C643.622889,508.690758 637.92775,514.3829 630.902439,514.3829 Z" id="Oval-Copy-19" fill="#E04733"></path>
+ <path d="M870.470919,510.3829 C875.287562,510.3829 879.19137,506.481147 879.19137,501.669145 C879.19137,496.857143 875.287562,492.95539 870.470919,492.95539 C865.654276,492.95539 861.750469,496.857143 861.750469,501.669145 C861.750469,506.481147 865.654276,510.3829 870.470919,510.3829 Z M870.470919,514.3829 C863.445609,514.3829 857.750469,508.690758 857.750469,501.669145 C857.750469,494.647532 863.445609,488.95539 870.470919,488.95539 C877.49623,488.95539 883.19137,494.647532 883.19137,501.669145 C883.19137,508.690758 877.49623,514.3829 870.470919,514.3829 Z" id="Oval-Copy-18" fill="#E04733"></path>
+ <path d="M391.333959,510.3829 C396.150602,510.3829 400.054409,506.481147 400.054409,501.669145 C400.054409,496.857143 396.150602,492.95539 391.333959,492.95539 C386.517316,492.95539 382.613508,496.857143 382.613508,501.669145 C382.613508,506.481147 386.517316,510.3829 391.333959,510.3829 Z M391.333959,514.3829 C384.308648,514.3829 378.613508,508.690758 378.613508,501.669145 C378.613508,494.647532 384.308648,488.95539 391.333959,488.95539 C398.359269,488.95539 404.054409,494.647532 404.054409,501.669145 C404.054409,508.690758 398.359269,514.3829 391.333959,514.3829 Z" id="Oval-Copy-17" fill="#E04733"></path>
+ <path d="M28.833894,372.064086 L22.5374943,372.064086 L22.5374943,369.838086 L37.8014935,369.838086 L37.8014935,372.064086 L31.5050938,372.064086 L31.5050938,390.698885 L28.833894,390.698885 L28.833894,372.064086 Z M49.2494929,391.080485 C47.9350864,391.080485 46.7319985,390.826087 45.6401931,390.317285 C44.5483877,389.808482 43.6102971,389.08239 42.8258932,388.138985 C42.0414893,387.19558 41.4319955,386.050792 40.9973933,384.704585 C40.5627912,383.358378 40.3454934,381.847894 40.3454934,380.173085 C40.3454934,378.498277 40.5627912,376.998392 40.9973933,375.673386 C41.4319955,374.348379 42.0414893,373.22479 42.8258932,372.302586 C43.6102971,371.380381 44.5483877,370.675488 45.6401931,370.187886 C46.7319985,369.700283 47.9350864,369.456486 49.2494929,369.456486 C50.5638994,369.456486 51.7669873,369.705583 52.8587927,370.203786 C53.9505981,370.701988 54.8939886,371.412181 55.6889925,372.334386 C56.4839965,373.25659 57.0987903,374.380179 57.5333924,375.705186 C57.9679946,377.030192 58.1852924,378.519477 58.1852924,380.173085 C58.1852924,381.847894 57.9679946,383.358378 57.5333924,384.704585 C57.0987903,386.050792 56.4839965,387.19558 55.6889925,388.138985 C54.8939886,389.08239 53.9505981,389.808482 52.8587927,390.317285 C51.7669873,390.826087 50.5638994,391.080485 49.2494929,391.080485 Z M49.2494929,388.759085 C50.1822975,388.759085 51.024989,388.557687 51.7775927,388.154885 C52.5301965,387.752083 53.17679,387.174389 53.7173926,386.421785 C54.2579953,385.669181 54.6766911,384.76819 54.9734926,383.718785 C55.270294,382.66938 55.4186925,381.487492 55.4186925,380.173085 C55.4186925,378.879879 55.270294,377.713891 54.9734926,376.675086 C54.6766911,375.63628 54.2579953,374.756489 53.7173926,374.035686 C53.17679,373.314882 52.5301965,372.758388 51.7775927,372.366186 C51.024989,371.973984 50.1822975,371.777886 49.2494929,371.777886 C48.3166883,371.777886 47.4739967,371.973984 46.721393,372.366186 C45.9687893,372.758388 45.3221958,373.314882 44.7815931,374.035686 C44.2409904,374.756489 43.8222947,375.63628 43.5254932,376.675086 C43.2286917,377.713891 43.0802932,378.879879 43.0802932,380.173085 C43.0802932,381.487492 43.2286917,382.66938 43.5254932,383.718785 C43.8222947,384.76819 44.2409904,385.669181 44.7815931,386.421785 C45.3221958,387.174389 45.9687893,387.752083 46.721393,388.154885 C47.4739967,388.557687 48.3166883,388.759085 49.2494929,388.759085 Z M62.6690922,369.838086 L69.0608918,369.838086 C70.2056975,369.838086 71.2550869,369.944085 72.2090916,370.156086 C73.1630964,370.368087 73.9739882,370.712583 74.6417915,371.189586 C75.3095948,371.666588 75.8289896,372.297282 76.1999914,373.081686 C76.5709933,373.86609 76.7564914,374.83068 76.7564914,375.975486 C76.7564914,377.077891 76.5709933,378.031881 76.1999914,378.837485 C75.8289896,379.643089 75.3042949,380.310883 74.6258915,380.840885 C73.9474882,381.370888 73.1365963,381.768384 72.1931916,382.033385 C71.249787,382.298387 70.2056975,382.430885 69.0608918,382.430885 L65.308492,382.430885 L65.308492,390.698885 L62.6690922,390.698885 L62.6690922,369.838086 Z M68.7428918,380.268485 C70.5661008,380.268485 71.9175873,379.923989 72.7973916,379.234985 C73.677196,378.545982 74.1170915,377.459493 74.1170915,375.975486 C74.1170915,374.470278 73.671896,373.426189 72.7814916,372.843186 C71.8910872,372.260183 70.5449007,371.968686 68.7428918,371.968686 L65.308492,371.968686 L65.308492,380.268485 L68.7428918,380.268485 Z M80.9858912,369.838086 L83.625291,369.838086 L83.625291,390.698885 L80.9858912,390.698885 L80.9858912,369.838086 Z M88.1408908,380.268485 C88.1408908,378.593677 88.3740884,377.088492 88.8404907,375.752886 C89.306893,374.417279 89.9534865,373.28309 90.7802906,372.350286 C91.6070947,371.417481 92.5875849,370.701988 93.7217905,370.203786 C94.8559961,369.705583 96.0908837,369.456486 97.4264903,369.456486 C98.6984966,369.456486 99.8114854,369.716183 100.76549,370.235586 C101.719495,370.754988 102.503887,371.353882 103.11869,372.032286 L101.62409,373.717686 C101.072887,373.124083 100.463393,372.652387 99.7955901,372.302586 C99.1277868,371.952784 98.3486947,371.777886 97.4582903,371.777886 C96.4618853,371.777886 95.5608944,371.973984 94.7552904,372.366186 C93.9496864,372.758388 93.2606934,373.320182 92.6882905,374.051586 C92.1158877,374.782989 91.6706922,375.66808 91.3526906,376.706886 C91.034689,377.745691 90.8756906,378.911679 90.8756906,380.204885 C90.8756906,381.519292 91.0293891,382.70118 91.3367906,383.750585 C91.6441921,384.79999 92.0734878,385.695681 92.6246905,386.437685 C93.1758933,387.179689 93.8489865,387.752083 94.6439904,388.154885 C95.4389944,388.557687 96.3346854,388.759085 97.3310903,388.759085 C98.3486953,388.759085 99.2337864,388.557687 99.9863901,388.154885 C100.738994,387.752083 101.454487,387.179689 102.13289,386.437685 L103.62749,388.059485 C102.800686,389.01349 101.867895,389.755482 100.82909,390.285485 C99.790285,390.815487 98.6030969,391.080485 97.2674903,391.080485 C95.9530838,391.080485 94.739396,390.836687 93.6263905,390.349085 C92.513385,389.861482 91.5487947,389.156589 90.7325906,388.234385 C89.9163866,387.31218 89.280393,386.177992 88.8245907,384.831785 C88.3687885,383.485578 88.1408908,381.964494 88.1408908,380.268485 Z M113.930689,369.838086 L120.131689,369.838086 C121.191694,369.838086 122.161585,369.933485 123.041389,370.124286 C123.921193,370.315087 124.673786,370.611884 125.299189,371.014686 C125.924592,371.417488 126.412187,371.942183 126.761989,372.588786 C127.11179,373.235389 127.286689,374.014481 127.286689,374.926086 C127.286689,375.964891 126.989892,376.902981 126.396289,377.740385 C125.802686,378.57779 124.891095,379.155484 123.661489,379.473485 L123.661489,379.600685 C125.187896,379.833887 126.375084,380.363881 127.223089,381.190685 C128.071093,382.017489 128.495089,383.151678 128.495089,384.593285 C128.495089,385.61089 128.304291,386.501281 127.922689,387.264485 C127.541087,388.027689 127.000492,388.663682 126.300889,389.172485 C125.601285,389.681287 124.763894,390.062884 123.788689,390.317285 C122.813484,390.571686 121.742895,390.698885 120.576889,390.698885 L113.930689,390.698885 L113.930689,369.838086 Z M119.654689,378.742085 C121.456698,378.742085 122.744585,378.434688 123.518389,377.819885 C124.292193,377.205082 124.679089,376.314691 124.679089,375.148686 C124.679089,374.00388 124.270993,373.182388 123.454789,372.684186 C122.638585,372.185983 121.414297,371.936886 119.781889,371.936886 L116.570089,371.936886 L116.570089,378.742085 L119.654689,378.742085 Z M120.195289,388.600085 C121.997298,388.600085 123.396484,388.266188 124.392889,387.598385 C125.389294,386.930582 125.887489,385.886492 125.887489,384.466085 C125.887489,383.172879 125.399894,382.224188 124.424689,381.619985 C123.449484,381.015782 122.039698,380.713685 120.195289,380.713685 L116.570089,380.713685 L116.570089,388.600085 L120.195289,388.600085 Z M135.268488,379.727885 L138.766488,379.727885 C140.398896,379.727885 141.649684,379.393989 142.518888,378.726185 C143.388092,378.058382 143.822688,377.046092 143.822688,375.689286 C143.822688,374.311279 143.388092,373.346688 142.518888,372.795486 C141.649684,372.244283 140.398896,371.968686 138.766488,371.968686 L135.268488,371.968686 L135.268488,379.727885 Z M144.077088,390.698885 L139.052688,381.890285 L135.268488,381.890285 L135.268488,390.698885 L132.629088,390.698885 L132.629088,369.838086 L139.148088,369.838086 C140.208093,369.838086 141.188583,369.938785 142.089588,370.140186 C142.990592,370.341587 143.764385,370.675484 144.410988,371.141886 C145.057591,371.608288 145.561086,372.212482 145.921488,372.954486 C146.281889,373.696489 146.462088,374.60808 146.462088,375.689286 C146.462088,377.321694 146.038092,378.625481 145.190088,379.600685 C144.342084,380.57589 143.207895,381.243683 141.787488,381.604085 L147.066288,390.698885 L144.077088,390.698885 Z M159.977087,382.208285 L158.991287,379.028285 C158.609685,377.86228 158.249289,376.701591 157.910087,375.546186 C157.570885,374.39078 157.231689,373.208892 156.892487,372.000486 L156.765287,372.000486 C156.447286,373.208892 156.118689,374.39078 155.779487,375.546186 C155.440285,376.701591 155.079889,377.86228 154.698287,379.028285 L153.712487,382.208285 L159.977087,382.208285 Z M160.644887,384.338885 L153.044687,384.338885 L151.041287,390.698885 L148.338288,390.698885 L155.397887,369.838086 L158.387087,369.838086 L165.446687,390.698885 L162.616487,390.698885 L160.644887,384.338885 Z M168.404086,369.838086 L171.138886,369.838086 L178.675486,382.939685 L180.933286,387.264485 L181.060486,387.264485 C180.996885,386.20448 180.927986,385.107391 180.853786,383.973185 C180.779585,382.83898 180.742486,381.720691 180.742486,380.618285 L180.742486,369.838086 L183.254686,369.838086 L183.254686,390.698885 L180.519886,390.698885 L172.951486,377.565485 L170.693686,373.272486 L170.566486,373.272486 C170.651287,374.332491 170.730786,375.40838 170.804986,376.500186 C170.879187,377.591991 170.916286,378.68908 170.916286,379.791485 L170.916286,390.698885 L168.404086,390.698885 L168.404086,369.838086 Z M187.770285,380.268485 C187.770285,378.593677 188.003483,377.088492 188.469885,375.752886 C188.936288,374.417279 189.582881,373.28309 190.409685,372.350286 C191.236489,371.417481 192.216979,370.701988 193.351185,370.203786 C194.485391,369.705583 195.720278,369.456486 197.055885,369.456486 C198.327891,369.456486 199.44088,369.716183 200.394885,370.235586 C201.348889,370.754988 202.133282,371.353882 202.748085,372.032286 L201.253485,373.717686 C200.702282,373.124083 200.092788,372.652387 199.424985,372.302586 C198.757181,371.952784 197.978089,371.777886 197.087685,371.777886 C196.09128,371.777886 195.190289,371.973984 194.384685,372.366186 C193.579081,372.758388 192.890088,373.320182 192.317685,374.051586 C191.745282,374.782989 191.300087,375.66808 190.982085,376.706886 C190.664084,377.745691 190.505085,378.911679 190.505085,380.204885 C190.505085,381.519292 190.658784,382.70118 190.966185,383.750585 C191.273587,384.79999 191.702882,385.695681 192.254085,386.437685 C192.805288,387.179689 193.478381,387.752083 194.273385,388.154885 C195.068389,388.557687 195.96408,388.759085 196.960485,388.759085 C197.97809,388.759085 198.863181,388.557687 199.615785,388.154885 C200.368388,387.752083 201.083881,387.179689 201.762285,386.437685 L203.256885,388.059485 C202.43008,389.01349 201.49729,389.755482 200.458485,390.285485 C199.41968,390.815487 198.232492,391.080485 196.896885,391.080485 C195.582478,391.080485 194.368791,390.836687 193.255785,390.349085 C192.14278,389.861482 191.178189,389.156589 190.361985,388.234385 C189.545781,387.31218 188.909788,386.177992 188.453985,384.831785 C187.998183,383.485578 187.770285,381.964494 187.770285,380.268485 Z M207.136484,369.838086 L209.775884,369.838086 L209.775884,378.583085 L219.474884,378.583085 L219.474884,369.838086 L222.146084,369.838086 L222.146084,390.698885 L219.474884,390.698885 L219.474884,380.872685 L209.775884,380.872685 L209.775884,390.698885 L207.136484,390.698885 L207.136484,369.838086 Z" id="TOPIC-BRANCH" fill="#E04733"></path>
+ <path d="M687.15753,304.150354 L690.273929,304.150354 L694.312529,315.343953 L695.838929,319.605153 L695.966129,319.605153 L697.428929,315.343953 L701.435729,304.150354 L704.552129,304.150354 L704.552129,325.011152 L702.039929,325.011152 L702.039929,313.531353 C702.039929,312.598548 702.077028,311.570359 702.151229,310.446753 C702.225429,309.323148 702.304928,308.294958 702.389729,307.362153 L702.262529,307.362153 L700.577129,312.004953 L696.570329,322.912353 L695.075729,322.912353 L691.037129,312.004953 L689.383529,307.362153 L689.256329,307.362153 C689.31993,308.294958 689.394129,309.323148 689.478929,310.446753 C689.56373,311.570359 689.606129,312.598548 689.606129,313.531353 L689.606129,325.011152 L687.15753,325.011152 L687.15753,304.150354 Z M710.276128,304.150354 L722.296528,304.150354 L722.296528,306.376353 L712.915528,306.376353 L712.915528,312.927153 L720.833728,312.927153 L720.833728,315.184953 L712.915528,315.184953 L712.915528,322.753353 L722.614528,322.753353 L722.614528,325.011152 L710.276128,325.011152 L710.276128,304.150354 Z M729.674127,314.040153 L733.172127,314.040153 C734.804535,314.040153 736.055323,313.706256 736.924527,313.038453 C737.793731,312.37065 738.228327,311.35836 738.228327,310.001553 C738.228327,308.623546 737.793731,307.658956 736.924527,307.107753 C736.055323,306.556551 734.804535,306.280953 733.172127,306.280953 L729.674127,306.280953 L729.674127,314.040153 Z M738.482727,325.011152 L733.458327,316.202553 L729.674127,316.202553 L729.674127,325.011152 L727.034727,325.011152 L727.034727,304.150354 L733.553727,304.150354 C734.613732,304.150354 735.594222,304.251053 736.495227,304.452454 C737.396231,304.653855 738.170024,304.987751 738.816627,305.454153 C739.46323,305.920556 739.966725,306.52475 740.327127,307.266753 C740.687528,308.008757 740.867727,308.920348 740.867727,310.001553 C740.867727,311.633961 740.443731,312.937748 739.595727,313.912953 C738.747723,314.888158 737.613534,315.555951 736.193127,315.916353 L741.471927,325.011152 L738.482727,325.011152 Z M744.302126,314.580753 C744.302126,312.905945 744.540624,311.40076 745.017626,310.065153 C745.494629,308.729547 746.162422,307.595358 747.021026,306.662553 C747.879631,305.729749 748.89192,305.014256 750.057926,304.516054 C751.223932,304.017851 752.506519,303.768754 753.905726,303.768754 C755.347333,303.768754 756.550421,304.039051 757.515026,304.579654 C758.479631,305.120256 759.269323,305.70855 759.884126,306.344553 L758.389526,308.029953 C757.859523,307.478751 757.250029,307.017655 756.561026,306.646653 C755.872022,306.275652 754.997531,306.090153 753.937526,306.090153 C752.877521,306.090153 751.92353,306.286251 751.075526,306.678453 C750.227522,307.070655 749.506729,307.63245 748.913126,308.363853 C748.319523,309.095257 747.858428,309.980348 747.529826,311.019153 C747.201225,312.057958 747.036926,313.223947 747.036926,314.517153 C747.036926,315.831559 747.190625,317.013448 747.498026,318.062853 C747.805428,319.112258 748.250623,320.007949 748.833626,320.749953 C749.416629,321.491956 750.137422,322.064351 750.996026,322.467153 C751.85463,322.869955 752.84572,323.071353 753.969326,323.071353 C754.71133,323.071353 755.416223,322.960054 756.084026,322.737453 C756.751829,322.514851 757.297724,322.212754 757.721726,321.831153 L757.721726,316.393353 L753.301526,316.393353 L753.301526,314.199153 L760.138526,314.199153 L760.138526,322.975953 C759.460122,323.675556 758.564431,324.25325 757.451426,324.709052 C756.33842,325.164855 755.082333,325.392752 753.683126,325.392752 C752.305119,325.392752 751.043732,325.148955 749.898926,324.661352 C748.754121,324.17375 747.76833,323.468857 746.941526,322.546653 C746.114722,321.624448 745.468129,320.490259 745.001726,319.144053 C744.535324,317.797846 744.302126,316.276761 744.302126,314.580753 Z M765.131125,304.150354 L777.151525,304.150354 L777.151525,306.376353 L767.770525,306.376353 L767.770525,312.927153 L775.688725,312.927153 L775.688725,315.184953 L767.770525,315.184953 L767.770525,322.753353 L777.469525,322.753353 L777.469525,325.011152 L765.131125,325.011152 L765.131125,304.150354 Z" id="MERGE" fill="#E04733"></path>
+ <path d="M189.429182,500.508554 L192.927182,500.508554 C194.55959,500.508554 195.810377,500.174658 196.679581,499.506855 C197.548786,498.839051 197.983381,497.826761 197.983381,496.469955 C197.983381,495.091948 197.548786,494.127358 196.679581,493.576155 C195.810377,493.024952 194.55959,492.749355 192.927182,492.749355 L189.429182,492.749355 L189.429182,500.508554 Z M198.237781,511.479554 L193.213382,502.670954 L189.429182,502.670954 L189.429182,511.479554 L186.789782,511.479554 L186.789782,490.618755 L193.308782,490.618755 C194.368787,490.618755 195.349277,490.719454 196.250281,490.920855 C197.151286,491.122256 197.925078,491.456153 198.571681,491.922555 C199.218284,492.388957 199.721779,492.993151 200.082181,493.735155 C200.442583,494.477159 200.622781,495.388749 200.622781,496.469955 C200.622781,498.102363 200.198785,499.40615 199.350781,500.381355 C198.502777,501.356559 197.368588,502.024353 195.948181,502.384754 L201.226981,511.479554 L198.237781,511.479554 Z M205.265581,490.618755 L217.28598,490.618755 L217.28598,492.844755 L207.904981,492.844755 L207.904981,499.395555 L215.82318,499.395555 L215.82318,501.653354 L207.904981,501.653354 L207.904981,509.221754 L217.60398,509.221754 L217.60398,511.479554 L205.265581,511.479554 L205.265581,490.618755 Z M219.16218,490.618755 L221.99238,490.618755 L225.33138,501.875954 C225.712982,503.126761 226.046878,504.282149 226.33308,505.342154 C226.619281,506.402159 226.963778,507.546948 227.36658,508.776554 L227.49378,508.776554 C227.875382,507.546948 228.214578,506.402159 228.51138,505.342154 C228.808181,504.282149 229.136778,503.126761 229.49718,501.875954 L232.836179,490.618755 L235.539179,490.618755 L228.92478,511.479554 L225.84018,511.479554 L219.16218,490.618755 Z M238.401179,490.618755 L241.040579,490.618755 L241.040579,511.479554 L238.401179,511.479554 L238.401179,490.618755 Z M246.764579,490.618755 L258.784978,490.618755 L258.784978,492.844755 L249.403978,492.844755 L249.403978,499.395555 L257.322178,499.395555 L257.322178,501.653354 L249.403978,501.653354 L249.403978,509.221754 L259.102978,509.221754 L259.102978,511.479554 L246.764579,511.479554 L246.764579,490.618755 Z M261.392578,490.618755 L264.127378,490.618755 L266.321578,501.971354 C266.512379,503.11616 266.713776,504.239749 266.925778,505.342154 C267.137779,506.44456 267.339177,507.568148 267.529977,508.712954 L267.657177,508.712954 C267.890379,507.568148 268.134176,506.43926 268.388577,505.326254 C268.642979,504.213249 268.886776,503.09496 269.119977,501.971354 L272.013777,490.618755 L274.430577,490.618755 L277.324377,501.971354 C277.578778,503.07376 277.833176,504.186749 278.087577,505.310354 C278.341978,506.43396 278.596376,507.568148 278.850777,508.712954 L278.977977,508.712954 C279.168778,507.568148 279.359576,506.43926 279.550377,505.326254 C279.741178,504.213249 279.942576,503.09496 280.154577,501.971354 L282.348777,490.618755 L284.892777,490.618755 L280.567977,511.479554 L277.387977,511.479554 L274.239777,498.918555 C274.048976,498.112951 273.874078,497.323259 273.715077,496.549455 C273.556076,495.775651 273.391778,494.985959 273.222177,494.180355 L273.094977,494.180355 C272.925376,494.985959 272.750478,495.775651 272.570277,496.549455 C272.390076,497.323259 272.215178,498.112951 272.045577,498.918555 L268.960977,511.479554 L265.812778,511.479554 L261.392578,490.618755 Z M303.813776,502.988954 L302.827976,499.808955 C302.446374,498.642949 302.085977,497.48226 301.746776,496.326855 C301.407574,495.171449 301.068377,493.989561 300.729176,492.781155 L300.601976,492.781155 C300.283974,493.989561 299.955377,495.171449 299.616176,496.326855 C299.276974,497.48226 298.916578,498.642949 298.534976,499.808955 L297.549176,502.988954 L303.813776,502.988954 Z M304.481576,505.119554 L296.881376,505.119554 L294.877976,511.479554 L292.174976,511.479554 L299.234576,490.618755 L302.223776,490.618755 L309.283375,511.479554 L306.453175,511.479554 L304.481576,505.119554 Z M312.240775,490.618755 L318.632575,490.618755 C319.77738,490.618755 320.82677,490.724754 321.780775,490.936755 C322.734779,491.148756 323.545671,491.493253 324.213474,491.970255 C324.881278,492.447257 325.400673,493.077951 325.771674,493.862355 C326.142676,494.646759 326.328174,495.611349 326.328174,496.756155 C326.328174,497.85856 326.142676,498.812551 325.771674,499.618155 C325.400673,500.423759 324.875978,501.091552 324.197574,501.621554 C323.519171,502.151557 322.708279,502.549053 321.764875,502.814054 C320.82147,503.079056 319.77738,503.211554 318.632575,503.211554 L314.880175,503.211554 L314.880175,511.479554 L312.240775,511.479554 L312.240775,490.618755 Z M318.314575,501.049154 C320.137784,501.049154 321.48927,500.704658 322.369075,500.015655 C323.248879,499.326651 323.688774,498.240162 323.688774,496.756155 C323.688774,495.250947 323.243579,494.206858 322.353175,493.623855 C321.46277,493.040852 320.116584,492.749355 318.314575,492.749355 L314.880175,492.749355 L314.880175,501.049154 L318.314575,501.049154 Z M330.557574,490.618755 L336.949374,490.618755 C338.094179,490.618755 339.143569,490.724754 340.097574,490.936755 C341.051578,491.148756 341.86247,491.493253 342.530273,491.970255 C343.198077,492.447257 343.717472,493.077951 344.088473,493.862355 C344.459475,494.646759 344.644973,495.611349 344.644973,496.756155 C344.644973,497.85856 344.459475,498.812551 344.088473,499.618155 C343.717472,500.423759 343.192777,501.091552 342.514373,501.621554 C341.83597,502.151557 341.025078,502.549053 340.081674,502.814054 C339.138269,503.079056 338.094179,503.211554 336.949374,503.211554 L333.196974,503.211554 L333.196974,511.479554 L330.557574,511.479554 L330.557574,490.618755 Z M336.631374,501.049154 C338.454583,501.049154 339.806069,500.704658 340.685874,500.015655 C341.565678,499.326651 342.005573,498.240162 342.005573,496.756155 C342.005573,495.250947 341.560378,494.206858 340.669974,493.623855 C339.779569,493.040852 338.433383,492.749355 336.631374,492.749355 L333.196974,492.749355 L333.196974,501.049154 L336.631374,501.049154 Z" id="REVIEW-APP" fill="#E04733"></path>
+ <path d="M281.839315,443.148434 C281.839315,441.89441 281.395681,440.823773 280.5084,439.936492 C279.621119,439.049211 278.550482,438.605577 277.296458,438.605577 C276.042434,438.605577 274.971797,439.049211 274.084516,439.936492 C273.197235,440.823773 272.753601,441.89441 272.753601,443.148434 C272.753601,444.402458 273.197235,445.473095 274.084516,446.360376 C274.971797,447.247657 276.042434,447.691291 277.296458,447.691291 C278.550482,447.691291 279.621119,447.247657 280.5084,446.360376 C281.395681,445.473095 281.839315,444.402458 281.839315,443.148434 Z M295.467886,452.234148 C295.467886,451.618966 295.243111,451.086605 294.793555,450.63705 C294.343999,450.187494 293.811639,449.962719 293.196457,449.962719 C292.581275,449.962719 292.048915,450.187494 291.599359,450.63705 C291.149803,451.086605 290.925029,451.618966 290.925029,452.234148 C290.925029,452.86116 291.146846,453.396478 291.590486,453.840119 C292.034127,454.283759 292.569445,454.505576 293.196457,454.505576 C293.823469,454.505576 294.358787,454.283759 294.802428,453.840119 C295.246069,453.396478 295.467886,452.86116 295.467886,452.234148 Z M295.467886,434.06272 C295.467886,433.447539 295.243111,432.915178 294.793555,432.465622 C294.343999,432.016066 293.811639,431.791292 293.196457,431.791292 C292.581275,431.791292 292.048915,432.016066 291.599359,432.465622 C291.149803,432.915178 290.925029,433.447539 290.925029,434.06272 C290.925029,434.689732 291.146846,435.22505 291.590486,435.668691 C292.034127,436.112332 292.569445,436.334149 293.196457,436.334149 C293.823469,436.334149 294.358787,436.112332 294.802428,435.668691 C295.246069,435.22505 295.467886,434.689732 295.467886,434.06272 Z M288.6536,441.53359 L288.6536,444.816514 C288.6536,444.934818 288.612194,445.050163 288.529381,445.162552 C288.446569,445.274941 288.351927,445.33705 288.245453,445.34888 L285.494895,445.774773 C285.36476,446.188838 285.175477,446.638387 284.927038,447.123434 C285.329272,447.691294 285.861633,448.371532 286.524136,449.16417 C286.606949,449.282474 286.648355,449.400777 286.648355,449.519081 C286.648355,449.661046 286.606949,449.773433 286.524136,449.856246 C286.252036,450.211159 285.764039,450.740562 285.060129,451.444471 C284.35622,452.148381 283.891883,452.500331 283.667105,452.500331 C283.53697,452.500331 283.412753,452.458925 283.294449,452.376112 L281.253712,450.779014 C280.815987,451.003792 280.360523,451.18716 279.887306,451.329125 C279.757171,452.60681 279.621124,453.523654 279.479159,454.079683 C279.396346,454.363613 279.218892,454.505576 278.946793,454.505576 L275.646123,454.505576 C275.515989,454.505576 275.397686,454.461213 275.291212,454.372485 C275.184739,454.283757 275.125588,454.180242 275.113757,454.061938 L274.70561,451.346871 C274.303376,451.228567 273.859742,451.045198 273.374695,450.796759 L271.280722,452.376112 C271.197909,452.458925 271.079606,452.500331 270.925811,452.500331 C270.795676,452.500331 270.671459,452.45301 270.553155,452.358367 C268.849575,450.784921 267.997798,449.838502 267.997798,449.519081 C267.997798,449.412607 268.039204,449.30022 268.122016,449.181916 C268.240321,449.01629 268.48284,448.702789 268.849583,448.241402 C269.216326,447.780016 269.494337,447.419194 269.683624,447.158925 C269.411524,446.638387 269.204495,446.153347 269.06253,445.703791 L266.365209,445.277898 C266.246904,445.266068 266.146347,445.209874 266.063534,445.109316 C265.980722,445.008757 265.939316,444.893412 265.939316,444.763278 L265.939316,441.480354 C265.939316,441.36205 265.980722,441.246705 266.063534,441.134316 C266.146347,441.021927 266.240989,440.959818 266.347463,440.947988 L269.098021,440.522095 C269.228155,440.10803 269.417439,439.658481 269.665878,439.173434 C269.263644,438.605574 268.731283,437.925336 268.06878,437.132698 C267.985967,437.002563 267.944561,436.884261 267.944561,436.777787 C267.944561,436.635822 267.985967,436.51752 268.06878,436.422876 C268.329049,436.067964 268.814089,435.541518 269.523914,434.843524 C270.233739,434.145529 270.701033,433.796537 270.925811,433.796537 C271.055946,433.796537 271.180163,433.837943 271.298467,433.920756 L273.339204,435.517854 C273.741438,435.304907 274.196902,435.115623 274.70561,434.949997 C274.835744,433.672312 274.971792,432.761384 275.113757,432.217185 C275.19657,431.933255 275.374024,431.791292 275.646123,431.791292 L278.946793,431.791292 C279.076927,431.791292 279.19523,431.835655 279.301703,431.924383 C279.408177,432.013111 279.467328,432.116626 279.479159,432.23493 L279.887306,434.949997 C280.28954,435.068301 280.733174,435.25167 281.218221,435.500108 L283.312194,433.920756 C283.406838,433.837943 283.52514,433.796537 283.667105,433.796537 C283.797239,433.796537 283.921457,433.843858 284.039761,433.938501 C285.743341,435.511947 286.595118,436.458366 286.595118,436.777787 C286.595118,436.884261 286.553712,436.996648 286.470899,437.114952 C286.328934,437.304239 286.080499,437.623655 285.725587,438.073211 C285.370674,438.522767 285.104494,438.877674 284.927038,439.137943 C285.199137,439.705803 285.400251,440.190843 285.530386,440.593077 L288.227707,441.001224 C288.346011,441.024885 288.446569,441.086994 288.529381,441.187552 C288.612194,441.288111 288.6536,441.403456 288.6536,441.53359 Z M300.010742,450.99196 L300.010742,453.476335 C300.010742,453.665622 299.12939,453.848991 297.366658,454.026447 C297.224693,454.345868 297.047239,454.653454 296.834292,454.949215 C297.437643,456.286052 297.739314,457.102338 297.739314,457.398098 C297.739314,457.44542 297.715653,457.486826 297.668332,457.522317 C296.225021,458.362277 295.491546,458.78225 295.467886,458.78225 C295.373242,458.78225 295.101147,458.504239 294.651591,457.94821 C294.202035,457.39218 293.894449,456.989952 293.728823,456.741514 C293.492215,456.765174 293.314761,456.777005 293.196457,456.777005 C293.078153,456.777005 292.900699,456.765174 292.664091,456.741514 C292.498465,456.989952 292.190879,457.39218 291.741323,457.94821 C291.291767,458.504239 291.019672,458.78225 290.925029,458.78225 C290.901368,458.78225 290.167893,458.362277 288.724582,457.522317 C288.677261,457.486826 288.6536,457.44542 288.6536,457.398098 C288.6536,457.102338 288.955271,456.286052 289.558622,454.949215 C289.345675,454.653454 289.168221,454.345868 289.026256,454.026447 C287.263524,453.848991 286.382172,453.665622 286.382172,453.476335 L286.382172,450.99196 C286.382172,450.802674 287.263524,450.619305 289.026256,450.441849 C289.180052,450.098767 289.357505,449.791181 289.558622,449.519081 C288.955271,448.182244 288.6536,447.365958 288.6536,447.070197 C288.6536,447.022875 288.677261,446.98147 288.724582,446.945978 C288.771904,446.922318 288.978933,446.804015 289.345676,446.591068 C289.712419,446.37812 290.061411,446.177006 290.392663,445.98772 C290.723914,445.798433 290.901368,445.703791 290.925029,445.703791 C291.019672,445.703791 291.291767,445.978844 291.741323,446.528958 C292.190879,447.079073 292.498465,447.478343 292.664091,447.726782 C292.900699,447.703121 293.078153,447.691291 293.196457,447.691291 C293.314761,447.691291 293.492215,447.703121 293.728823,447.726782 C294.332174,446.886822 294.876365,446.224329 295.361412,445.739282 L295.467886,445.703791 C295.515207,445.703791 296.248682,446.117849 297.668332,446.945978 C297.715653,446.98147 297.739314,447.022875 297.739314,447.070197 C297.739314,447.365958 297.437643,448.182244 296.834292,449.519081 C297.035409,449.791181 297.212862,450.098767 297.366658,450.441849 C299.12939,450.619305 300.010742,450.802674 300.010742,450.99196 Z M300.010742,432.820533 L300.010742,435.304908 C300.010742,435.494194 299.12939,435.677563 297.366658,435.855019 C297.224693,436.17444 297.047239,436.482027 296.834292,436.777787 C297.437643,438.114624 297.739314,438.93091 297.739314,439.226671 C297.739314,439.273992 297.715653,439.315398 297.668332,439.35089 C296.225021,440.190849 295.491546,440.610822 295.467886,440.610822 C295.373242,440.610822 295.101147,440.332812 294.651591,439.776782 C294.202035,439.220753 293.894449,438.818525 293.728823,438.570086 C293.492215,438.593747 293.314761,438.605577 293.196457,438.605577 C293.078153,438.605577 292.900699,438.593747 292.664091,438.570086 C292.498465,438.818525 292.190879,439.220753 291.741323,439.776782 C291.291767,440.332812 291.019672,440.610822 290.925029,440.610822 C290.901368,440.610822 290.167893,440.190849 288.724582,439.35089 C288.677261,439.315398 288.6536,439.273992 288.6536,439.226671 C288.6536,438.93091 288.955271,438.114624 289.558622,436.777787 C289.345675,436.482027 289.168221,436.17444 289.026256,435.855019 C287.263524,435.677563 286.382172,435.494194 286.382172,435.304908 L286.382172,432.820533 C286.382172,432.631246 287.263524,432.447877 289.026256,432.270421 C289.180052,431.927339 289.357505,431.619753 289.558622,431.347653 C288.955271,430.010816 288.6536,429.19453 288.6536,428.89877 C288.6536,428.851448 288.677261,428.810042 288.724582,428.774551 C288.771904,428.75089 288.978933,428.632588 289.345676,428.41964 C289.712419,428.206693 290.061411,428.005579 290.392663,427.816292 C290.723914,427.627005 290.901368,427.532363 290.925029,427.532363 C291.019672,427.532363 291.291767,427.807416 291.741323,428.357531 C292.190879,428.907645 292.498465,429.306916 292.664091,429.555354 C292.900699,429.531694 293.078153,429.519863 293.196457,429.519863 C293.314761,429.519863 293.492215,429.531694 293.728823,429.555354 C294.332174,428.715395 294.876365,428.052901 295.361412,427.567854 L295.467886,427.532363 C295.515207,427.532363 296.248682,427.946422 297.668332,428.774551 C297.715653,428.810042 297.739314,428.851448 297.739314,428.89877 C297.739314,429.19453 297.437643,430.010816 296.834292,431.347653 C297.035409,431.619753 297.212862,431.927339 297.366658,432.270421 C299.12939,432.447877 300.010742,432.631246 300.010742,432.820533 Z" id="" fill="#E04733"></path>
+ <path d="M518.847717,443.148434 C518.847717,441.89441 518.404083,440.823773 517.516802,439.936492 C516.629521,439.049211 515.558884,438.605577 514.30486,438.605577 C513.050836,438.605577 511.9802,439.049211 511.092919,439.936492 C510.205637,440.823773 509.762003,441.89441 509.762003,443.148434 C509.762003,444.402458 510.205637,445.473095 511.092919,446.360376 C511.9802,447.247657 513.050836,447.691291 514.30486,447.691291 C515.558884,447.691291 516.629521,447.247657 517.516802,446.360376 C518.404083,445.473095 518.847717,444.402458 518.847717,443.148434 Z M532.476288,452.234148 C532.476288,451.618966 532.251513,451.086605 531.801958,450.63705 C531.352402,450.187494 530.820041,449.962719 530.20486,449.962719 C529.589678,449.962719 529.057317,450.187494 528.607761,450.63705 C528.158206,451.086605 527.933431,451.618966 527.933431,452.234148 C527.933431,452.86116 528.155248,453.396478 528.598889,453.840119 C529.042529,454.283759 529.577847,454.505576 530.20486,454.505576 C530.831872,454.505576 531.36719,454.283759 531.81083,453.840119 C532.254471,453.396478 532.476288,452.86116 532.476288,452.234148 Z M532.476288,434.06272 C532.476288,433.447539 532.251513,432.915178 531.801958,432.465622 C531.352402,432.016066 530.820041,431.791292 530.20486,431.791292 C529.589678,431.791292 529.057317,432.016066 528.607761,432.465622 C528.158206,432.915178 527.933431,433.447539 527.933431,434.06272 C527.933431,434.689732 528.155248,435.22505 528.598889,435.668691 C529.042529,436.112332 529.577847,436.334149 530.20486,436.334149 C530.831872,436.334149 531.36719,436.112332 531.81083,435.668691 C532.254471,435.22505 532.476288,434.689732 532.476288,434.06272 Z M525.662003,441.53359 L525.662003,444.816514 C525.662003,444.934818 525.620597,445.050163 525.537784,445.162552 C525.454971,445.274941 525.360329,445.33705 525.253855,445.34888 L522.503297,445.774773 C522.373163,446.188838 522.183879,446.638387 521.93544,447.123434 C522.337674,447.691294 522.870035,448.371532 523.532538,449.16417 C523.615351,449.282474 523.656757,449.400777 523.656757,449.519081 C523.656757,449.661046 523.615351,449.773433 523.532538,449.856246 C523.260439,450.211159 522.772442,450.740562 522.068532,451.444471 C521.364622,452.148381 520.900285,452.500331 520.675507,452.500331 C520.545373,452.500331 520.421155,452.458925 520.302851,452.376112 L518.262115,450.779014 C517.824389,451.003792 517.368925,451.18716 516.895708,451.329125 C516.765574,452.60681 516.629526,453.523654 516.487561,454.079683 C516.404748,454.363613 516.227295,454.505576 515.955195,454.505576 L512.654526,454.505576 C512.524391,454.505576 512.406089,454.461213 512.299615,454.372485 C512.193141,454.283757 512.13399,454.180242 512.12216,454.061938 L511.714012,451.346871 C511.311778,451.228567 510.868144,451.045198 510.383097,450.796759 L508.289124,452.376112 C508.206311,452.458925 508.088009,452.500331 507.934213,452.500331 C507.804079,452.500331 507.679861,452.45301 507.561557,452.358367 C505.857977,450.784921 505.0062,449.838502 505.0062,449.519081 C505.0062,449.412607 505.047606,449.30022 505.130419,449.181916 C505.248723,449.01629 505.491243,448.702789 505.857986,448.241402 C506.224729,447.780016 506.502739,447.419194 506.692026,447.158925 C506.419926,446.638387 506.212897,446.153347 506.070932,445.703791 L503.373611,445.277898 C503.255307,445.266068 503.15475,445.209874 503.071937,445.109316 C502.989124,445.008757 502.947718,444.893412 502.947718,444.763278 L502.947718,441.480354 C502.947718,441.36205 502.989124,441.246705 503.071937,441.134316 C503.15475,441.021927 503.249392,440.959818 503.355865,440.947988 L506.106423,440.522095 C506.236558,440.10803 506.425842,439.658481 506.67428,439.173434 C506.272046,438.605574 505.739686,437.925336 505.077182,437.132698 C504.994369,437.002563 504.952964,436.884261 504.952964,436.777787 C504.952964,436.635822 504.994369,436.51752 505.077182,436.422876 C505.337451,436.067964 505.822491,435.541518 506.532316,434.843524 C507.242141,434.145529 507.709436,433.796537 507.934213,433.796537 C508.064348,433.796537 508.188565,433.837943 508.30687,433.920756 L510.347606,435.517854 C510.74984,435.304907 511.205304,435.115623 511.714012,434.949997 C511.844147,433.672312 511.980195,432.761384 512.12216,432.217185 C512.204973,431.933255 512.382426,431.791292 512.654526,431.791292 L515.955195,431.791292 C516.08533,431.791292 516.203632,431.835655 516.310106,431.924383 C516.41658,432.013111 516.475731,432.116626 516.487561,432.23493 L516.895708,434.949997 C517.297943,435.068301 517.741577,435.25167 518.226624,435.500108 L520.320597,433.920756 C520.41524,433.837943 520.533542,433.796537 520.675507,433.796537 C520.805642,433.796537 520.929859,433.843858 521.048164,433.938501 C522.751743,435.511947 523.603521,436.458366 523.603521,436.777787 C523.603521,436.884261 523.562115,436.996648 523.479302,437.114952 C523.337337,437.304239 523.088902,437.623655 522.733989,438.073211 C522.379077,438.522767 522.112897,438.877674 521.93544,439.137943 C522.20754,439.705803 522.408654,440.190843 522.538789,440.593077 L525.23611,441.001224 C525.354414,441.024885 525.454971,441.086994 525.537784,441.187552 C525.620597,441.288111 525.662003,441.403456 525.662003,441.53359 Z M537.019145,450.99196 L537.019145,453.476335 C537.019145,453.665622 536.137792,453.848991 534.37506,454.026447 C534.233095,454.345868 534.055642,454.653454 533.842694,454.949215 C534.446045,456.286052 534.747716,457.102338 534.747716,457.398098 C534.747716,457.44542 534.724056,457.486826 534.676734,457.522317 C533.233424,458.362277 532.499949,458.78225 532.476288,458.78225 C532.381645,458.78225 532.109549,458.504239 531.659993,457.94821 C531.210438,457.39218 530.902851,456.989952 530.737226,456.741514 C530.500617,456.765174 530.323164,456.777005 530.20486,456.777005 C530.086555,456.777005 529.909102,456.765174 529.672493,456.741514 C529.506868,456.989952 529.199281,457.39218 528.749726,457.94821 C528.30017,458.504239 528.028074,458.78225 527.933431,458.78225 C527.90977,458.78225 527.176295,458.362277 525.732985,457.522317 C525.685663,457.486826 525.662003,457.44542 525.662003,457.398098 C525.662003,457.102338 525.963674,456.286052 526.567025,454.949215 C526.354077,454.653454 526.176624,454.345868 526.034659,454.026447 C524.271927,453.848991 523.390574,453.665622 523.390574,453.476335 L523.390574,450.99196 C523.390574,450.802674 524.271927,450.619305 526.034659,450.441849 C526.188454,450.098767 526.365908,449.791181 526.567025,449.519081 C525.963674,448.182244 525.662003,447.365958 525.662003,447.070197 C525.662003,447.022875 525.685663,446.98147 525.732985,446.945978 C525.780306,446.922318 525.987336,446.804015 526.354078,446.591068 C526.720821,446.37812 527.069813,446.177006 527.401065,445.98772 C527.732317,445.798433 527.90977,445.703791 527.933431,445.703791 C528.028074,445.703791 528.30017,445.978844 528.749726,446.528958 C529.199281,447.079073 529.506868,447.478343 529.672493,447.726782 C529.909102,447.703121 530.086555,447.691291 530.20486,447.691291 C530.323164,447.691291 530.500617,447.703121 530.737226,447.726782 C531.340577,446.886822 531.884768,446.224329 532.369815,445.739282 L532.476288,445.703791 C532.52361,445.703791 533.257084,446.117849 534.676734,446.945978 C534.724056,446.98147 534.747716,447.022875 534.747716,447.070197 C534.747716,447.365958 534.446045,448.182244 533.842694,449.519081 C534.043811,449.791181 534.221265,450.098767 534.37506,450.441849 C536.137792,450.619305 537.019145,450.802674 537.019145,450.99196 Z M537.019145,432.820533 L537.019145,435.304908 C537.019145,435.494194 536.137792,435.677563 534.37506,435.855019 C534.233095,436.17444 534.055642,436.482027 533.842694,436.777787 C534.446045,438.114624 534.747716,438.93091 534.747716,439.226671 C534.747716,439.273992 534.724056,439.315398 534.676734,439.35089 C533.233424,440.190849 532.499949,440.610822 532.476288,440.610822 C532.381645,440.610822 532.109549,440.332812 531.659993,439.776782 C531.210438,439.220753 530.902851,438.818525 530.737226,438.570086 C530.500617,438.593747 530.323164,438.605577 530.20486,438.605577 C530.086555,438.605577 529.909102,438.593747 529.672493,438.570086 C529.506868,438.818525 529.199281,439.220753 528.749726,439.776782 C528.30017,440.332812 528.028074,440.610822 527.933431,440.610822 C527.90977,440.610822 527.176295,440.190849 525.732985,439.35089 C525.685663,439.315398 525.662003,439.273992 525.662003,439.226671 C525.662003,438.93091 525.963674,438.114624 526.567025,436.777787 C526.354077,436.482027 526.176624,436.17444 526.034659,435.855019 C524.271927,435.677563 523.390574,435.494194 523.390574,435.304908 L523.390574,432.820533 C523.390574,432.631246 524.271927,432.447877 526.034659,432.270421 C526.188454,431.927339 526.365908,431.619753 526.567025,431.347653 C525.963674,430.010816 525.662003,429.19453 525.662003,428.89877 C525.662003,428.851448 525.685663,428.810042 525.732985,428.774551 C525.780306,428.75089 525.987336,428.632588 526.354078,428.41964 C526.720821,428.206693 527.069813,428.005579 527.401065,427.816292 C527.732317,427.627005 527.90977,427.532363 527.933431,427.532363 C528.028074,427.532363 528.30017,427.807416 528.749726,428.357531 C529.199281,428.907645 529.506868,429.306916 529.672493,429.555354 C529.909102,429.531694 530.086555,429.519863 530.20486,429.519863 C530.323164,429.519863 530.500617,429.531694 530.737226,429.555354 C531.340577,428.715395 531.884768,428.052901 532.369815,427.567854 L532.476288,427.532363 C532.52361,427.532363 533.257084,427.946422 534.676734,428.774551 C534.724056,428.810042 534.747716,428.851448 534.747716,428.89877 C534.747716,429.19453 534.446045,430.010816 533.842694,431.347653 C534.043811,431.619753 534.221265,431.927339 534.37506,432.270421 C536.137792,432.447877 537.019145,432.631246 537.019145,432.820533 Z" id="-copy" fill="#E04733"></path>
+ <path d="M859.734507,544.858471 C857.81798,544.917623 856.250474,545.674758 855.031941,547.1299 L852.654039,547.1299 C851.683945,547.1299 850.867659,546.890337 850.205155,546.411205 C849.542652,545.932074 849.211405,545.231132 849.211405,544.30836 C849.211405,540.132223 849.94488,538.044186 851.411852,538.044186 C851.482834,538.044186 851.740142,538.168403 852.183782,538.416842 C852.627423,538.665281 853.204147,538.916673 853.913972,539.171027 C854.623797,539.425381 855.327696,539.552556 856.025691,539.552556 C856.818329,539.552556 857.605039,539.416509 858.385847,539.144409 C858.326695,539.582134 858.297119,539.972532 858.297119,540.315614 C858.297119,541.960042 858.776244,543.474313 859.734507,544.858471 Z M878.739975,556.162377 C878.739975,557.582027 878.308171,558.702942 877.444551,559.525156 C876.580931,560.34737 875.433398,560.75847 874.001917,560.75847 L858.49232,560.75847 C857.06084,560.75847 855.913307,560.34737 855.049686,559.525156 C854.186066,558.702942 853.754262,557.582027 853.754262,556.162377 C853.754262,555.535365 853.774965,554.92315 853.816372,554.325714 C853.857778,553.728278 853.94059,553.08353 854.064809,552.391451 C854.189028,551.699371 854.345779,551.057581 854.535066,550.46606 C854.724352,549.874539 854.978703,549.297815 855.298124,548.73587 C855.617545,548.173926 855.984282,547.694801 856.398347,547.298482 C856.812411,546.902163 857.318154,546.585704 857.91559,546.349096 C858.513026,546.112488 859.172562,545.994185 859.894217,545.994185 C860.012521,545.994185 860.266872,546.12136 860.657275,546.375714 C861.047679,546.630068 861.479483,546.913994 861.952699,547.2275 C862.425916,547.541006 863.058834,547.824932 863.851471,548.079286 C864.644109,548.33364 865.44265,548.460815 866.247119,548.460815 C867.051587,548.460815 867.850128,548.33364 868.642766,548.079286 C869.435404,547.824932 870.068321,547.541006 870.541538,547.2275 C871.014755,546.913994 871.446558,546.630068 871.836962,546.375714 C872.227366,546.12136 872.481716,545.994185 872.60002,545.994185 C873.321675,545.994185 873.981211,546.112488 874.578647,546.349096 C875.176083,546.585704 875.681826,546.902163 876.09589,547.298482 C876.509955,547.694801 876.876692,548.173926 877.196114,548.73587 C877.515535,549.297815 877.769885,549.874539 877.959172,550.46606 C878.148458,551.057581 878.305209,551.699371 878.429428,552.391451 C878.553648,553.08353 878.636459,553.728278 878.677866,554.325714 C878.719272,554.92315 878.739975,555.535365 878.739975,556.162377 Z M860.568548,533.501329 C860.568548,534.755353 860.124914,535.82599 859.237632,536.713271 C858.350351,537.600552 857.279715,538.044186 856.025691,538.044186 C854.771667,538.044186 853.70103,537.600552 852.813749,536.713271 C851.926468,535.82599 851.482834,534.755353 851.482834,533.501329 C851.482834,532.247305 851.926468,531.176668 852.813749,530.289387 C853.70103,529.402106 854.771667,528.958472 856.025691,528.958472 C857.279715,528.958472 858.350351,529.402106 859.237632,530.289387 C860.124914,531.176668 860.568548,532.247305 860.568548,533.501329 Z M873.061404,540.315614 C873.061404,542.19665 872.395953,543.802605 871.065031,545.133527 C869.73411,546.464449 868.128155,547.1299 866.247119,547.1299 C864.366083,547.1299 862.760128,546.464449 861.429206,545.133527 C860.098284,543.802605 859.432833,542.19665 859.432833,540.315614 C859.432833,538.434578 860.098284,536.828623 861.429206,535.497702 C862.760128,534.16678 864.366083,533.501329 866.247119,533.501329 C868.128155,533.501329 869.73411,534.16678 871.065031,535.497702 C872.395953,536.828623 873.061404,538.434578 873.061404,540.315614 Z M883.282832,544.30836 C883.282832,545.231132 882.951585,545.932074 882.289082,546.411205 C881.626579,546.890337 880.810292,547.1299 879.840198,547.1299 L877.462297,547.1299 C876.243764,545.674758 874.676257,544.917623 872.75973,544.858471 C873.717994,543.474313 874.197118,541.960042 874.197118,540.315614 C874.197118,539.972532 874.167543,539.582134 874.108391,539.144409 C874.889198,539.416509 875.675909,539.552556 876.468547,539.552556 C877.166541,539.552556 877.87044,539.425381 878.580265,539.171027 C879.29009,538.916673 879.866814,538.665281 880.310455,538.416842 C880.754096,538.168403 881.011403,538.044186 881.082386,538.044186 C882.549357,538.044186 883.282832,540.132223 883.282832,544.30836 Z M881.011404,533.501329 C881.011404,534.755353 880.56777,535.82599 879.680489,536.713271 C878.793207,537.600552 877.722571,538.044186 876.468547,538.044186 C875.214523,538.044186 874.143886,537.600552 873.256605,536.713271 C872.369324,535.82599 871.92569,534.755353 871.92569,533.501329 C871.92569,532.247305 872.369324,531.176668 873.256605,530.289387 C874.143886,529.402106 875.214523,528.958472 876.468547,528.958472 C877.722571,528.958472 878.793207,529.402106 879.680489,530.289387 C880.56777,531.176668 881.011404,532.247305 881.011404,533.501329 Z M920.88562,539.002445 C920.88562,539.475661 920.719997,539.877889 920.388745,540.209141 L905.127585,555.470301 C904.796334,555.801553 904.394106,555.967176 903.920889,555.967176 C903.447672,555.967176 903.045444,555.801553 902.714193,555.470301 L893.876916,546.633025 C893.545665,546.301773 893.380041,545.899545 893.380041,545.426328 C893.380041,544.953112 893.545665,544.550884 893.876916,544.219632 L896.290309,541.806239 C896.621561,541.474988 897.023789,541.309364 897.497005,541.309364 C897.970222,541.309364 898.37245,541.474988 898.703702,541.806239 L903.920889,547.041172 L915.56196,535.382356 C915.893211,535.051104 916.29544,534.885481 916.768656,534.885481 C917.241873,534.885481 917.644101,535.051104 917.975353,535.382356 L920.388745,537.795748 C920.719997,538.127 920.88562,538.529228 920.88562,539.002445 Z" id="--copy" fill="#E04733"></path>
+ <path d="M1045.7411,135.899363 C1043.82457,135.958515 1042.25707,136.715651 1041.03854,138.170792 L1038.66063,138.170792 C1037.69054,138.170792 1036.87425,137.931229 1036.21175,137.452098 C1035.54925,136.972966 1035.218,136.272024 1035.218,135.349252 C1035.218,131.173115 1035.95147,129.085078 1037.41845,129.085078 C1037.48943,129.085078 1037.74674,129.209295 1038.19038,129.457734 C1038.63402,129.706173 1039.21074,129.957566 1039.92057,130.211919 C1040.63039,130.466273 1041.33429,130.593448 1042.03229,130.593448 C1042.82492,130.593448 1043.61163,130.457401 1044.39244,130.185301 C1044.33329,130.623027 1044.30371,131.013424 1044.30371,131.356506 C1044.30371,133.000934 1044.78284,134.515205 1045.7411,135.899363 Z M1064.74657,147.203269 C1064.74657,148.622919 1064.31477,149.743834 1063.45115,150.566048 C1062.58753,151.388262 1061.43999,151.799362 1060.00851,151.799362 L1044.49891,151.799362 C1043.06743,151.799362 1041.9199,151.388262 1041.05628,150.566048 C1040.19266,149.743834 1039.76086,148.622919 1039.76086,147.203269 C1039.76086,146.576257 1039.78156,145.964042 1039.82297,145.366606 C1039.86437,144.76917 1039.94718,144.124422 1040.0714,143.432343 C1040.19562,142.740264 1040.35237,142.098473 1040.54166,141.506952 C1040.73095,140.915432 1040.9853,140.338707 1041.30472,139.776763 C1041.62414,139.214818 1041.99088,138.735693 1042.40494,138.339374 C1042.81901,137.943055 1043.32475,137.626597 1043.92218,137.389988 C1044.51962,137.15338 1045.17916,137.035078 1045.90081,137.035078 C1046.01912,137.035078 1046.27347,137.162253 1046.66387,137.416607 C1047.05427,137.67096 1047.48608,137.954886 1047.95929,138.268392 C1048.43251,138.581898 1049.06543,138.865824 1049.85807,139.120178 C1050.6507,139.374532 1051.44924,139.501707 1052.25371,139.501707 C1053.05818,139.501707 1053.85672,139.374532 1054.64936,139.120178 C1055.442,138.865824 1056.07492,138.581898 1056.54813,138.268392 C1057.02135,137.954886 1057.45315,137.67096 1057.84356,137.416607 C1058.23396,137.162253 1058.48831,137.035078 1058.60661,137.035078 C1059.32827,137.035078 1059.98781,137.15338 1060.58524,137.389988 C1061.18268,137.626597 1061.68842,137.943055 1062.10248,138.339374 C1062.51655,138.735693 1062.88329,139.214818 1063.20271,139.776763 C1063.52213,140.338707 1063.77648,140.915432 1063.96577,141.506952 C1064.15505,142.098473 1064.3118,142.740264 1064.43602,143.432343 C1064.56024,144.124422 1064.64305,144.76917 1064.68446,145.366606 C1064.72587,145.964042 1064.74657,146.576257 1064.74657,147.203269 Z M1046.57514,124.542221 C1046.57514,125.796245 1046.13151,126.866882 1045.24423,127.754163 C1044.35695,128.641444 1043.28631,129.085078 1042.03229,129.085078 C1040.77826,129.085078 1039.70762,128.641444 1038.82034,127.754163 C1037.93306,126.866882 1037.48943,125.796245 1037.48943,124.542221 C1037.48943,123.288197 1037.93306,122.21756 1038.82034,121.330279 C1039.70762,120.442998 1040.77826,119.999364 1042.03229,119.999364 C1043.28631,119.999364 1044.35695,120.442998 1045.24423,121.330279 C1046.13151,122.21756 1046.57514,123.288197 1046.57514,124.542221 Z M1059.068,131.356506 C1059.068,133.237543 1058.40255,134.843497 1057.07163,136.174419 C1055.7407,137.505341 1054.13475,138.170792 1052.25371,138.170792 C1050.37268,138.170792 1048.76672,137.505341 1047.4358,136.174419 C1046.10488,134.843497 1045.43943,133.237543 1045.43943,131.356506 C1045.43943,129.47547 1046.10488,127.869516 1047.4358,126.538594 C1048.76672,125.207672 1050.37268,124.542221 1052.25371,124.542221 C1054.13475,124.542221 1055.7407,125.207672 1057.07163,126.538594 C1058.40255,127.869516 1059.068,129.47547 1059.068,131.356506 Z M1069.28943,135.349252 C1069.28943,136.272024 1068.95818,136.972966 1068.29568,137.452098 C1067.63317,137.931229 1066.81689,138.170792 1065.84679,138.170792 L1063.46889,138.170792 C1062.25036,136.715651 1060.68285,135.958515 1058.76632,135.899363 C1059.72459,134.515205 1060.20371,133.000934 1060.20371,131.356506 C1060.20371,131.013424 1060.17414,130.623027 1060.11498,130.185301 C1060.89579,130.457401 1061.6825,130.593448 1062.47514,130.593448 C1063.17314,130.593448 1063.87703,130.466273 1064.58686,130.211919 C1065.29668,129.957566 1065.87341,129.706173 1066.31705,129.457734 C1066.76069,129.209295 1067.018,129.085078 1067.08898,129.085078 C1068.55595,129.085078 1069.28943,131.173115 1069.28943,135.349252 Z M1067.018,124.542221 C1067.018,125.796245 1066.57436,126.866882 1065.68708,127.754163 C1064.7998,128.641444 1063.72917,129.085078 1062.47514,129.085078 C1061.22112,129.085078 1060.15048,128.641444 1059.2632,127.754163 C1058.37592,126.866882 1057.93228,125.796245 1057.93228,124.542221 C1057.93228,123.288197 1058.37592,122.21756 1059.2632,121.330279 C1060.15048,120.442998 1061.22112,119.999364 1062.47514,119.999364 C1063.72917,119.999364 1064.7998,120.442998 1065.68708,121.330279 C1066.57436,122.21756 1067.018,123.288197 1067.018,124.542221 Z M1106.89221,130.043337 C1106.89221,130.516553 1106.72659,130.918782 1106.39534,131.250033 L1091.13418,146.511193 C1090.80293,146.842445 1090.4007,147.008068 1089.92748,147.008068 C1089.45427,147.008068 1089.05204,146.842445 1088.72079,146.511193 L1079.88351,137.673917 C1079.55226,137.342665 1079.38664,136.940437 1079.38664,136.46722 C1079.38664,135.994004 1079.55226,135.591776 1079.88351,135.260524 L1082.2969,132.847131 C1082.62816,132.51588 1083.03038,132.350256 1083.5036,132.350256 C1083.97682,132.350256 1084.37904,132.51588 1084.7103,132.847131 L1089.92748,138.082064 L1101.56855,126.423248 C1101.89981,126.091996 1102.30203,125.926373 1102.77525,125.926373 C1103.24847,125.926373 1103.6507,126.091996 1103.98195,126.423248 L1106.39534,128.83664 C1106.72659,129.167892 1106.89221,129.57012 1106.89221,130.043337 Z" id="--copy-2" fill="#E04733"></path>
+ <path d="M385.53174,544.858471 C383.615212,544.917623 382.047706,545.674758 380.829173,547.1299 L378.451271,547.1299 C377.481177,547.1299 376.664891,546.890337 376.002387,546.411205 C375.339884,545.932074 375.008638,545.231132 375.008638,544.30836 C375.008638,540.132223 375.742112,538.044186 377.209084,538.044186 C377.280066,538.044186 377.537374,538.168403 377.981015,538.416842 C378.424655,538.665281 379.001379,538.916673 379.711204,539.171027 C380.421029,539.425381 381.124928,539.552556 381.822923,539.552556 C382.615561,539.552556 383.402272,539.416509 384.183079,539.144409 C384.123927,539.582134 384.094351,539.972532 384.094351,540.315614 C384.094351,541.960042 384.573476,543.474313 385.53174,544.858471 Z M404.537207,556.162377 C404.537207,557.582027 404.105404,558.702942 403.241783,559.525156 C402.378163,560.34737 401.23063,560.75847 399.79915,560.75847 L384.289552,560.75847 C382.858072,560.75847 381.710539,560.34737 380.846918,559.525156 C379.983298,558.702942 379.551494,557.582027 379.551494,556.162377 C379.551494,555.535365 379.572197,554.92315 379.613604,554.325714 C379.65501,553.728278 379.737822,553.08353 379.862041,552.391451 C379.986261,551.699371 380.143011,551.057581 380.332298,550.46606 C380.521585,549.874539 380.775935,549.297815 381.095356,548.73587 C381.414777,548.173926 381.781515,547.694801 382.195579,547.298482 C382.609644,546.902163 383.115386,546.585704 383.712822,546.349096 C384.310258,546.112488 384.969794,545.994185 385.691449,545.994185 C385.809754,545.994185 386.064104,546.12136 386.454507,546.375714 C386.844911,546.630068 387.276715,546.913994 387.749932,547.2275 C388.223148,547.541006 388.856066,547.824932 389.648704,548.079286 C390.441342,548.33364 391.239883,548.460815 392.044351,548.460815 C392.848819,548.460815 393.64736,548.33364 394.439998,548.079286 C395.232636,547.824932 395.865554,547.541006 396.33877,547.2275 C396.811987,546.913994 397.243791,546.630068 397.634194,546.375714 C398.024598,546.12136 398.278948,545.994185 398.397252,545.994185 C399.118908,545.994185 399.778443,546.112488 400.375879,546.349096 C400.973315,546.585704 401.479058,546.902163 401.893123,547.298482 C402.307187,547.694801 402.673925,548.173926 402.993346,548.73587 C403.312767,549.297815 403.567117,549.874539 403.756404,550.46606 C403.945691,551.057581 404.102441,551.699371 404.226661,552.391451 C404.35088,553.08353 404.433692,553.728278 404.475098,554.325714 C404.516504,554.92315 404.537207,555.535365 404.537207,556.162377 Z M386.36578,533.501329 C386.36578,534.755353 385.922146,535.82599 385.034865,536.713271 C384.147584,537.600552 383.076947,538.044186 381.822923,538.044186 C380.568899,538.044186 379.498262,537.600552 378.610981,536.713271 C377.7237,535.82599 377.280066,534.755353 377.280066,533.501329 C377.280066,532.247305 377.7237,531.176668 378.610981,530.289387 C379.498262,529.402106 380.568899,528.958472 381.822923,528.958472 C383.076947,528.958472 384.147584,529.402106 385.034865,530.289387 C385.922146,531.176668 386.36578,532.247305 386.36578,533.501329 Z M398.858636,540.315614 C398.858636,542.19665 398.193185,543.802605 396.862264,545.133527 C395.531342,546.464449 393.925387,547.1299 392.044351,547.1299 C390.163315,547.1299 388.55736,546.464449 387.226438,545.133527 C385.895516,543.802605 385.230066,542.19665 385.230066,540.315614 C385.230066,538.434578 385.895516,536.828623 387.226438,535.497702 C388.55736,534.16678 390.163315,533.501329 392.044351,533.501329 C393.925387,533.501329 395.531342,534.16678 396.862264,535.497702 C398.193185,536.828623 398.858636,538.434578 398.858636,540.315614 Z M409.080064,544.30836 C409.080064,545.231132 408.748818,545.932074 408.086314,546.411205 C407.423811,546.890337 406.607525,547.1299 405.637431,547.1299 L403.259529,547.1299 C402.040996,545.674758 400.47349,544.917623 398.556962,544.858471 C399.515226,543.474313 399.99435,541.960042 399.99435,540.315614 C399.99435,539.972532 399.964775,539.582134 399.905623,539.144409 C400.68643,539.416509 401.473141,539.552556 402.265779,539.552556 C402.963773,539.552556 403.667673,539.425381 404.377498,539.171027 C405.087323,538.916673 405.664047,538.665281 406.107687,538.416842 C406.551328,538.168403 406.808635,538.044186 406.879618,538.044186 C408.34659,538.044186 409.080064,540.132223 409.080064,544.30836 Z M406.808636,533.501329 C406.808636,534.755353 406.365002,535.82599 405.477721,536.713271 C404.59044,537.600552 403.519803,538.044186 402.265779,538.044186 C401.011755,538.044186 399.941118,537.600552 399.053837,536.713271 C398.166556,535.82599 397.722922,534.755353 397.722922,533.501329 C397.722922,532.247305 398.166556,531.176668 399.053837,530.289387 C399.941118,529.402106 401.011755,528.958472 402.265779,528.958472 C403.519803,528.958472 404.59044,529.402106 405.477721,530.289387 C406.365002,531.176668 406.808636,532.247305 406.808636,533.501329 Z M440.063768,552.418069 C440.063768,552.891286 439.898145,553.293514 439.566893,553.624765 L437.1535,556.038158 C436.822249,556.36941 436.420021,556.535033 435.946804,556.535033 C435.473587,556.535033 435.071359,556.36941 434.740108,556.038158 L429.52292,550.820971 L424.305733,556.038158 C423.974481,556.36941 423.572253,556.535033 423.099037,556.535033 C422.62582,556.535033 422.223592,556.36941 421.89234,556.038158 L419.478948,553.624765 C419.147696,553.293514 418.982073,552.891286 418.982073,552.418069 C418.982073,551.944852 419.147696,551.542624 419.478948,551.211373 L424.696135,545.994185 L419.478948,540.776998 C419.147696,540.445747 418.982073,540.043518 418.982073,539.570302 C418.982073,539.097085 419.147696,538.694857 419.478948,538.363605 L421.89234,535.950213 C422.223592,535.618961 422.62582,535.453338 423.099037,535.453338 C423.572253,535.453338 423.974481,535.618961 424.305733,535.950213 L429.52292,541.1674 L434.740108,535.950213 C435.071359,535.618961 435.473587,535.453338 435.946804,535.453338 C436.420021,535.453338 436.822249,535.618961 437.1535,535.950213 L439.566893,538.363605 C439.898145,538.694857 440.063768,539.097085 440.063768,539.570302 C440.063768,540.043518 439.898145,540.445747 439.566893,540.776998 L434.349706,545.994185 L439.566893,551.211373 C439.898145,551.542624 440.063768,551.944852 440.063768,552.418069 Z" id="-" fill="#E04733"></path>
+ <path d="M622.540142,544.858471 C620.623615,544.917623 619.056108,545.674758 617.837575,547.1299 L615.459674,547.1299 C614.48958,547.1299 613.673293,546.890337 613.01079,546.411205 C612.348287,545.932074 612.01704,545.231132 612.01704,544.30836 C612.01704,540.132223 612.750515,538.044186 614.217486,538.044186 C614.288469,538.044186 614.545777,538.168403 614.989417,538.416842 C615.433058,538.665281 616.009782,538.916673 616.719607,539.171027 C617.429432,539.425381 618.133331,539.552556 618.831325,539.552556 C619.623963,539.552556 620.410674,539.416509 621.191481,539.144409 C621.132329,539.582134 621.102754,539.972532 621.102754,540.315614 C621.102754,541.960042 621.581878,543.474313 622.540142,544.858471 Z M641.54561,556.162377 C641.54561,557.582027 641.113806,558.702942 640.250186,559.525156 C639.386565,560.34737 638.239032,560.75847 636.807552,560.75847 L621.297955,560.75847 C619.866474,560.75847 618.718941,560.34737 617.855321,559.525156 C616.991701,558.702942 616.559897,557.582027 616.559897,556.162377 C616.559897,555.535365 616.5806,554.92315 616.622006,554.325714 C616.663413,553.728278 616.746224,553.08353 616.870444,552.391451 C616.994663,551.699371 617.151414,551.057581 617.3407,550.46606 C617.529987,549.874539 617.784337,549.297815 618.103758,548.73587 C618.42318,548.173926 618.789917,547.694801 619.203982,547.298482 C619.618046,546.902163 620.123789,546.585704 620.721225,546.349096 C621.318661,546.112488 621.978197,545.994185 622.699852,545.994185 C622.818156,545.994185 623.072506,546.12136 623.46291,546.375714 C623.853314,546.630068 624.285117,546.913994 624.758334,547.2275 C625.231551,547.541006 625.864468,547.824932 626.657106,548.079286 C627.449744,548.33364 628.248285,548.460815 629.052753,548.460815 C629.857222,548.460815 630.655763,548.33364 631.448401,548.079286 C632.241038,547.824932 632.873956,547.541006 633.347173,547.2275 C633.820389,546.913994 634.252193,546.630068 634.642597,546.375714 C635.033001,546.12136 635.287351,545.994185 635.405655,545.994185 C636.12731,545.994185 636.786846,546.112488 637.384282,546.349096 C637.981718,546.585704 638.487461,546.902163 638.901525,547.298482 C639.31559,547.694801 639.682327,548.173926 640.001748,548.73587 C640.32117,549.297815 640.57552,549.874539 640.764806,550.46606 C640.954093,551.057581 641.110844,551.699371 641.235063,552.391451 C641.359282,553.08353 641.442094,553.728278 641.4835,554.325714 C641.524907,554.92315 641.54561,555.535365 641.54561,556.162377 Z M623.374182,533.501329 C623.374182,534.755353 622.930548,535.82599 622.043267,536.713271 C621.155986,537.600552 620.085349,538.044186 618.831325,538.044186 C617.577301,538.044186 616.506665,537.600552 615.619384,536.713271 C614.732102,535.82599 614.288468,534.755353 614.288468,533.501329 C614.288468,532.247305 614.732102,531.176668 615.619384,530.289387 C616.506665,529.402106 617.577301,528.958472 618.831325,528.958472 C620.085349,528.958472 621.155986,529.402106 622.043267,530.289387 C622.930548,531.176668 623.374182,532.247305 623.374182,533.501329 Z M635.867039,540.315614 C635.867039,542.19665 635.201588,543.802605 633.870666,545.133527 C632.539744,546.464449 630.933789,547.1299 629.052753,547.1299 C627.171717,547.1299 625.565762,546.464449 624.234841,545.133527 C622.903919,543.802605 622.238468,542.19665 622.238468,540.315614 C622.238468,538.434578 622.903919,536.828623 624.234841,535.497702 C625.565762,534.16678 627.171717,533.501329 629.052753,533.501329 C630.933789,533.501329 632.539744,534.16678 633.870666,535.497702 C635.201588,536.828623 635.867039,538.434578 635.867039,540.315614 Z M646.088467,544.30836 C646.088467,545.231132 645.75722,545.932074 645.094717,546.411205 C644.432214,546.890337 643.615927,547.1299 642.645833,547.1299 L640.267931,547.1299 C639.049399,545.674758 637.481892,544.917623 635.565365,544.858471 C636.523628,543.474313 637.002753,541.960042 637.002753,540.315614 C637.002753,539.972532 636.973177,539.582134 636.914025,539.144409 C637.694833,539.416509 638.481544,539.552556 639.274181,539.552556 C639.972176,539.552556 640.676075,539.425381 641.3859,539.171027 C642.095725,538.916673 642.672449,538.665281 643.11609,538.416842 C643.55973,538.168403 643.817038,538.044186 643.88802,538.044186 C645.354992,538.044186 646.088467,540.132223 646.088467,544.30836 Z M643.817038,533.501329 C643.817038,534.755353 643.373404,535.82599 642.486123,536.713271 C641.598842,537.600552 640.528205,538.044186 639.274181,538.044186 C638.020157,538.044186 636.949521,537.600552 636.06224,536.713271 C635.174958,535.82599 634.731325,534.755353 634.731325,533.501329 C634.731325,532.247305 635.174958,531.176668 636.06224,530.289387 C636.949521,529.402106 638.020157,528.958472 639.274181,528.958472 C640.528205,528.958472 641.598842,529.402106 642.486123,530.289387 C643.373404,531.176668 643.817038,532.247305 643.817038,533.501329 Z M677.07217,552.418069 C677.07217,552.891286 676.906547,553.293514 676.575295,553.624765 L674.161903,556.038158 C673.830651,556.36941 673.428423,556.535033 672.955206,556.535033 C672.48199,556.535033 672.079762,556.36941 671.74851,556.038158 L666.531323,550.820971 L661.314136,556.038158 C660.982884,556.36941 660.580656,556.535033 660.107439,556.535033 C659.634223,556.535033 659.231994,556.36941 658.900743,556.038158 L656.48735,553.624765 C656.156098,553.293514 655.990475,552.891286 655.990475,552.418069 C655.990475,551.944852 656.156098,551.542624 656.48735,551.211373 L661.704537,545.994185 L656.48735,540.776998 C656.156098,540.445747 655.990475,540.043518 655.990475,539.570302 C655.990475,539.097085 656.156098,538.694857 656.48735,538.363605 L658.900743,535.950213 C659.231994,535.618961 659.634223,535.453338 660.107439,535.453338 C660.580656,535.453338 660.982884,535.618961 661.314136,535.950213 L666.531323,541.1674 L671.74851,535.950213 C672.079762,535.618961 672.48199,535.453338 672.955206,535.453338 C673.428423,535.453338 673.830651,535.618961 674.161903,535.950213 L676.575295,538.363605 C676.906547,538.694857 677.07217,539.097085 677.07217,539.570302 C677.07217,540.043518 676.906547,540.445747 676.575295,540.776998 L671.358108,545.994185 L676.575295,551.211373 C676.906547,551.542624 677.07217,551.944852 677.07217,552.418069 Z" id="--copy" fill="#E04733"></path>
+ <path d="M756.856155,443.148434 C756.856155,441.89441 756.412521,440.823773 755.52524,439.936492 C754.637959,439.049211 753.567322,438.605577 752.313298,438.605577 C751.059274,438.605577 749.988638,439.049211 749.101357,439.936492 C748.214075,440.823773 747.770441,441.89441 747.770441,443.148434 C747.770441,444.402458 748.214075,445.473095 749.101357,446.360376 C749.988638,447.247657 751.059274,447.691291 752.313298,447.691291 C753.567322,447.691291 754.637959,447.247657 755.52524,446.360376 C756.412521,445.473095 756.856155,444.402458 756.856155,443.148434 Z M770.484726,452.234148 C770.484726,451.618966 770.259951,451.086605 769.810396,450.63705 C769.36084,450.187494 768.828479,449.962719 768.213297,449.962719 C767.598116,449.962719 767.065755,450.187494 766.616199,450.63705 C766.166644,451.086605 765.941869,451.618966 765.941869,452.234148 C765.941869,452.86116 766.163686,453.396478 766.607327,453.840119 C767.050967,454.283759 767.586285,454.505576 768.213297,454.505576 C768.840309,454.505576 769.375628,454.283759 769.819268,453.840119 C770.262909,453.396478 770.484726,452.86116 770.484726,452.234148 Z M770.484726,434.06272 C770.484726,433.447539 770.259951,432.915178 769.810396,432.465622 C769.36084,432.016066 768.828479,431.791292 768.213297,431.791292 C767.598116,431.791292 767.065755,432.016066 766.616199,432.465622 C766.166644,432.915178 765.941869,433.447539 765.941869,434.06272 C765.941869,434.689732 766.163686,435.22505 766.607327,435.668691 C767.050967,436.112332 767.586285,436.334149 768.213297,436.334149 C768.840309,436.334149 769.375628,436.112332 769.819268,435.668691 C770.262909,435.22505 770.484726,434.689732 770.484726,434.06272 Z M763.670441,441.53359 L763.670441,444.816514 C763.670441,444.934818 763.629035,445.050163 763.546222,445.162552 C763.463409,445.274941 763.368767,445.33705 763.262293,445.34888 L760.511735,445.774773 C760.381601,446.188838 760.192317,446.638387 759.943878,447.123434 C760.346112,447.691294 760.878473,448.371532 761.540976,449.16417 C761.623789,449.282474 761.665195,449.400777 761.665195,449.519081 C761.665195,449.661046 761.623789,449.773433 761.540976,449.856246 C761.268877,450.211159 760.780879,450.740562 760.07697,451.444471 C759.37306,452.148381 758.908723,452.500331 758.683945,452.500331 C758.553811,452.500331 758.429593,452.458925 758.311289,452.376112 L756.270553,450.779014 C755.832827,451.003792 755.377363,451.18716 754.904146,451.329125 C754.774012,452.60681 754.637964,453.523654 754.495999,454.079683 C754.413186,454.363613 754.235733,454.505576 753.963633,454.505576 L750.662964,454.505576 C750.532829,454.505576 750.414527,454.461213 750.308053,454.372485 C750.201579,454.283757 750.142428,454.180242 750.130598,454.061938 L749.72245,451.346871 C749.320216,451.228567 748.876582,451.045198 748.391535,450.796759 L746.297562,452.376112 C746.214749,452.458925 746.096447,452.500331 745.942651,452.500331 C745.812517,452.500331 745.688299,452.45301 745.569995,452.358367 C743.866415,450.784921 743.014638,449.838502 743.014638,449.519081 C743.014638,449.412607 743.056044,449.30022 743.138857,449.181916 C743.257161,449.01629 743.499681,448.702789 743.866424,448.241402 C744.233167,447.780016 744.511177,447.419194 744.700464,447.158925 C744.428364,446.638387 744.221335,446.153347 744.07937,445.703791 L741.382049,445.277898 C741.263745,445.266068 741.163188,445.209874 741.080375,445.109316 C740.997562,445.008757 740.956156,444.893412 740.956156,444.763278 L740.956156,441.480354 C740.956156,441.36205 740.997562,441.246705 741.080375,441.134316 C741.163188,441.021927 741.25783,440.959818 741.364303,440.947988 L744.114861,440.522095 C744.244996,440.10803 744.43428,439.658481 744.682718,439.173434 C744.280484,438.605574 743.748124,437.925336 743.08562,437.132698 C743.002807,437.002563 742.961401,436.884261 742.961401,436.777787 C742.961401,436.635822 743.002807,436.51752 743.08562,436.422876 C743.345889,436.067964 743.830929,435.541518 744.540754,434.843524 C745.250579,434.145529 745.717873,433.796537 745.942651,433.796537 C746.072786,433.796537 746.197003,433.837943 746.315308,433.920756 L748.356044,435.517854 C748.758278,435.304907 749.213742,435.115623 749.72245,434.949997 C749.852585,433.672312 749.988633,432.761384 750.130598,432.217185 C750.21341,431.933255 750.390864,431.791292 750.662964,431.791292 L753.963633,431.791292 C754.093768,431.791292 754.21207,431.835655 754.318544,431.924383 C754.425017,432.013111 754.484169,432.116626 754.495999,432.23493 L754.904146,434.949997 C755.306381,435.068301 755.750014,435.25167 756.235061,435.500108 L758.329035,433.920756 C758.423678,433.837943 758.54198,433.796537 758.683945,433.796537 C758.81408,433.796537 758.938297,433.843858 759.056602,433.938501 C760.760181,435.511947 761.611959,436.458366 761.611959,436.777787 C761.611959,436.884261 761.570553,436.996648 761.48774,437.114952 C761.345775,437.304239 761.09734,437.623655 760.742427,438.073211 C760.387515,438.522767 760.121334,438.877674 759.943878,439.137943 C760.215978,439.705803 760.417092,440.190843 760.547226,440.593077 L763.244548,441.001224 C763.362852,441.024885 763.463409,441.086994 763.546222,441.187552 C763.629035,441.288111 763.670441,441.403456 763.670441,441.53359 Z M775.027583,450.99196 L775.027583,453.476335 C775.027583,453.665622 774.14623,453.848991 772.383498,454.026447 C772.241533,454.345868 772.06408,454.653454 771.851132,454.949215 C772.454483,456.286052 772.756154,457.102338 772.756154,457.398098 C772.756154,457.44542 772.732494,457.486826 772.685172,457.522317 C771.241861,458.362277 770.508387,458.78225 770.484726,458.78225 C770.390083,458.78225 770.117987,458.504239 769.668431,457.94821 C769.218876,457.39218 768.911289,456.989952 768.745663,456.741514 C768.509055,456.765174 768.331602,456.777005 768.213297,456.777005 C768.094993,456.777005 767.91754,456.765174 767.680931,456.741514 C767.515306,456.989952 767.207719,457.39218 766.758164,457.94821 C766.308608,458.504239 766.036512,458.78225 765.941869,458.78225 C765.918208,458.78225 765.184733,458.362277 763.741423,457.522317 C763.694101,457.486826 763.670441,457.44542 763.670441,457.398098 C763.670441,457.102338 763.972112,456.286052 764.575463,454.949215 C764.362515,454.653454 764.185062,454.345868 764.043097,454.026447 C762.280365,453.848991 761.399012,453.665622 761.399012,453.476335 L761.399012,450.99196 C761.399012,450.802674 762.280365,450.619305 764.043097,450.441849 C764.196892,450.098767 764.374346,449.791181 764.575463,449.519081 C763.972112,448.182244 763.670441,447.365958 763.670441,447.070197 C763.670441,447.022875 763.694101,446.98147 763.741423,446.945978 C763.788744,446.922318 763.995774,446.804015 764.362516,446.591068 C764.729259,446.37812 765.078251,446.177006 765.409503,445.98772 C765.740755,445.798433 765.918208,445.703791 765.941869,445.703791 C766.036512,445.703791 766.308608,445.978844 766.758164,446.528958 C767.207719,447.079073 767.515306,447.478343 767.680931,447.726782 C767.91754,447.703121 768.094993,447.691291 768.213297,447.691291 C768.331602,447.691291 768.509055,447.703121 768.745663,447.726782 C769.349015,446.886822 769.893206,446.224329 770.378253,445.739282 L770.484726,445.703791 C770.532048,445.703791 771.265522,446.117849 772.685172,446.945978 C772.732494,446.98147 772.756154,447.022875 772.756154,447.070197 C772.756154,447.365958 772.454483,448.182244 771.851132,449.519081 C772.052249,449.791181 772.229703,450.098767 772.383498,450.441849 C774.14623,450.619305 775.027583,450.802674 775.027583,450.99196 Z M775.027583,432.820533 L775.027583,435.304908 C775.027583,435.494194 774.14623,435.677563 772.383498,435.855019 C772.241533,436.17444 772.06408,436.482027 771.851132,436.777787 C772.454483,438.114624 772.756154,438.93091 772.756154,439.226671 C772.756154,439.273992 772.732494,439.315398 772.685172,439.35089 C771.241861,440.190849 770.508387,440.610822 770.484726,440.610822 C770.390083,440.610822 770.117987,440.332812 769.668431,439.776782 C769.218876,439.220753 768.911289,438.818525 768.745663,438.570086 C768.509055,438.593747 768.331602,438.605577 768.213297,438.605577 C768.094993,438.605577 767.91754,438.593747 767.680931,438.570086 C767.515306,438.818525 767.207719,439.220753 766.758164,439.776782 C766.308608,440.332812 766.036512,440.610822 765.941869,440.610822 C765.918208,440.610822 765.184733,440.190849 763.741423,439.35089 C763.694101,439.315398 763.670441,439.273992 763.670441,439.226671 C763.670441,438.93091 763.972112,438.114624 764.575463,436.777787 C764.362515,436.482027 764.185062,436.17444 764.043097,435.855019 C762.280365,435.677563 761.399012,435.494194 761.399012,435.304908 L761.399012,432.820533 C761.399012,432.631246 762.280365,432.447877 764.043097,432.270421 C764.196892,431.927339 764.374346,431.619753 764.575463,431.347653 C763.972112,430.010816 763.670441,429.19453 763.670441,428.89877 C763.670441,428.851448 763.694101,428.810042 763.741423,428.774551 C763.788744,428.75089 763.995774,428.632588 764.362516,428.41964 C764.729259,428.206693 765.078251,428.005579 765.409503,427.816292 C765.740755,427.627005 765.918208,427.532363 765.941869,427.532363 C766.036512,427.532363 766.308608,427.807416 766.758164,428.357531 C767.207719,428.907645 767.515306,429.306916 767.680931,429.555354 C767.91754,429.531694 768.094993,429.519863 768.213297,429.519863 C768.331602,429.519863 768.509055,429.531694 768.745663,429.555354 C769.349015,428.715395 769.893206,428.052901 770.378253,427.567854 L770.484726,427.532363 C770.532048,427.532363 771.265522,427.946422 772.685172,428.774551 C772.732494,428.810042 772.756154,428.851448 772.756154,428.89877 C772.756154,429.19453 772.454483,430.010816 771.851132,431.347653 C772.052249,431.619753 772.229703,431.927339 772.383498,432.270421 C774.14623,432.447877 775.027583,432.631246 775.027583,432.820533 Z" id="-copy-2" fill="#E04733"></path>
+ <path d="M885.860729,188.873341 C885.860729,187.619317 885.417095,186.54868 884.529814,185.661399 C883.642532,184.774118 882.571896,184.330484 881.317872,184.330484 C880.063848,184.330484 878.993211,184.774118 878.10593,185.661399 C877.218649,186.54868 876.775015,187.619317 876.775015,188.873341 C876.775015,190.127365 877.218649,191.198002 878.10593,192.085283 C878.993211,192.972564 880.063848,193.416198 881.317872,193.416198 C882.571896,193.416198 883.642532,192.972564 884.529814,192.085283 C885.417095,191.198002 885.860729,190.127365 885.860729,188.873341 Z M899.489299,197.959055 C899.489299,197.343873 899.264525,196.811512 898.814969,196.361957 C898.365413,195.912401 897.833053,195.687626 897.217871,195.687626 C896.602689,195.687626 896.070329,195.912401 895.620773,196.361957 C895.171217,196.811512 894.946442,197.343873 894.946442,197.959055 C894.946442,198.586067 895.168259,199.121385 895.6119,199.565026 C896.055541,200.008666 896.590859,200.230483 897.217871,200.230483 C897.844883,200.230483 898.380201,200.008666 898.823842,199.565026 C899.267482,199.121385 899.489299,198.586067 899.489299,197.959055 Z M899.489299,179.787627 C899.489299,179.172446 899.264525,178.640085 898.814969,178.190529 C898.365413,177.740973 897.833053,177.516199 897.217871,177.516199 C896.602689,177.516199 896.070329,177.740973 895.620773,178.190529 C895.171217,178.640085 894.946442,179.172446 894.946442,179.787627 C894.946442,180.414639 895.168259,180.949958 895.6119,181.393598 C896.055541,181.837239 896.590859,182.059056 897.217871,182.059056 C897.844883,182.059056 898.380201,181.837239 898.823842,181.393598 C899.267482,180.949958 899.489299,180.414639 899.489299,179.787627 Z M892.675014,187.258497 L892.675014,190.541421 C892.675014,190.659725 892.633608,190.77507 892.550795,190.887459 C892.467982,190.999848 892.37334,191.061957 892.266867,191.073787 L889.516309,191.49968 C889.386174,191.913745 889.19689,192.363294 888.948452,192.848341 C889.350686,193.416201 889.883047,194.096439 890.54555,194.889077 C890.628363,195.007381 890.669769,195.125684 890.669769,195.243988 C890.669769,195.385953 890.628363,195.49834 890.54555,195.581153 C890.27345,195.936066 889.785453,196.465469 889.081543,197.169379 C888.377634,197.873288 887.913297,198.225238 887.688519,198.225238 C887.558384,198.225238 887.434167,198.183832 887.315863,198.101019 L885.275126,196.503921 C884.837401,196.728699 884.381937,196.912068 883.90872,197.054033 C883.778585,198.331717 883.642538,199.248561 883.500573,199.80459 C883.41776,200.08852 883.240306,200.230483 882.968207,200.230483 L879.667537,200.230483 C879.537403,200.230483 879.4191,200.18612 879.312626,200.097392 C879.206153,200.008664 879.147001,199.905149 879.135171,199.786845 L878.727024,197.071778 C878.32479,196.953474 877.881156,196.770105 877.396109,196.521667 L875.302136,198.101019 C875.219323,198.183832 875.10102,198.225238 874.947225,198.225238 C874.81709,198.225238 874.692873,198.177917 874.574569,198.083274 C872.870989,196.509828 872.019212,195.563409 872.019212,195.243988 C872.019212,195.137514 872.060617,195.025127 872.14343,194.906823 C872.261734,194.741197 872.504254,194.427696 872.870997,193.96631 C873.23774,193.504923 873.515751,193.144101 873.705037,192.883832 C873.432938,192.363294 873.225909,191.878254 873.083944,191.428698 L870.386622,191.002805 C870.268318,190.990975 870.167761,190.934781 870.084948,190.834223 C870.002135,190.733664 869.96073,190.618319 869.96073,190.488185 L869.96073,187.205261 C869.96073,187.086957 870.002135,186.971612 870.084948,186.859223 C870.167761,186.746834 870.262403,186.684725 870.368877,186.672895 L873.119435,186.247002 C873.249569,185.832937 873.438853,185.383388 873.687292,184.898341 C873.285058,184.330481 872.752697,183.650243 872.090194,182.857605 C872.007381,182.72747 871.965975,182.609168 871.965975,182.502694 C871.965975,182.360729 872.007381,182.242427 872.090194,182.147783 C872.350463,181.792871 872.835503,181.266425 873.545328,180.568431 C874.255153,179.870436 874.722447,179.521444 874.947225,179.521444 C875.077359,179.521444 875.201577,179.56285 875.319881,179.645663 L877.360618,181.242761 C877.762852,181.029814 878.218316,180.84053 878.727024,180.674904 C878.857158,179.397219 878.993206,178.486291 879.135171,177.942092 C879.217984,177.658162 879.395438,177.516199 879.667537,177.516199 L882.968207,177.516199 C883.098341,177.516199 883.216643,177.560562 883.323117,177.64929 C883.429591,177.738018 883.488742,177.841533 883.500573,177.959837 L883.90872,180.674904 C884.310954,180.793208 884.754588,180.976577 885.239635,181.225016 L887.333608,179.645663 C887.428251,179.56285 887.546554,179.521444 887.688519,179.521444 C887.818653,179.521444 887.942871,179.568765 888.061175,179.663408 C889.764755,181.236854 890.616532,182.183273 890.616532,182.502694 C890.616532,182.609168 890.575126,182.721555 890.492313,182.839859 C890.350348,183.029146 890.101913,183.348562 889.747001,183.798118 C889.392088,184.247674 889.125908,184.602581 888.948452,184.86285 C889.220551,185.43071 889.421665,185.91575 889.5518,186.317984 L892.249121,186.726131 C892.367425,186.749792 892.467982,186.811901 892.550795,186.912459 C892.633608,187.013018 892.675014,187.128363 892.675014,187.258497 Z M904.032156,196.716867 L904.032156,199.201242 C904.032156,199.390529 903.150804,199.573898 901.388072,199.751354 C901.246107,200.070775 901.068653,200.378361 900.855706,200.674122 C901.459057,202.010959 901.760728,202.827245 901.760728,203.123005 C901.760728,203.170327 901.737067,203.211733 901.689746,203.247224 C900.246435,204.087184 899.51296,204.507157 899.489299,204.507157 C899.394656,204.507157 899.122561,204.229147 898.673005,203.673117 C898.223449,203.117087 897.915863,202.714859 897.750237,202.466421 C897.513629,202.490081 897.336175,202.501912 897.217871,202.501912 C897.099567,202.501912 896.922113,202.490081 896.685505,202.466421 C896.519879,202.714859 896.212293,203.117087 895.762737,203.673117 C895.313181,204.229147 895.041086,204.507157 894.946442,204.507157 C894.922782,204.507157 894.189307,204.087184 892.745996,203.247224 C892.698675,203.211733 892.675014,203.170327 892.675014,203.123005 C892.675014,202.827245 892.976685,202.010959 893.580036,200.674122 C893.367089,200.378361 893.189635,200.070775 893.04767,199.751354 C891.284938,199.573898 890.403586,199.390529 890.403586,199.201242 L890.403586,196.716867 C890.403586,196.527581 891.284938,196.344212 893.04767,196.166756 C893.201466,195.823674 893.378919,195.516088 893.580036,195.243988 C892.976685,193.907151 892.675014,193.090865 892.675014,192.795104 C892.675014,192.747783 892.698675,192.706377 892.745996,192.670885 C892.793318,192.647225 893.000347,192.528922 893.36709,192.315975 C893.733833,192.103027 894.082825,191.901913 894.414076,191.712627 C894.745328,191.52334 894.922782,191.428698 894.946442,191.428698 C895.041086,191.428698 895.313181,191.703751 895.762737,192.253865 C896.212293,192.80398 896.519879,193.20325 896.685505,193.451689 C896.922113,193.428028 897.099567,193.416198 897.217871,193.416198 C897.336175,193.416198 897.513629,193.428028 897.750237,193.451689 C898.353588,192.611729 898.897779,191.949236 899.382826,191.464189 L899.489299,191.428698 C899.536621,191.428698 900.270096,191.842756 901.689746,192.670885 C901.737067,192.706377 901.760728,192.747783 901.760728,192.795104 C901.760728,193.090865 901.459057,193.907151 900.855706,195.243988 C901.056823,195.516088 901.234276,195.823674 901.388072,196.166756 C903.150804,196.344212 904.032156,196.527581 904.032156,196.716867 Z M904.032156,178.54544 L904.032156,181.029815 C904.032156,181.219101 903.150804,181.40247 901.388072,181.579926 C901.246107,181.899347 901.068653,182.206934 900.855706,182.502694 C901.459057,183.839531 901.760728,184.655817 901.760728,184.951578 C901.760728,184.9989 901.737067,185.040305 901.689746,185.075797 C900.246435,185.915756 899.51296,186.33573 899.489299,186.33573 C899.394656,186.33573 899.122561,186.057719 898.673005,185.501689 C898.223449,184.94566 897.915863,184.543432 897.750237,184.294993 C897.513629,184.318654 897.336175,184.330484 897.217871,184.330484 C897.099567,184.330484 896.922113,184.318654 896.685505,184.294993 C896.519879,184.543432 896.212293,184.94566 895.762737,185.501689 C895.313181,186.057719 895.041086,186.33573 894.946442,186.33573 C894.922782,186.33573 894.189307,185.915756 892.745996,185.075797 C892.698675,185.040305 892.675014,184.9989 892.675014,184.951578 C892.675014,184.655817 892.976685,183.839531 893.580036,182.502694 C893.367089,182.206934 893.189635,181.899347 893.04767,181.579926 C891.284938,181.40247 890.403586,181.219101 890.403586,181.029815 L890.403586,178.54544 C890.403586,178.356153 891.284938,178.172784 893.04767,177.995328 C893.201466,177.652246 893.378919,177.34466 893.580036,177.07256 C892.976685,175.735723 892.675014,174.919437 892.675014,174.623677 C892.675014,174.576355 892.698675,174.534949 892.745996,174.499458 C892.793318,174.475797 893.000347,174.357495 893.36709,174.144547 C893.733833,173.9316 894.082825,173.730486 894.414076,173.541199 C894.745328,173.351912 894.922782,173.25727 894.946442,173.25727 C895.041086,173.25727 895.313181,173.532323 895.762737,174.082438 C896.212293,174.632552 896.519879,175.031823 896.685505,175.280261 C896.922113,175.256601 897.099567,175.24477 897.217871,175.24477 C897.336175,175.24477 897.513629,175.256601 897.750237,175.280261 C898.353588,174.440302 898.897779,173.777809 899.382826,173.292762 L899.489299,173.25727 C899.536621,173.25727 900.270096,173.671329 901.689746,174.499458 C901.737067,174.534949 901.760728,174.576355 901.760728,174.623677 C901.760728,174.919437 901.459057,175.735723 900.855706,177.07256 C901.056823,177.34466 901.234276,177.652246 901.388072,177.995328 C903.150804,178.172784 904.032156,178.356153 904.032156,178.54544 Z" id="-copy-3" fill="#E04733"></path>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/doc/ci/review_apps/img/review_apps_preview_in_mr.png b/doc/ci/review_apps/img/review_apps_preview_in_mr.png
index 7d0923f198f..3e6506a6a3a 100644
--- a/doc/ci/review_apps/img/review_apps_preview_in_mr.png
+++ b/doc/ci/review_apps/img/review_apps_preview_in_mr.png
Binary files differ
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 1b17f6ac5ea..64be011008e 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -1,95 +1,94 @@
-# Getting started with Review Apps
+# Review Apps
-> - [Introduced][ce-21971] in GitLab 8.12. Further additions were made in GitLab
-> 8.13 and 8.14.
-> - Inspired by [Heroku's Review Apps][heroku-apps] which itself was inspired by
-> [Fourchette].
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/21971) in GitLab 8.12. Further additions were made in GitLab 8.13 and 8.14.
+> - Inspired by [Heroku's Review Apps](https://devcenter.heroku.com/articles/github-integration-review-apps), which itself was inspired by [Fourchette](https://github.com/rainforestapp/fourchette).
-The basis of Review Apps is the [dynamic environments] which allow you to create
-a new environment (dynamically) for each one of your branches.
+For a video introduction to Review Apps, see [8.14 Webcast: Review Apps & Time Tracking Beta (EE) - GitLab Release](https://www.youtube.com/watch?v=CteZol_7pxo).
-A Review App can then be visible as a link when you visit the [merge request]
-relevant to the branch. That way, you are able to see live all changes introduced
-by the merge request changes. Reviewing anything, from performance to interface
-changes, becomes much easier with a live environment and as such, Review Apps
-can make a huge impact on your development flow.
+## Overview
-They mostly make sense to be used with web applications, but you can use them
-any way you'd like.
+Review Apps are a collaboration tool that takes the hard work out of providing an environment to showcase product changes.
-## Overview
+Review Apps:
+
+- Provide an automatic live preview of changes made in a feature branch by spinning up a dynamic environment for your merge requests.
+- Allow designers and product manages to see your changes without needing to check out your branch and run your changes in a sandbox environment.
+- Are fully integrated with the [GitLab DevOps LifeCycle](../../README.md#complete-devops-with-gitlab).
+- Allow you to deploy your changes wherever you want.
+
+![Review Apps Workflow](img/continuous-delivery-review-apps.svg)
+
+Reviewing anything, from performance to interface changes, becomes much easier with a live environment and so Review Apps can make a large impact on your development flow.
-Simply put, a Review App is a mapping of a branch with an environment as there
-is a 1:1 relation between them.
+## What are Review Apps?
-Here's an example of what it looks like when viewing a merge request with a
-dynamically set environment.
+A Review App is a mapping of a branch with an [environment](../environments.md). The following is an example of a merge request with an environment set dynamically.
![Review App in merge request](img/review_apps_preview_in_mr.png)
-In the image above you can see that the `add-new-line` branch was successfully
-built and deployed under a dynamic environment and can be previewed with an
-also dynamically URL.
+In this example, you can see a branch was:
+
+- Successfully built.
+- Deployed under a dynamic environment that can be reached by clicking on the **View app** button.
+
+## How do Review Apps work?
+
+The basis of Review Apps in GitLab is [dynamic environments](../environments.md#dynamic-environments), which allow you to dynamically create a new environment for each branch.
+
+Access to the Review App is made available as a link on the [merge request](../../user/project/merge_requests.md) relevant to the branch. Review Apps enable you to review all changes proposed by the merge request in live environment.
-The details of the Review Apps implementation depend widely on your real
-technology stack and on your deployment process. The simplest case is to
-deploy a simple static HTML website, but it will not be that straightforward
-when your app is using a database for example. To make a branch be deployed
-on a temporary instance and booting up this instance with all required software
-and services automatically on the fly is not a trivial task. However, it is
-doable, especially if you use Docker, or at least a configuration management
-tool like Chef, Puppet, Ansible or Salt.
+## Use cases
-## Prerequisites
+Some supported use cases include the:
-To get a better understanding of Review Apps, you must first learn how
-environments and deployments work. The following docs will help you grasp that
-knowledge:
+- Simple case of deploying a simple static HTML website.
+- More complicated case of an application that uses a database. Deploying a branch on a temporary instance and booting up this instance with all required software and services automatically on the fly is not a trivial task. However, it is possible, especially if you use Docker or a configuration management tool like Chef, Puppet, Ansible, or Salt.
-1. First, learn about [environments][] and their role in the development workflow.
-1. Then make a small stop to learn about [CI variables][variables] and how they
- can be used in your CI jobs.
-1. Next, explore the [`environment` syntax][yaml-env] as defined in `.gitlab-ci.yml`.
- This will be your primary reference when you are finally comfortable with
- how environments work.
-1. Additionally, find out about [manual actions][] and how you can use them to
- deploy to critical environments like production with the push of a button.
-1. And as a last step, follow the [example tutorials](#examples) which will
- guide you step by step to set up the infrastructure and make use of
- Review Apps.
+Review Apps usually make sense with web applications, but you can use them any way you'd like.
-## Configuration
+## Implementing Review Apps
-The configuration of Review apps depends on your technology stack and your
-infrastructure. Read the [dynamic environments] documentation to understand
-how to define and create them.
+Implementing Review Apps depends on your:
-## Creating and destroying Review Apps
+- Technology stack.
+- Deployment process.
-The creation and destruction of a Review App is defined in `.gitlab-ci.yml`
-at a job level under the `environment` keyword.
+### Prerequisite Knowledge
-Check the [environments] documentation how to do so.
+To get a better understanding of Review Apps, review documentation on how environments and deployments work. Before you implement your own Review Apps:
-## A simple workflow
+1. Learn about [environments](../environments.md) and their role in the development workflow.
+1. Learn about [CI variables](../variables/README.md) and how they can be used in your CI jobs.
+1. Explore the [`environment` syntax](../yaml/README.md#environment) as defined in `.gitlab-ci.yml`. This will become a primary reference.
+1. Additionally, find out about [manual actions](../environments.md#manual-actions) and how you can use them to deploy to critical environments like production with the push of a button.
+1. Follow the [example tutorials](#examples). These will guide you through setting up infrastructure and using Review Apps.
-The process of adding Review Apps in your workflow would look like:
+### Configuring dynamic environments
+
+Configuring Review Apps dynamic environments depends on your technology stack and infrastructure.
+
+For more information, see [dynamic environments](../environments.md#dynamic-environments) documentation to understand how to define and create them.
+
+### Creating and destroying Review Apps
+
+Creating and destroying Review Apps is defined in `.gitlab-ci.yml` at a job level under the `environment` keyword.
+
+For more information, see [Introduction to environments and deployments](../environments.md).
+
+### Adding Review Apps to your workflow
+
+The process of adding Review Apps in your workflow is as follows:
1. Set up the infrastructure to host and deploy the Review Apps.
-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_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.
+1. [Install](https://docs.gitlab.com/runner/install/) and [configure](https://docs.gitlab.com/runner/commands/) a Runner to do deployment.
+1. Set up a job in `.gitlab-ci.yml` that uses the predefined [predefined CI environment variable](../variables/README.md) `${CI_COMMIT_REF_NAME}` to create dynamic environments and restrict it to run only on branches.
+1. Optionally, set a job that [manually stops](../environments.md#stopping-an-environment) the Review Apps.
-From there on, you would follow the branched Git flow:
+After adding Review Apps to your workflow, you follow the branched Git flow. That is:
-1. Push a branch and let the Runner deploy the Review App based on the `script`
- definition of the dynamic environment job.
-1. Wait for the Runner to build and/or deploy your web app.
-1. Click on the link that's present in the MR related to the branch and see the
- changes live.
+1. Push a branch and let the Runner deploy the Review App based on the `script` definition of the dynamic environment job.
+1. Wait for the Runner to build and deploy your web application.
+1. Click on the link that provided in the merge request related to the branch to see the changes live.
## Limitations
@@ -97,27 +96,9 @@ Check the [environments limitations](../environments.md#limitations).
## Examples
-A list of examples used with Review Apps can be found below:
-
-- [Use with NGINX][app-nginx] - Use NGINX and the shell executor of GitLab Runner
- to deploy a simple HTML website.
-
-And below is a soon to be added examples list:
-
-- Use with Amazon S3
-- Use on Heroku with dpl
-- Use with OpenShift/kubernetes
-
-[app-nginx]: https://gitlab.com/gitlab-examples/review-apps-nginx
-[ce-21971]: https://gitlab.com/gitlab-org/gitlab-ce/issues/21971
-[dynamic environments]: ../environments.md#dynamic-environments
-[environments]: ../environments.md
-[fourchette]: https://github.com/rainforestapp/fourchette
-[heroku-apps]: https://devcenter.heroku.com/articles/github-integration-review-apps
-[manual actions]: ../environments.md#manual-actions
-[merge request]: ../../user/project/merge_requests.md
-[variables]: ../variables/README.md
-[yaml-env]: ../yaml/README.md#environment
-[install-runner]: https://docs.gitlab.com/runner/install/
-[conf-runner]: https://docs.gitlab.com/runner/commands/
-[manual-env]: ../environments.md#stopping-an-environment
+The following are example projects that use Review Apps with:
+
+- [NGINX](https://gitlab.com/gitlab-examples/review-apps-nginx).
+- [OpenShift](https://gitlab.com/gitlab-examples/review-apps-openshift).
+
+See also the video [Demo: Cloud Native Development with GitLab](https://www.youtube.com/watch?v=jfIyQEwrocw), which includes a Review Apps example.
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index f4784c19359..32beafad307 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -1,11 +1,7 @@
# GitLab Developers Guide to Working with Gitaly
[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab CE/EE,
-Workhorse and GitLab-Shell. All Rugged operations in GitLab CE/EE are currently being phased out to
-be replaced by Gitaly API calls.
-
-Visit the [Gitaly Migration Board](https://gitlab.com/gitlab-org/gitaly/boards/331341) for current
-status of the migration.
+Workhorse and GitLab-Shell.
## Developing new Git features
@@ -52,57 +48,6 @@ comfortable writing Go code.
There is documentation for this approach in [the Gitaly
repo](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md).
-## Modifying existing Git features
-
-If you modify existing Git features in `lib/gitlab/git` you need to make
-sure the changes also work in Gitaly. Because we are still in the
-migration process there are a number of subtle pitfalls. Features that
-have been migrated have dual implementations (Gitaly and local). The
-Gitaly implementation may or may not use a vendored (and therefore
-possibly outdated) copy of the local implementation in `lib/gitlab/git`.
-
-To avoid unexpected problems and conflicts, all changes to
-`lib/gitlab/git` need to be approved by a member of the Gitaly team.
-
-For the time being, while the Gitaly migration is still in progress,
-there should be no Enterprise Edition-only Git code in
-`lib/gitlab/git`. Also no mixins.
-
-## Feature Flags
-
-Gitaly makes heavy use of [feature flags](feature_flags.md).
-
-Each Rugged-to-Gitaly migration goes through a [series of phases](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/MIGRATION_PROCESS.md):
-
-* **Opt-In**: by default the Rugged implementation is used.
- * Production instances can choose to enable the Gitaly endpoint by enabling the feature flag.
- * For testing purposes, you may wish to enable all feature flags by default. This can be done by exporting the following
- environment variable: `GITALY_FEATURE_DEFAULT_ON=1`.
- * On developer instances (ie, when `Rails.env.development?` is true), the Gitaly endpoint
- is enabled by default, but can be _disabled_ using feature flags.
-* **Opt-Out**: by default, the Gitaly endpoint is used, but the feature can be explicitly disabled using the feature flag.
-* **Mandatory**: The migration is complete and cannot be disabled. The old codepath is removed.
-
-### Enabling and Disabling Feature
-
-In the Rails console, type:
-
-```ruby
-Feature.enable(:gitaly_feature_name)
-Feature.disable(:gitaly_feature_name)
-```
-
-Where `gitaly_feature_name` is the name of the Gitaly feature. This can be determined by finding the appropriate
-`gitaly_migrate` code block, for example:
-
-```ruby
-gitaly_migrate(:tag_names) do
-...
-end
-```
-
-Since Gitaly features are always prefixed with `gitaly_`, the name of the feature flag in this case would be `gitaly_tag_names`.
-
## Gitaly-Related Test Failures
If your test-suite is failing with Gitaly issues, as a first step, try running:
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index a14c0752366..7761f65d78a 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -69,7 +69,7 @@ The easiest way to check if a method has been instrumented is to check its
source location. For example:
```ruby
-method = Rugged::TagCollection.instance_method(:[])
+method = Banzai::Renderer.method(:render)
method.source_location
```
@@ -82,7 +82,7 @@ method (along with its source location), this is easier than running the above
Ruby code. In case of the above snippet you'd run the following:
```
-$ Rugged::TagCollection#[]
+$ Banzai::Renderer.render
```
This will print out something along the lines of:
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 681dc8ff20d..b5a9e469965 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -440,6 +440,30 @@ no longer be valid as soon as the deployment job finishes. This means that
Kubernetes can run the application, but in case it should be restarted or
executed somewhere else, it cannot be accessed again.
+> [Introduced][ce-21955] in GitLab 11.4
+
+Database initialization and migrations for PostgreSQL can be configured to run
+within the application pod by setting the project variables `DB_INITIALIZE` and
+`DB_MIGRATE` respectively.
+
+If present, `DB_INITIALIZE` will be run as a shell command within an application pod as a helm
+post-install hook. Note that this means that if any deploy succeeds,
+`DB_INITIALIZE` will not be processed thereafter.
+
+If present, `DB_MIGRATE` will be run as a shell command within an application pod as
+a helm pre-upgrade hook.
+
+For example, in a Rails application:
+
+* `DB_INITIALIZE` can be set to `cd /app && RAILS_ENV=production
+ bin/setup`
+* `DB_MIGRATE` can be set to `cd /app && RAILS_ENV=production bin/update`
+
+NOTE: **Note:**
+The `/app` path is the directory of your project inside the docker image
+as [configured by
+Herokuish](https://github.com/gliderlabs/herokuish#paths)
+
> [Introduced][ce-19507] in GitLab 11.0.
For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md###gitlab-deploy-token)
@@ -581,6 +605,8 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.|
| `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).|
+| `DB_INITIALIZE` | From GitLab 11.4, this variable can be used to specify the command to run to initialize the application's PostgreSQL database. It runs inside the application pod. |
+| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). |
| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. |
@@ -834,4 +860,5 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/
[postgresql]: https://www.postgresql.org/
[Auto DevOps template]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
[ee]: https://about.gitlab.com/pricing/
+[ce-21955]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21955
[ce-19507]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19507
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index ba8b79b911b..30f49fefc41 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -1,123 +1,129 @@
-# GitLab JIRA integration
+# GitLab Jira integration
-GitLab can be configured to interact with [JIRA], a project management platform.
+GitLab Issues are a powerful tool for discussing ideas and planning and tracking work.
+However, many organizations have been using Jira for these purposes and have
+extensive data and business processes built into it.
-Once your GitLab project is connected to JIRA, you can reference and close the
-issues in JIRA directly from GitLab.
+While you can always migrate content and process from Jira to GitLab Issues,
+you can also opt to continue using Jira and use it together with GitLab through
+our integration.
-For a use case, check out this article of [How and why to integrate GitLab with
-JIRA](https://www.programmableweb.com/news/how-and-why-to-integrate-gitlab-jira/how-to/2017/04/25).
+Once you integrate your GitLab project with your Jira instance, you can automatically
+detect and cross-reference activity between the GitLab project and any of your projects
+in Jira. This includes the ability to close or transition Jira issues when the work
+is completed in GitLab.
+
+Here's how the integration responds when you take the following actions in GitLab:
+
+- **Mention a Jira issue ID** in a commit message or MR (merge request).
+ - GitLab hyperlinks to the Jira issue.
+ - The Jira issue adds an issue link to the commit/MR in GitLab.
+ - The Jira issue adds a comment reflecting the comment made in GitLab, the comment author, and a link to the commit/MR in GitLab.
+- **Mention that a commit or MR 'closes', 'resolves', or 'fixes' a Jira issue ID**. When the commit is made on master or the change is merged to master:
+ - GitLab's merge request page displays a note that it "Closed" the Jira issue, with a link to the issue. (Note: Before the merge, an MR will display that it "Closes" the Jira issue.)
+ - The Jira issue shows the activity and the Jira issue is closed, or otherwise transitioned.
+
+You can also use [Jira's Smart Commits](https://confluence.atlassian.com/fisheye/using-smart-commits-298976812.html)
+directly from GitLab, as covered in the article
+[How and why to integrate GitLab with Jira](https://www.programmableweb.com/news/how-and-why-to-integrate-gitlab-Jira/how-to/2017/04/25).
## Configuration
-Each GitLab project can be configured to connect to a different JIRA instance. That
-means one GitLab project maps to _all_ JIRA projects in that JIRA instance once
-the configuration is set up. Therefore, you don't have to explicitly associate
-one GitLab project to any JIRA project. Once the configuration is set up, any JIRA
-projects in the JIRA instance are already mapped to the GitLab project.
+Each GitLab project can be configured to connect to an entire Jira instance. That
+means one GitLab project can interact with _all_ Jira projects in that instance, once
+configured. Therefore, you will not have to explicitly associate
+a GitLab project with any single Jira project.
-If you have one JIRA instance you can pre-fill the settings page with a default
-template, see the [Services Templates][services-templates] docs.
+If you have one Jira instance, you can pre-fill the settings page with a default
+template. See the [Services Templates][services-templates] docs.
-Configuration happens via user name and password. Connecting to a JIRA server
+Configuration happens via user name and password. Connecting to a Jira server
via CAS is not possible.
-In order to enable the JIRA service in GitLab, you need to first configure the
-project in JIRA and then enter the correct values in GitLab.
+In order to enable the Jira service in GitLab, you need to first configure the
+project in Jira and then enter the correct values in GitLab.
-### Configuring JIRA
+### Configuring Jira
-We need to create a user in JIRA which will have access to all projects that
-need to integrate with GitLab. Login to your JIRA instance as admin and under
-Administration go to User Management and create a new user.
+We need to create a user in Jira which will have access to all projects that
+need to integrate with GitLab. Login to your Jira instance as admin and under
+*Administration*, go to *User Management* and create a new user.
-As an example, we'll create a user named `gitlab` and add it to `JIRA-developers`
+As an example, we'll create a user named `gitlab` and add it to the `Jira-developers`
group.
-**It is important that the user `GitLab` has write-access to projects in JIRA**
+**It is important that the user `gitlab` has 'write' access to projects in Jira**
We have split this stage in steps so it is easier to follow.
----
-
-1. Login to your JIRA instance as an administrator and under **Administration**
+1. Log in to your Jira instance as an administrator and under **Administration**
go to **User Management** to create a new user.
- ![JIRA user management link](img/jira_user_management_link.png)
-
- ---
+ ![Jira user management link](img/jira_user_management_link.png)
1. The next step is to create a new user (e.g., `gitlab`) who has write access
- to projects in JIRA. Enter the user's name and a _valid_ e-mail address
- since JIRA sends a verification e-mail to set up the password.
- _**Note:** JIRA creates the username automatically by using the e-mail
- prefix. You can change it later if you want._
+ to projects in Jira. Enter the user's name and a _valid_ e-mail address
+ since Jira sends a verification e-mail to set up the password.
+ _**Note:** Jira creates the username automatically by using the e-mail
+ prefix. You can change it later, if needed. Our integration does not support SSO (such as SAML). You will need to create
+ an HTTP basic authentication password. You can do this by visiting the user
+ profile, looking up the username, and setting a password._
- ![JIRA create new user](img/jira_create_new_user.png)
-
- ---
+ ![Jira create new user](img/jira_create_new_user.png)
1. Now, let's create a `gitlab-developers` group which will have write access
- to projects in JIRA. Go to the **Groups** tab and select **Create group**.
-
- ![JIRA create new user](img/jira_create_new_group.png)
-
- ---
-
- Give it an optional description and hit **Create group**.
+ to projects in Jira. Go to the **Groups** tab and select **Create group**.
- ![jira create new group](img/jira_create_new_group_name.png)
+ ![Jira create new user](img/jira_create_new_group.png)
- ---
+ Give it an optional description and click **Create group**.
-1. Give the newly-created group write access by going to
- **Application access ➔ View configuration** and adding the `gitlab-developers`
- group to JIRA Core.
+ ![Jira create new group](img/jira_create_new_group_name.png)
- ![JIRA group access](img/jira_group_access.png)
+1. To give the newly-created group 'write' access, go to
+ **Application access ➔ View configuration** and add the `gitlab-developers`
+ group to Jira Core.
- ---
+ ![Jira group access](img/jira_group_access.png)
1. Add the `gitlab` user to the `gitlab-developers` group by going to
**Users ➔ GitLab user ➔ Add group** and selecting the `gitlab-developers`
- group from the dropdown menu. Notice that the group says _Access_ which is
- what we aim for.
+ group from the dropdown menu. Notice that the group says _Access_, which is
+ intended as part of this process.
- ![JIRA add user to group](img/jira_add_user_to_group.png)
+ ![Jira add user to group](img/jira_add_user_to_group.png)
- ---
-
-The JIRA configuration is over. Write down the new JIRA username and its
+The Jira configuration is complete. Write down the new Jira username and its
password as they will be needed when configuring GitLab in the next section.
### Configuring GitLab
> **Notes:**
-> - The currently supported JIRA versions are `v6.x` and `v7.x.`. GitLab 7.8 or
+> - The currently supported Jira versions are `v6.x` and `v7.x.`. GitLab 7.8 or
> higher is required.
-> - GitLab 8.14 introduced a new way to integrate with JIRA which greatly simplified
+> - GitLab 8.14 introduced a new way to integrate with Jira which greatly simplified
> the configuration options you have to enter. If you are using an older version,
> [follow this documentation][jira-repo-old-docs].
> - In order to support Oracle's Access Manager, GitLab will send additional cookies
> to enable Basic Auth. The cookie being added to each request is `OBBasicAuth` with
> a value of `fromDialog`.
-To enable JIRA integration in a project, navigate to the
+To enable Jira integration in a project, navigate to the
[Integrations page](project_services.md#accessing-the-project-services), click
-the **JIRA** service, and fill in the required details on the page as described
+the **Jira** service, and fill in the required details on the page as described
in the table below.
| Field | Description |
| ----- | ----------- |
-| `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
-| `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
-| `Username` | The user name created in [configuring JIRA step](#configuring-jira). Using the email address will cause `401 unauthorized`. |
-| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
-| `Transition ID` | This is the ID of a transition that moves issues to the desired state. It is possible to insert transition ids separated by `,` or `;` which means the issue will be moved to each state after another using the given order. **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
+| `Web URL` | The base URL to the Jira instance web interface which is being linked to this GitLab project. E.g., `https://Jira.example.com`. |
+| `Jira API URL` | The base URL to the Jira instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
+| `Username` | The user name created in [configuring Jira step](#configuring-jira). Using the email address will cause `401 unauthorized`. |
+| `Password` |The password of the user created in [configuring Jira step](#configuring-jira). |
+| `Transition ID` | This is the ID of a transition that moves issues to the desired state. It is possible to insert transition ids separated by `,` or `;` which means the issue will be moved to each state after another using the given order. **Closing Jira issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
-### Getting a transition ID
+### Obtaining a transition ID
-In the most recent JIRA UI, you can no longer see transition IDs in the workflow
+In the most recent Jira user interface, you can no longer see transition IDs in the workflow
administration UI. You can get the ID you need in either of the following ways:
1. By using the API, with a request like `https://yourcompany.atlassian.net/rest/api/2/issue/ISSUE-123/transitions`
@@ -129,25 +135,23 @@ Note that the transition ID may vary between workflows (e.g., bug vs. story),
even if the status you are changing to is the same.
After saving the configuration, your GitLab project will be able to interact
-with all JIRA projects in your JIRA instance and you'll see the JIRA link on the GitLab project pages that takes you to the appropriate JIRA project.
-
-![JIRA service page](img/jira_service_page.png)
+with all Jira projects in your Jira instance and you'll see the Jira link on the GitLab project pages that takes you to the appropriate Jira project.
----
+![Jira service page](img/jira_service_page.png)
-## JIRA issues
+## Jira issues
-By now you should have [configured JIRA](#configuring-jira) and enabled the
-[JIRA service in GitLab](#configuring-gitlab). If everything is set up correctly
-you should be able to reference and close JIRA issues by just mentioning their
+By now you should have [configured Jira](#configuring-jira) and enabled the
+[Jira service in GitLab](#configuring-gitlab). If everything is set up correctly
+you should be able to reference and close Jira issues by just mentioning their
ID in GitLab commits and merge requests.
-### Referencing JIRA Issues
+### Referencing Jira Issues
-When GitLab project has JIRA issue tracker configured and enabled, mentioning
-JIRA issue in GitLab will automatically add a comment in JIRA issue with the
+When GitLab project has Jira issue tracker configured and enabled, mentioning
+Jira issue in GitLab will automatically add a comment in Jira issue with the
link back to GitLab. This means that in comments in merge requests and commits
-referencing an issue, e.g., `PROJECT-7`, will add a comment in JIRA issue in the
+referencing an issue, e.g., `PROJECT-7`, will add a comment in Jira issue in the
format:
```
@@ -156,21 +160,19 @@ ENTITY_TITLE
```
* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
-* `LINK_TO_THE_COMMENT` Link to the origin of mention with a name of the entity where JIRA issue was mentioned.
+* `LINK_TO_THE_COMMENT` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
* `RESOURCE_NAME` Kind of resource which referenced the issue. Can be a commit or merge request.
* `PROJECT_NAME` GitLab project name.
* `ENTITY_TITLE` Merge request title or commit message first line.
-![example of mentioning or closing the JIRA issue](img/jira_issue_reference.png)
+![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
----
+### Closing Jira Issues
-### Closing JIRA Issues
-
-JIRA issues can be closed directly from GitLab by using trigger words in
+Jira issues can be closed directly from GitLab by using trigger words in
commits and merge requests. When a commit which contains the trigger word
-followed by the JIRA issue ID in the commit message is pushed, GitLab will
-add a comment in the mentioned JIRA issue and immediately close it (provided
+followed by the Jira issue ID in the commit message is pushed, GitLab will
+add a comment in the mentioned Jira issue and immediately close it (provided
the transition ID was set up correctly).
There are currently three trigger words, and you can use either one to achieve
@@ -180,66 +182,66 @@ the same goal:
- `Closes PROJECT-1`
- `Fixes PROJECT-1`
-where `PROJECT-1` is the issue ID of the JIRA project.
+where `PROJECT-1` is the issue ID of the Jira project.
> **Notes:**
> - Only commits and merges into the project's default branch (usually **master**) will
> close an issue in Jira. You can change your projects default branch under
> [project settings](img/jira_project_settings.png).
-> - The JIRA issue will not be transitioned if it has a resolution.
+> - The Jira issue will not be transitioned if it has a resolution.
-### JIRA issue closing example
+### Jira issue closing example
Let's consider the following example:
-1. For the project named `PROJECT` in JIRA, we implemented a new feature
+1. For the project named `PROJECT` in Jira, we implemented a new feature
and created a merge request in GitLab.
-1. This feature was requested in JIRA issue `PROJECT-7` and the merge request
+1. This feature was requested in Jira issue `PROJECT-7` and the merge request
in GitLab contains the improvement
1. In the merge request description we use the issue closing trigger
`Closes PROJECT-7`.
-1. Once the merge request is merged, the JIRA issue will be automatically closed
+1. Once the merge request is merged, the Jira issue will be automatically closed
with a comment and an associated link to the commit that resolved the issue.
----
-
-In the following screenshot you can see what the link references to the JIRA
+In the following screenshot you can see what the link references to the Jira
issue look like.
-![A Git commit that causes the JIRA issue to be closed](img/jira_merge_request_close.png)
+![A Git commit that causes the Jira issue to be closed](img/jira_merge_request_close.png)
----
-
-Once this merge request is merged, the JIRA issue will be automatically closed
+Once this merge request is merged, the Jira issue will be automatically closed
with a link to the commit that resolved the issue.
-![The GitLab integration closes JIRA issue](img/jira_service_close_issue.png)
-
----
+![The GitLab integration closes Jira issue](img/jira_service_close_issue.png)
-![The GitLab integration creates a comment and a link on JIRA issue.](img/jira_service_close_comment.png)
+![The GitLab integration creates a comment and a link on Jira issue.](img/jira_service_close_comment.png)
## Troubleshooting
-If things don't work as expected that's usually because you have configured
-incorrectly the JIRA-GitLab integration.
+If these features do not work as expected, it is likely due to a problem with the way the integration settings were configured.
+
+### GitLab is unable to comment on a Jira issue
+
+Make sure that the Jira user you set up for the integration has the
+correct access permission to post comments on a Jira issue and also to transition
+the issue, if you'd like GitLab to also be able to do so.
+Jira issue references and update comments will not work if the GitLab issue tracker is disabled.
-### GitLab is unable to comment on a ticket
+### GitLab is unable to close a Jira issue
-Make sure that the user you set up for GitLab to communicate with JIRA has the
-correct access permission to post comments on a ticket and to also transition
-the ticket, if you'd like GitLab to also take care of closing them.
-JIRA issue references and update comments will not work if the GitLab issue tracker is disabled.
+Make sure the `Transition ID` you set within the Jira settings matches the one
+your project needs to close an issue.
-### GitLab is unable to close a ticket
+Make sure that the Jira issue is not already marked as resolved; that is,
+the Jira issue resolution field is not set. (It should not be struck through in
+Jira lists.)
-Make sure the `Transition ID` you set within the JIRA settings matches the one
-your project needs to close a ticket.
+### CAPTCHA
-Make sure that the JIRA issue is not already marked as resolved, in other words that
-the JIRA issue resolution field is not set. (It should not be struck through in
-JIRA lists.)
+CAPTCHA may be triggered after several consecutive failed login attempts
+which may lead to a `401 unauthorized` error when testing your Jira integration.
+If CAPTCHA has been triggered, you will not be able to use Jira's REST API to
+authenticate with the Jira site. You will need to log in to your Jira instance
+and complete the CAPTCHA.
[services-templates]: services_templates.md
[jira-repo-old-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/project_services/jira.md
-[jira]: https://www.atlassian.com/software/jira
diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
new file mode 100644
index 00000000000..40913718385
--- /dev/null
+++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index f01da06fa6e..66ac7740157 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -7,7 +7,7 @@ have been marked a **Work In Progress**.
![Blocked Accept Button](img/wip_blocked_accept_button.png)
To mark a merge request a Work In Progress, simply start its title with `[WIP]`
-or `WIP:`. As an alternative, you're also able to do it by sending a commit
+or `WIP:`. As an alternative, you're also able to do it by sending a commit
with its title starting with `wip` or `WIP` to the merge request's source branch.
![Mark as WIP](img/wip_mark_as_wip.png)
@@ -15,4 +15,11 @@ with its title starting with `wip` or `WIP` to the merge request's source branch
To allow a Work In Progress merge request to be accepted again when it's ready,
simply remove the `WIP` prefix.
-![Unark as WIP](img/wip_unmark_as_wip.png)
+![Unmark as WIP](img/wip_unmark_as_wip.png)
+
+## Filtering merge requests with WIP Status
+
+To filter merge requests with the `WIP` status, you can type `wip`
+and select the value for your filter from the merge request search input.
+
+![Filter WIP MRs](img/filter_wip_merge_requests.png)
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 9adbafee255..d02e921fa84 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -78,10 +78,10 @@ git clone git@gitlab.example.com:group/project.git
```
If you already cloned the repository and you want to get the latest LFS object
-that are on the remote repository, eg. from branch `master`:
+that are on the remote repository, eg. for a branch from origin:
```bash
-git lfs fetch master
+git lfs fetch origin master
```
## File Locking
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index f0db1318146..ff927d1aa3c 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -110,6 +110,9 @@ module API
if result[:status] == :success
commit_detail = user_project.repository.commit(result[:result])
+
+ Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden
+
present commit_detail, with: Entities::CommitDetail
else
render_api_error!(result[:message], 400)
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 764905ca00f..440d94ae186 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -33,7 +33,6 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def find_merge_requests(args = {})
args = declared_params.merge(args)
-
args[:milestone_title] = args.delete(:milestone)
args[:label_name] = args.delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope]
@@ -97,6 +96,7 @@ module API
optional :source_branch, type: String, desc: 'Return merge requests with the given source branch'
optional :target_branch, type: String, desc: 'Return merge requests with the given target branch'
optional :search, type: String, desc: 'Search merge requests for text present in the title or description'
+ optional :wip, type: String, values: %w[yes no], desc: 'Search merge requests for WIP in the title'
use :pagination
end
end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 9d01bcb7ca6..a96595b33a5 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -598,26 +598,59 @@ rollout 100%:
secret_name=''
fi
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="$CI_APPLICATION_REPOSITORY" \
- --set image.tag="$CI_APPLICATION_TAG" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
+ if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
+ helm upgrade --install \
+ --wait \
+ --set service.enabled="$service_enabled" \
+ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
+ --set image.repository="$CI_APPLICATION_REPOSITORY" \
+ --set image.tag="$CI_APPLICATION_TAG" \
+ --set image.pullPolicy=IfNotPresent \
+ --set image.secrets[0].name="$secret_name" \
+ --set application.track="$track" \
+ --set application.database_url="$DATABASE_URL" \
+ --set service.url="$CI_ENVIRONMENT_URL" \
+ --set replicaCount="$replicas" \
+ --set postgresql.enabled="$postgres_enabled" \
+ --set postgresql.nameOverride="postgres" \
+ --set postgresql.postgresUser="$POSTGRES_USER" \
+ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
+ --set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set application.initializeCommand="$DB_INITIALIZE" \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+
+ helm upgrade --reuse-values \
+ --wait \
+ --set application.initializeCommand="" \
+ --set application.migrateCommand="$DB_MIGRATE" \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+ else
+ helm upgrade --install \
+ --wait \
+ --set service.enabled="$service_enabled" \
+ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
+ --set image.repository="$CI_APPLICATION_REPOSITORY" \
+ --set image.tag="$CI_APPLICATION_TAG" \
+ --set image.pullPolicy=IfNotPresent \
+ --set image.secrets[0].name="$secret_name" \
+ --set application.track="$track" \
+ --set application.database_url="$DATABASE_URL" \
+ --set service.url="$CI_ENVIRONMENT_URL" \
+ --set replicaCount="$replicas" \
+ --set postgresql.enabled="$postgres_enabled" \
+ --set postgresql.nameOverride="postgres" \
+ --set postgresql.postgresUser="$POSTGRES_USER" \
+ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
+ --set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set application.migrateCommand="$DB_MIGRATE" \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+ fi
kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/$name"
}
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 5b264868af0..74cdabfed9d 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -53,9 +53,6 @@ module Gitlab
# Already a commit?
return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
- # A rugged reference?
- commit_id = Gitlab::Git::Ref.dereference_object(commit_id)
-
# Some weird thing?
return nil unless commit_id.is_a?(String)
@@ -127,8 +124,6 @@ module Gitlab
# :topo, or any combination of them (in an array). Commit ordering types
# are documented here:
# http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant)
- #
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326
def find_all(repo, options = {})
repo.wrapped_gitaly_errors do
Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
@@ -328,7 +323,6 @@ module Gitlab
entry = @repository.gitaly_commit_client.tree_entry(id, path, 1)
return unless entry
- # To be compatible with the rugged format
entry = entry.to_h
entry.delete(:data)
entry[:name] = File.basename(path)
@@ -346,8 +340,8 @@ module Gitlab
subject: message_split[0] ? message_split[0].chomp.b : "",
body: raw_commit.message.b,
parent_ids: raw_commit.parent_ids,
- author: gitaly_commit_author_from_rugged(raw_commit.author),
- committer: gitaly_commit_author_from_rugged(raw_commit.committer)
+ author: gitaly_commit_author_from_raw(raw_commit.author),
+ committer: gitaly_commit_author_from_raw(raw_commit.committer)
)
end
@@ -381,7 +375,7 @@ module Gitlab
SERIALIZE_KEYS
end
- def gitaly_commit_author_from_rugged(author_or_committer)
+ def gitaly_commit_author_from_raw(author_or_committer)
Gitaly::CommitAuthor.new(
name: author_or_committer[:name].b,
email: author_or_committer[:email].b,
diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb
index fa71a4e7ea7..31a280155bd 100644
--- a/lib/gitlab/git/ref.rb
+++ b/lib/gitlab/git/ref.rb
@@ -1,5 +1,3 @@
-# Gitaly note: JV: probably no RPC's here (just one interaction with Rugged).
-
module Gitlab
module Git
class Ref
@@ -26,13 +24,6 @@ module Gitlab
str.gsub(%r{\Arefs/heads/}, '')
end
- # Gitaly: this method will probably be migrated indirectly via its call sites.
- def self.dereference_object(object)
- object = object.target while object.is_a?(Rugged::Tag::Annotation)
-
- object
- end
-
def initialize(repository, name, target, dereferenced_target)
@name = Gitlab::Git.ref_name(name)
@dereferenced_target = dereferenced_target
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 45d42c7078f..7732049b69b 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -9,14 +9,6 @@ module Gitlab
include Gitlab::EncodingHelper
include Gitlab::Utils::StrongMemoize
- ALLOWED_OBJECT_DIRECTORIES_VARIABLES = %w[
- GIT_OBJECT_DIRECTORY
- GIT_ALTERNATE_OBJECT_DIRECTORIES
- ].freeze
- ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES = %w[
- GIT_OBJECT_DIRECTORY_RELATIVE
- GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
- ].freeze
SEARCH_CONTEXT_LINES = 3
REV_LIST_COMMIT_LIMIT = 2_000
# In https://gitlab.com/gitlab-org/gitaly/merge_requests/698
@@ -104,15 +96,6 @@ module Gitlab
raise Gitlab::Git::CommandError.new(e.message)
end
- # This method will be removed when Gitaly reaches v1.1.
- def rugged
- circuit_breaker.perform do
- Rugged::Repository.new(path, alternates: alternate_object_directories)
- end
- rescue Rugged::RepositoryError, Rugged::OSError
- raise NoRepository.new('no repository for such path')
- end
-
def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
end
@@ -638,20 +621,6 @@ module Gitlab
end
end
- AUTOCRLF_VALUES = {
- "true" => true,
- "false" => false,
- "input" => :input
- }.freeze
-
- def autocrlf
- AUTOCRLF_VALUES[rugged.config['core.autocrlf']]
- end
-
- def autocrlf=(value)
- rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
- end
-
# Returns result like "git ls-files" , recursive and full file path
#
# Ex.
@@ -1024,14 +993,6 @@ module Gitlab
found_module && found_module['url']
end
- def alternate_object_directories
- relative_object_directories.map { |d| File.join(path, d) }
- end
-
- def relative_object_directories
- Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
- end
-
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index d2dc4f2e688..072019dfb0a 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -1,9 +1,16 @@
+# We only need Gollum::Page so let's not load all of gollum-lib.
+require 'gollum-lib/pagination'
+require 'gollum-lib/wiki'
+require 'gollum-lib/page'
+
module Gitlab
module Git
class Wiki
DuplicatePageError = Class.new(StandardError)
OperationError = Class.new(StandardError)
+ DEFAULT_PAGINATION = Kaminari.config.default_per_page
+
CommitDetails = Struct.new(:user_id, :username, :name, :email, :message) do
def to_h
{ user_id: user_id, username: username, name: name, email: email, message: message }
@@ -74,7 +81,7 @@ module Gitlab
# Gitaly uses gollum-lib to get the versions. Gollum defaults to 20
# per page, but also fetches 20 if `limit` or `per_page` < 20.
# Slicing returns an array with the expected number of items.
- slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page
+ slice_bound = options[:limit] || options[:per_page] || DEFAULT_PAGINATION
versions[0..slice_bound]
end
@@ -104,26 +111,6 @@ module Gitlab
private
- def new_page(gollum_page)
- Gitlab::Git::WikiPage.new(gollum_page, new_version(gollum_page, gollum_page.version.id))
- end
-
- def new_version(gollum_page, commit_id)
- Gitlab::Git::WikiPageVersion.new(version(commit_id), gollum_page&.format)
- end
-
- def version(commit_id)
- commit_find_proc = -> { Gitlab::Git::Commit.find(@repository, commit_id) }
-
- Gitlab::SafeRequestStore.fetch([:wiki_version_commit, commit_id]) { commit_find_proc.call }
- end
-
- def assert_type!(object, klass)
- unless object.is_a?(klass)
- raise ArgumentError, "expected a #{klass}, got #{object.inspect}"
- end
- end
-
def gitaly_wiki_client
@gitaly_wiki_client ||= Gitlab::GitalyClient::WikiService.new(@repository)
end
diff --git a/lib/gitlab/git/wiki_file.rb b/lib/gitlab/git/wiki_file.rb
index 84335aca4bc..64313bb04e8 100644
--- a/lib/gitlab/git/wiki_file.rb
+++ b/lib/gitlab/git/wiki_file.rb
@@ -3,17 +3,12 @@ module Gitlab
class WikiFile
attr_reader :mime_type, :raw_data, :name, :path
- # This class is meant to be serializable so that it can be constructed
- # by Gitaly and sent over the network to GitLab.
- #
- # Because Gollum::File is not serializable we must get all the data from
- # 'gollum_file' during initialization, and NOT store it in an instance
- # variable.
- def initialize(gollum_file)
- @mime_type = gollum_file.mime_type
- @raw_data = gollum_file.raw_data
- @name = gollum_file.name
- @path = gollum_file.path
+ # This class wraps Gitlab::GitalyClient::WikiFile
+ def initialize(gitaly_file)
+ @mime_type = gitaly_file.mime_type
+ @raw_data = gitaly_file.raw_data
+ @name = gitaly_file.name
+ @path = gitaly_file.path
end
end
end
diff --git a/lib/gitlab/git/wiki_page.rb b/lib/gitlab/git/wiki_page.rb
index 669ae11a423..c4087c9ebdc 100644
--- a/lib/gitlab/git/wiki_page.rb
+++ b/lib/gitlab/git/wiki_page.rb
@@ -3,25 +3,15 @@ module Gitlab
class WikiPage
attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical, :formatted_data
- # This class is meant to be serializable so that it can be constructed
- # by Gitaly and sent over the network to GitLab.
- #
- # Because Gollum::Page is not serializable we must get all the data from
- # 'gollum_page' during initialization, and NOT store it in an instance
- # variable.
- #
- # Note that 'version' is a WikiPageVersion instance which it itself
- # serializable. That means it's OK to store 'version' in an instance
- # variable.
- def initialize(gollum_page, version)
- @url_path = gollum_page.url_path
- @title = gollum_page.title
- @format = gollum_page.format
- @path = gollum_page.path
- @raw_data = gollum_page.raw_data
- @name = gollum_page.name
- @historical = gollum_page.historical?
- @formatted_data = gollum_page.formatted_data if gollum_page.is_a?(Gollum::Page)
+ # This class abstracts away Gitlab::GitalyClient::WikiPage
+ def initialize(gitaly_page, version)
+ @url_path = gitaly_page.url_path
+ @title = gitaly_page.title
+ @format = gitaly_page.format
+ @path = gitaly_page.path
+ @raw_data = gitaly_page.raw_data
+ @name = gitaly_page.name
+ @historical = gitaly_page.historical?
@version = version
end
diff --git a/lib/gitlab/git/wiki_page_version.rb b/lib/gitlab/git/wiki_page_version.rb
index 55f1afedcab..d5e7e70fd31 100644
--- a/lib/gitlab/git/wiki_page_version.rb
+++ b/lib/gitlab/git/wiki_page_version.rb
@@ -3,11 +3,6 @@ module Gitlab
class WikiPageVersion
attr_reader :commit, :format
- # This class is meant to be serializable so that it can be constructed
- # by Gitaly and sent over the network to GitLab.
- #
- # Both 'commit' (a Gitlab::Git::Commit) and 'format' (a string) are
- # serializable.
def initialize(commit, format)
@commit = commit
@format = format
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 75be7d1f5a0..7c2c228ad01 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -110,7 +110,7 @@ module Gitlab
repository: @gitaly_repo,
page_path: encode_binary(page_path),
page: options[:page] || 1,
- per_page: options[:per_page] || Gollum::Page.per_page
+ per_page: options[:per_page] || Gitlab::Git::Wiki::DEFAULT_PAGINATION
)
stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request, timeout: GitalyClient.medium_timeout)
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index f7d8ee571cd..5097c3253c9 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -10,6 +10,7 @@ module Gitlab
.merge(features_usage_data)
.merge(components_usage_data)
.merge(cycle_analytics_usage_data)
+ .merge(usage_counters)
end
def to_json(force_refresh: false)
@@ -106,6 +107,12 @@ module Gitlab
}
end
+ def usage_counters
+ {
+ web_ide_commits: Gitlab::WebIdeCommitsCounter.total_count
+ }
+ end
+
def components_usage_data
{
gitlab_pages: { enabled: Gitlab.config.pages.enabled, version: Gitlab::Pages::VERSION },
diff --git a/lib/gitlab/web_ide_commits_counter.rb b/lib/gitlab/web_ide_commits_counter.rb
new file mode 100644
index 00000000000..1cd9b5295b9
--- /dev/null
+++ b/lib/gitlab/web_ide_commits_counter.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module WebIdeCommitsCounter
+ WEB_IDE_COMMITS_KEY = "WEB_IDE_COMMITS_COUNT".freeze
+
+ class << self
+ def increment
+ Gitlab::Redis::SharedState.with { |redis| redis.incr(WEB_IDE_COMMITS_KEY) }
+ end
+
+ def total_count
+ Gitlab::Redis::SharedState.with { |redis| redis.get(WEB_IDE_COMMITS_KEY).to_i }
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cc11577b624..5591b0b8bb2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19,6 +19,11 @@ msgstr ""
msgid " Status"
msgstr ""
+msgid "%d addition"
+msgid_plural "%d additions"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d changed file"
msgid_plural "%d changed files"
msgstr[0] ""
@@ -34,6 +39,11 @@ msgid_plural "%d commits behind"
msgstr[0] ""
msgstr[1] ""
+msgid "%d deleted"
+msgid_plural "%d deletions"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
@@ -598,7 +608,7 @@ msgstr ""
msgid "Application"
msgstr ""
-msgid "Application Id"
+msgid "Application ID"
msgstr ""
msgid "Application: %{name}"
@@ -1101,9 +1111,6 @@ msgstr ""
msgid "Callback URL"
msgstr ""
-msgid "Callback url"
-msgstr ""
-
msgid "Can't find HEAD commit for this branch"
msgstr ""
@@ -1284,6 +1291,9 @@ msgstr ""
msgid "CircuitBreakerApiLink|circuitbreaker api"
msgstr ""
+msgid "Clear search"
+msgstr ""
+
msgid "Clear search input"
msgstr ""
@@ -1930,6 +1940,9 @@ msgstr ""
msgid "Copy HTTPS clone URL"
msgstr ""
+msgid "Copy ID to clipboard"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -1951,6 +1964,9 @@ msgstr ""
msgid "Copy reference to clipboard"
msgstr ""
+msgid "Copy secret to clipboard"
+msgstr ""
+
msgid "Copy to clipboard"
msgstr ""
@@ -3735,6 +3751,12 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Filter files"
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
msgid "Merged"
msgstr ""
@@ -3995,9 +4017,6 @@ msgstr ""
msgid "No file chosen"
msgstr ""
-msgid "No files found"
-msgstr ""
-
msgid "No files found."
msgstr ""
@@ -4291,9 +4310,6 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}"
-msgstr ""
-
msgid "Pipeline Health"
msgstr ""
@@ -5318,7 +5334,7 @@ msgstr ""
msgid "Seconds to wait for a storage access attempt"
msgstr ""
-msgid "Secret:"
+msgid "Secret"
msgstr ""
msgid "Select"
@@ -6090,7 +6106,7 @@ msgstr ""
msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}."
msgstr ""
-msgid "This job is creating a deployment to %{environmentLink} and will overwrite the last %{deploymentLink}."
+msgid "This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}."
msgstr ""
msgid "This job is creating a deployment to %{environmentLink}."
@@ -6394,6 +6410,9 @@ msgstr ""
msgid "Toggle discussion"
msgstr ""
+msgid "Toggle file browser"
+msgstr ""
+
msgid "Toggle navigation"
msgstr ""
@@ -7017,6 +7036,9 @@ msgstr ""
msgid "for this project"
msgstr ""
+msgid "from"
+msgstr ""
+
msgid "here"
msgstr ""
@@ -7032,6 +7054,9 @@ msgstr ""
msgid "issue boards"
msgstr ""
+msgid "latest deployment"
+msgstr ""
+
msgid "latest version"
msgstr ""
diff --git a/scripts/lint-rugged b/scripts/lint-rugged
index d0c2c544c47..22e3e1f1505 100755
--- a/scripts/lint-rugged
+++ b/scripts/lint-rugged
@@ -1,21 +1,9 @@
#!/usr/bin/env ruby
ALLOWED = [
- # Can be fixed once Rugged is no longer used in production. Doesn't make Rugged calls.
- 'config/initializers/8_metrics.rb',
-
- # Can be deleted once wiki's are fully (mandatory) migrated
- 'config/initializers/gollum.rb',
-
- # Needs to be migrated, https://gitlab.com/gitlab-org/gitaly/issues/953
+ # Needed to handle repositories that are not in any storage
'lib/gitlab/bare_repository_import/repository.rb',
- # Needs to be migrated, https://gitlab.com/gitlab-org/gitaly/issues/954
- 'lib/tasks/gitlab/cleanup.rake',
-
- # The only place where Rugged code is still allowed in production
- 'lib/gitlab/git/',
-
# Needed to avoid using the git binary to validate a branch name
'lib/gitlab/git_ref_validator.rb'
].freeze
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index f979d2f6090..a4904272706 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'admin manage applications' do
check :doorkeeper_application_trusted
click_on 'Submit'
expect(page).to have_content('Application: test')
- expect(page).to have_content('Application Id')
+ expect(page).to have_content('Application ID')
expect(page).to have_content('Secret')
expect(page).to have_content('Trusted Y')
@@ -28,7 +28,7 @@ RSpec.describe 'admin manage applications' do
click_on 'Submit'
expect(page).to have_content('test_changed')
- expect(page).to have_content('Application Id')
+ expect(page).to have_content('Application ID')
expect(page).to have_content('Secret')
expect(page).to have_content('Trusted N')
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index b99c5a7f4e3..0e296ab2109 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -15,6 +15,7 @@ describe 'Dropdown hint', :js do
before do
project.add_maintainer(user)
create(:issue, project: project)
+ create(:merge_request, source_project: project, target_project: project)
end
context 'when user not logged in' do
@@ -224,4 +225,21 @@ describe 'Dropdown hint', :js do
end
end
end
+
+ context 'merge request page' do
+ before do
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ filtered_search.click
+ end
+
+ it 'shows the WIP menu item and opens the WIP options dropdown' do
+ click_hint('wip')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-wip', visible: true)
+ expect_tokens([{ name: 'wip' }])
+ expect_filtered_search_input_empty
+ end
+ end
end
diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb
index cac8a5068ec..3b37ede8579 100644
--- a/spec/features/markdown/markdown_spec.rb
+++ b/spec/features/markdown/markdown_spec.rb
@@ -264,9 +264,9 @@ describe 'GitLab Markdown', :aggregate_failures do
@project_wiki = @feat.project_wiki
@project_wiki_page = @feat.project_wiki_page
- file = Gollum::File.new(@project_wiki.wiki)
- expect(file).to receive(:path).and_return('images/example.jpg')
- expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file)
+ path = 'images/example.jpg'
+ gitaly_wiki_file = Gitlab::GitalyClient::WikiFile.new(path: path)
+ expect(@project_wiki).to receive(:find_file).with(path).and_return(Gitlab::Git::WikiFile.new(gitaly_wiki_file))
allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' }
@html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki, page_slug: @project_wiki_page.slug })
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 441b080bee5..00cf368e8c9 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -28,7 +28,7 @@ describe 'User comments on a diff', :js do
click_button('Comment')
end
- page.within('.files > div:nth-child(3)') do
+ page.within('.diff-files-holder > div:nth-child(3)') do
expect(page).to have_content('Line is wrong')
find('.js-btn-vue-toggle-comments').click
@@ -49,7 +49,7 @@ describe 'User comments on a diff', :js do
wait_for_requests
- page.within('.files > div:nth-child(2) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(2) .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
@@ -63,7 +63,7 @@ describe 'User comments on a diff', :js do
wait_for_requests
# Hide the comment.
- page.within('.files > div:nth-child(3)') do
+ page.within('.diff-files-holder > div:nth-child(3)') do
find('.js-btn-vue-toggle-comments').click
expect(page).not_to have_content('Line is wrong')
@@ -71,21 +71,21 @@ describe 'User comments on a diff', :js do
# At this moment a user should see only one comment.
# The other one should be hidden.
- page.within('.files > div:nth-child(2) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(2) .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
# Show the comment.
- page.within('.files > div:nth-child(3)') do
+ page.within('.diff-files-holder > div:nth-child(3)') do
find('.js-btn-vue-toggle-comments').click
end
# Now both the comments should be shown.
- page.within('.files > div:nth-child(3) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(3) .note-body > .note-text') do
expect(page).to have_content('Line is wrong')
end
- page.within('.files > div:nth-child(2) .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(2) .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
@@ -95,11 +95,11 @@ describe 'User comments on a diff', :js do
wait_for_requests
- page.within('.files > div:nth-child(3) .parallel .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(3) .parallel .note-body > .note-text') do
expect(page).to have_content('Line is wrong')
end
- page.within('.files > div:nth-child(2) .parallel .note-body > .note-text') do
+ page.within('.diff-files-holder > div:nth-child(2) .parallel .note-body > .note-text') do
expect(page).to have_content('Line is correct')
end
end
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index 428eb414274..d3da8cc6752 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -81,6 +81,8 @@ describe 'Merge request > User sees avatars on diff notes', :js do
visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
+
+ find('.js-toggle-tree-list').click
end
it 'shows note avatar' do
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index f42b4dcbb47..92db4f44098 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -110,7 +110,8 @@ describe 'Merge request > User sees versions', :js do
diff_id: merge_request_diff3.id,
start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
)
- expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
+ expect(page).to have_content '4 changed files'
+ expect(page).to have_content '15 additions 6 deletions'
expect(page).to have_content 'Not all comments are displayed'
position = Gitlab::Diff::Position.new(
@@ -131,7 +132,8 @@ describe 'Merge request > User sees versions', :js do
end
it 'show diff between new and old version' do
- expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
+ expect(page).to have_content '4 changed files'
+ expect(page).to have_content '15 additions 6 deletions'
end
it 'returns to latest version when "Show latest version" button is clicked' do
@@ -158,7 +160,7 @@ describe 'Merge request > User sees versions', :js do
it 'has 0 chages between versions' do
page.within '.mr-version-compare-dropdown' do
- expect(find('.dropdown-toggle')).to have_content 'version 1'
+ expect(find('.dropdown-menu-toggle')).to have_content 'version 1'
end
page.within '.mr-version-dropdown' do
@@ -179,7 +181,7 @@ describe 'Merge request > User sees versions', :js do
it 'sets the compared versions to be the same' do
page.within '.mr-version-compare-dropdown' do
- expect(find('.dropdown-toggle')).to have_content 'version 2'
+ expect(find('.dropdown-menu-toggle')).to have_content 'version 2'
end
page.within '.mr-version-dropdown' do
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index b1bfe9e5de3..7f95a1282f9 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -10,6 +10,8 @@ describe 'User views diffs', :js do
visit(diffs_project_merge_request_path(project, merge_request))
wait_for_requests
+
+ find('.js-toggle-tree-list').click
end
shared_examples 'unfold diffs' do
diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb
index 387584fef62..34aaab240cc 100644
--- a/spec/features/profiles/user_manages_applications_spec.rb
+++ b/spec/features/profiles/user_manages_applications_spec.rb
@@ -16,7 +16,7 @@ describe 'User manages applications' do
click_on 'Save application'
expect(page).to have_content 'Application: test'
- expect(page).to have_content 'Application Id'
+ expect(page).to have_content 'Application ID'
expect(page).to have_content 'Secret'
click_on 'Edit'
@@ -26,7 +26,7 @@ describe 'User manages applications' do
click_on 'Save application'
expect(page).to have_content 'test_changed'
- expect(page).to have_content 'Application Id'
+ expect(page).to have_content 'Application ID'
expect(page).to have_content 'Secret'
visit applications_profile_path
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 4c5dda29fee..70e0879dd81 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -60,7 +60,7 @@ describe 'Environment' do
context 'with manual action' do
let(:action) do
create(:ci_build, :manual, pipeline: pipeline,
- name: 'deploy to production')
+ name: 'deploy to production', environment: environment.name)
end
context 'when user has ability to trigger deployment' do
@@ -73,12 +73,16 @@ describe 'Environment' do
expect(page).to have_link(action.name.humanize)
end
- it 'does allow to play manual action' do
+ it 'does allow to play manual action', :js do
expect(action).to be_manual
+ find('button.dropdown').click
+
expect { click_link(action.name.humanize) }
.not_to change { Ci::Pipeline.count }
+ wait_for_all_requests
+
expect(page).to have_content(action.name)
expect(action.reload).to be_pending
end
@@ -165,10 +169,10 @@ describe 'Environment' do
name: action.ref, project: project)
end
- it 'allows to stop environment' do
+ it 'allows to stop environment', :js do
click_button('Stop')
click_button('Stop environment') # confirm modal
-
+ wait_for_all_requests
expect(page).to have_content('close_app')
end
end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index a2b96514d64..f76f9ba7577 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -12,7 +12,7 @@ describe 'Import/Export - project export integration test', :js do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
- let(:sensitive_words) { %w[pass secret token key] }
+ let(:sensitive_words) { %w[pass secret token key encrypted] }
let(:safe_list) do
{
token: [ProjectHook, Ci::Trigger, CommitStatus],
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 2d791947ee9..fc7b78ac21f 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -16,7 +16,9 @@ describe 'User browses a job', :js do
visit(project_job_path(project, build))
end
- it 'erases the job log' do
+ it 'erases the job log', :js do
+ wait_for_requests
+
expect(page).to have_content("Job ##{build.id}")
expect(page).to have_css('#build-trace')
@@ -29,18 +31,17 @@ describe 'User browses a job', :js do
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
- page.within('.erased') do
- expect(page).to have_content('Job has been erased')
- end
+ expect(page).to have_content('Job has been erased')
end
context 'with a failed job' do
let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason' do
+ wait_for_all_requests
within('.builds-container') do
build_link = first('.build-job > a')
- expect(build_link['data-title']).to eq('test - failed - (unknown failure)')
+ expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)')
end
end
end
@@ -49,9 +50,10 @@ describe 'User browses a job', :js do
let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason and retried label' do
+ wait_for_all_requests
within('.builds-container') do
build_link = first('.build-job > a')
- expect(build_link['data-title']).to eq('test - failed - (unknown failure) (retried)')
+ expect(build_link['data-original-title']).to eq('test - failed - (unknown failure) (retried)')
end
end
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index d0bf4975b81..6213cabfaad 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -134,23 +134,25 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).to have_content pipeline.commit.title
end
- it 'shows active job' do
+ it 'shows active job', :js do
visit project_job_path(project, job)
+ wait_for_requests
expect(page).to have_selector('.build-job.active')
end
end
- context 'sidebar' do
+ context 'sidebar', :js do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') }
before do
visit project_job_path(project, job)
+ wait_for_requests
end
it 'renders escaped tooltip name' do
page.within('aside.right-sidebar') do
- expect(find('.active.build-job a')['data-title']).to eq('<img src="x"> - passed')
+ expect(find('.active.build-job a')['data-original-title']).to eq('&lt;img src=x onerror=alert(document.domain)&gt; - passed')
end
end
end
@@ -367,39 +369,167 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
- context 'when job starts environment' do
- let(:environment) { create(:environment, project: project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ context 'when job starts environment', :js do
+ let(:environment) { create(:environment, name: 'production', project: project) }
- context 'job is successfull and has deployment' do
- let(:deployment) { create(:deployment) }
- let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
+ context 'job is successful and has deployment' do
+ let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) }
- it 'shows a link for the job' do
- visit project_job_path(project, job)
+ before do
+ visit project_job_path(project, build)
+ wait_for_requests
+ # scroll to the top of the page first
+ execute_script "window.scrollTo(0,0)"
+ end
+ it 'shows a link for the job' do
expect(page).to have_link environment.name
end
+
+ it 'shows deployment message' do
+ expect(page).to have_content 'This job is the most recent deployment'
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
+ end
end
context 'job is complete and not successful' do
- let(:job) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
+ let(:build) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
- visit project_job_path(project, job)
+ visit project_job_path(project, build)
+ wait_for_requests
+ # scroll to the top of the page first
+ execute_script "window.scrollTo(0,0)"
expect(page).to have_link environment.name
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
- context 'job creates a new deployment' do
- let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
- let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, pipeline: pipeline) }
+ context 'deployment still not finished' do
+ let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
- visit project_job_path(project, job)
+ visit project_job_path(project, build)
+ wait_for_all_requests
+ # scroll to the top of the page first
+ execute_script "window.scrollTo(0,0)"
+
+ expect(page).to have_link environment.name
+ expect(page).to have_content 'This job is creating a deployment'
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
+ end
+ end
+ end
+
+ describe 'environment info in job view', :js do
+ before do
+ visit project_job_path(project, job)
+ wait_for_requests
+ # scroll to the top of the page first
+ execute_script "window.scrollTo(0,0)"
+ end
+
+ context 'job with outdated deployment' do
+ let(:job) { create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) }
+ let(:second_build) { create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) }
+ let(:environment) { create(:environment, name: 'staging', project: project) }
+ let!(:first_deployment) { create(:deployment, environment: environment, deployable: job) }
+ let!(:second_deployment) { create(:deployment, environment: environment, deployable: second_build) }
+
+ it 'shows deployment message' do
+ expected_text = 'This job is an out-of-date deployment ' \
+ "to staging. View the most recent deployment ##{second_deployment.iid}."
+
+ expect(page).to have_css('.environment-information', text: expected_text)
+ end
+
+ it 'renders a link to the most recent deployment' do
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
+ expect(find('.js-job-deployment-link')['href']).to include(second_deployment.deployable.project.path, second_deployment.deployable_id.to_s)
+ end
+ end
+
+ context 'job failed to deploy' do
+ let(:job) { create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline) }
+ let!(:environment) { create(:environment, name: 'staging', project: project) }
+
+ it 'shows deployment message' do
+ expected_text = 'The deployment of this job to staging did not succeed.'
+
+ expect(page).to have_css(
+ '.environment-information', text: expected_text)
+ end
+ end
+
+ context 'job will deploy' do
+ let(:job) { create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline) }
+
+ context 'when environment exists' do
+ let!(:environment) { create(:environment, name: 'staging', project: project) }
+
+ it 'shows deployment message' do
+ expected_text = 'This job is creating a deployment to staging'
+
+ expect(page).to have_css(
+ '.environment-information', text: expected_text)
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
+ end
+
+ context 'when it has deployment' do
+ let!(:deployment) { create(:deployment, environment: environment) }
+
+ it 'shows that deployment will be overwritten' do
+ expected_text = 'This job is creating a deployment to staging'
+
+ expect(page).to have_css(
+ '.environment-information', text: expected_text)
+ expect(page).to have_css(
+ '.environment-information', text: 'latest deployment')
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
+ end
+ end
+ end
+
+ context 'when environment does not exist' do
+ let!(:environment) { create(:environment, name: 'staging', project: project) }
+
+ it 'shows deployment message' do
+ expected_text = 'This job is creating a deployment to staging'
+
+ expect(page).to have_css(
+ '.environment-information', text: expected_text)
+ expect(page).not_to have_css(
+ '.environment-information', text: 'latest deployment')
+ expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
+ end
+ end
+ end
+
+ context 'job that failed to deploy and environment has not been created' do
+ let(:job) { create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline) }
+ let!(:environment) { create(:environment, name: 'staging', project: project) }
+
+ it 'shows deployment message' do
+ expected_text = 'The deployment of this job to staging did not succeed'
+
+ expect(page).to have_css(
+ '.environment-information', text: expected_text)
+ end
+ end
+
+ context 'job that will deploy and environment has not been created' do
+ let(:job) { create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline) }
+ let!(:environment) { create(:environment, name: 'staging', project: project) }
+
+ it 'shows deployment message' do
+ expected_text = 'This job is creating a deployment to staging'
- expect(page).to have_link('latest deployment')
+ expect(page).to have_css(
+ '.environment-information', text: expected_text)
+ expect(page).not_to have_css(
+ '.environment-information', text: 'latest deployment')
end
end
end
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 747406efc8b..9a4ce426e69 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -83,12 +83,13 @@ describe 'User views a wiki page' do
end
it 'shows a file stored in a page' do
- gollum_file_double = double('Gollum::File',
- mime_type: 'image/jpeg',
- name: 'images/image.jpg',
- path: 'images/image.jpg',
- raw_data: '')
- wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double)
+ raw_file = Gitlab::GitalyClient::WikiFile.new(
+ mime_type: 'image/jpeg',
+ name: 'images/image.jpg',
+ path: 'images/image.jpg',
+ raw_data: ''
+ )
+ wiki_file = Gitlab::Git::WikiFile.new(raw_file)
allow(wiki_file).to receive(:mime_type).and_return('image/jpeg')
allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file)
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 35d0eeda8f6..33d01697c75 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -16,12 +16,18 @@ describe MergeRequestsFinder do
p
end
let(:project4) { create(:project, :public, group: subgroup) }
+ let(:project5) { create(:project, :public, group: subgroup) }
+ let(:project6) { create(:project, :public, group: subgroup) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') }
- let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked') }
- let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) }
- let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4) }
+ let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') }
+ let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') }
+ let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') }
+ let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') }
+ let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') }
+ let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') }
+ let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') }
before do
project1.add_maintainer(user)
@@ -29,19 +35,21 @@ describe MergeRequestsFinder do
project3.add_developer(user)
project2.add_developer(user2)
project4.add_developer(user)
+ project5.add_developer(user)
+ project6.add_developer(user)
end
describe "#execute" do
it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests.size).to eq(7)
end
it 'filters by project' do
params = { project_id: project1.id, scope: 'authored', state: 'opened' }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(1)
+ expect(merge_requests.size).to eq(2)
end
it 'filters by group' do
@@ -49,7 +57,7 @@ describe MergeRequestsFinder do
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(2)
+ expect(merge_requests.size).to eq(3)
end
it 'filters by group including subgroups', :nested_groups do
@@ -57,13 +65,13 @@ describe MergeRequestsFinder do
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests.size).to eq(6)
end
it 'filters by non_archived' do
params = { non_archived: true }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(4)
+ expect(merge_requests.size).to eq(8)
end
it 'filters by iid' do
@@ -98,6 +106,36 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request3)
end
+ it 'filters by wip' do
+ params = { wip: 'yes' }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9)
+ end
+
+ it 'filters by not wip' do
+ params = { wip: 'no' }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3)
+ end
+
+ it 'returns all items if no valid wip param exists' do
+ params = { wip: '' }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9)
+ end
+
+ it 'adds wip to scalar params' do
+ scalar_params = described_class.scalar_params
+
+ expect(scalar_params).to include(:wip, :assignee_id)
+ end
+
context 'filtering by group milestone' do
let!(:group) { create(:group, :public) }
let(:group_milestone) { create(:milestone, group: group) }
@@ -207,7 +245,7 @@ describe MergeRequestsFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(3)
+ expect(finder.row_count).to eq(7)
end
it 'returns the number of rows for a given state' do
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index cf7d8df5405..a3a714678af 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -44,7 +44,8 @@ describe('diffs/components/app', () => {
it('shows comments message, with commit', done => {
vm.$store.state.diffs.commit = getDiffWithCommit().commit;
- vm.$nextTick()
+ vm
+ .$nextTick()
.then(() => {
expect(vm.$el).toContainText('Only comments from the following commit are shown below');
expect(vm.$el).toContainElement('.blob-commit-info');
@@ -55,10 +56,14 @@ describe('diffs/components/app', () => {
it('shows comments message, with old mergeRequestDiff', done => {
vm.$store.state.diffs.mergeRequestDiff = { latest: false };
+ vm.$store.state.diffs.targetBranch = 'master';
- vm.$nextTick()
+ vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).toContainText("Not all comments are displayed because you're viewing an old version of the diff.");
+ expect(vm.$el).toContainText(
+ "Not all comments are displayed because you're viewing an old version of the diff.",
+ );
})
.then(done)
.catch(done.fail);
@@ -67,9 +72,12 @@ describe('diffs/components/app', () => {
it('shows comments message, with startVersion', done => {
vm.$store.state.diffs.startVersion = 'test';
- vm.$nextTick()
+ vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).toContainText("Not all comments are displayed because you're comparing two versions of the diff.");
+ expect(vm.$el).toContainText(
+ "Not all comments are displayed because you're comparing two versions of the diff.",
+ );
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/diffs/components/changed_files_spec.js b/spec/javascripts/diffs/components/changed_files_spec.js
deleted file mode 100644
index 7f21273a991..00000000000
--- a/spec/javascripts/diffs/components/changed_files_spec.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { mountComponentWithStore } from 'spec/helpers';
-import diffsModule from '~/diffs/store/modules';
-import changedFiles from '~/diffs/components/changed_files.vue';
-
-describe('ChangedFiles', () => {
- const Component = Vue.extend(changedFiles);
- const store = new Vuex.Store({
- modules: {
- diffs: diffsModule(),
- },
- });
-
- let vm;
-
- beforeEach(() => {
- setFixtures(`
- <div id="dummy-element"></div>
- <div class="js-tabs-affix"></div>
- `);
-
- const props = {
- diffFiles: [
- {
- addedLines: 10,
- removedLines: 20,
- blob: {
- path: 'some/code.txt',
- },
- filePath: 'some/code.txt',
- },
- ],
- };
-
- vm = mountComponentWithStore(Component, { props, store });
- });
-
- describe('with single file added', () => {
- it('shows files changes', () => {
- expect(vm.$el).toContainText('1 changed file');
- });
-
- it('shows file additions and deletions', () => {
- expect(vm.$el).toContainText('10 additions');
- expect(vm.$el).toContainText('20 deletions');
- });
- });
-
- describe('diff view mode buttons', () => {
- let inlineButton;
- let parallelButton;
-
- beforeEach(() => {
- inlineButton = vm.$el.querySelector('.js-inline-diff-button');
- parallelButton = vm.$el.querySelector('.js-parallel-diff-button');
- });
-
- it('should have Inline and Side-by-side buttons', () => {
- expect(inlineButton).toBeDefined();
- expect(parallelButton).toBeDefined();
- });
-
- it('should add active class to Inline button', done => {
- vm.$store.state.diffs.diffViewType = 'inline';
-
- vm.$nextTick(() => {
- expect(inlineButton.classList.contains('active')).toEqual(true);
- expect(parallelButton.classList.contains('active')).toEqual(false);
-
- done();
- });
- });
-
- it('should toggle active state of buttons when diff view type changed', done => {
- vm.$store.state.diffs.diffViewType = 'parallel';
-
- vm.$nextTick(() => {
- expect(inlineButton.classList.contains('active')).toEqual(false);
- expect(parallelButton.classList.contains('active')).toEqual(true);
-
- done();
- });
- });
-
- describe('clicking them', () => {
- it('should toggle the diff view type', done => {
- parallelButton.click();
-
- vm.$nextTick(() => {
- expect(inlineButton.classList.contains('active')).toEqual(false);
- expect(parallelButton.classList.contains('active')).toEqual(true);
-
- inlineButton.click();
-
- vm.$nextTick(() => {
- expect(inlineButton.classList.contains('active')).toEqual(true);
- expect(parallelButton.classList.contains('active')).toEqual(false);
- done();
- });
- });
- });
- });
- });
-});
diff --git a/spec/javascripts/diffs/components/file_row_stats_spec.js b/spec/javascripts/diffs/components/file_row_stats_spec.js
new file mode 100644
index 00000000000..a8a7f3f1d82
--- /dev/null
+++ b/spec/javascripts/diffs/components/file_row_stats_spec.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import FileRowStats from '~/diffs/components/file_row_stats.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('Diff file row stats', () => {
+ let Component;
+ let vm;
+
+ beforeAll(() => {
+ Component = Vue.extend(FileRowStats);
+ });
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ file: {
+ addedLines: 20,
+ removedLines: 10,
+ },
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders added lines count', () => {
+ expect(vm.$el.querySelector('.cgreen').textContent).toContain('+20');
+ });
+
+ it('renders removed lines count', () => {
+ expect(vm.$el.querySelector('.cred').textContent).toContain('-10');
+ });
+});
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
new file mode 100644
index 00000000000..08e25d2004e
--- /dev/null
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -0,0 +1,120 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import TreeList from '~/diffs/components/tree_list.vue';
+import createStore from '~/diffs/store/modules';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+
+describe('Diffs tree list component', () => {
+ let Component;
+ let vm;
+
+ beforeAll(() => {
+ Component = Vue.extend(TreeList);
+ });
+
+ beforeEach(() => {
+ Vue.use(Vuex);
+
+ const store = new Vuex.Store({
+ modules: {
+ diffs: createStore(),
+ },
+ });
+
+ // Setup initial state
+ store.state.diffs.addedLines = 10;
+ store.state.diffs.removedLines = 20;
+ store.state.diffs.diffFiles.push('test');
+
+ vm = mountComponentWithStore(Component, { store });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders diff stats', () => {
+ expect(vm.$el.textContent).toContain('1 changed file');
+ expect(vm.$el.textContent).toContain('10 additions');
+ expect(vm.$el.textContent).toContain('20 deletions');
+ });
+
+ it('renders empty text', () => {
+ expect(vm.$el.textContent).toContain('No files found');
+ });
+
+ describe('with files', () => {
+ beforeEach(done => {
+ Object.assign(vm.$store.state.diffs.treeEntries, {
+ 'index.js': {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'index.js',
+ name: 'index.js',
+ path: 'index.js',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ },
+ app: {
+ key: 'app',
+ path: 'app',
+ name: 'app',
+ type: 'tree',
+ tree: [],
+ },
+ });
+ vm.$store.state.diffs.tree = [
+ vm.$store.state.diffs.treeEntries['index.js'],
+ vm.$store.state.diffs.treeEntries.app,
+ ];
+
+ vm.$nextTick(done);
+ });
+
+ it('renders tree', () => {
+ expect(vm.$el.querySelectorAll('.file-row').length).toBe(2);
+ expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js');
+ expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app');
+ });
+
+ it('filters tree list to blobs matching search', done => {
+ vm.search = 'index';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.file-row').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js');
+
+ done();
+ });
+ });
+
+ it('calls toggleTreeOpen when clicking folder', () => {
+ spyOn(vm.$store, 'dispatch').and.stub();
+
+ vm.$el.querySelectorAll('.file-row')[1].click();
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app');
+ });
+
+ it('calls scrollToFile when clicking blob', () => {
+ spyOn(vm.$store, 'dispatch').and.stub();
+
+ vm.$el.querySelector('.file-row').click();
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'index.js');
+ });
+ });
+
+ describe('clearSearch', () => {
+ it('resets search', () => {
+ vm.search = 'test';
+
+ vm.$el.querySelector('.tree-list-clear-icon').click();
+
+ expect(vm.search).toBe('');
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 05b39bad6ea..aacad7a479b 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -22,6 +22,9 @@ import actions, {
expandAllFiles,
toggleFileDiscussions,
saveDiffDiscussion,
+ toggleTreeOpen,
+ scrollToFile,
+ toggleShowTreeList,
} from '~/diffs/store/actions';
import * as types from '~/diffs/store/mutation_types';
import { reduceDiscussionsToLineCodes } from '~/notes/stores/utils';
@@ -608,4 +611,88 @@ describe('DiffsStoreActions', () => {
.catch(done.fail);
});
});
+
+ describe('toggleTreeOpen', () => {
+ it('commits TOGGLE_FOLDER_OPEN', done => {
+ testAction(
+ toggleTreeOpen,
+ 'path',
+ {},
+ [{ type: types.TOGGLE_FOLDER_OPEN, payload: 'path' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('scrollToFile', () => {
+ let commit;
+
+ beforeEach(() => {
+ commit = jasmine.createSpy();
+ jasmine.clock().install();
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
+ it('updates location hash', () => {
+ const state = {
+ treeEntries: {
+ path: {
+ fileHash: 'test',
+ },
+ },
+ };
+
+ scrollToFile({ state, commit }, 'path');
+
+ expect(document.location.hash).toBe('#test');
+ });
+
+ it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ const state = {
+ treeEntries: {
+ path: {
+ fileHash: 'test',
+ },
+ },
+ };
+
+ scrollToFile({ state, commit }, 'path');
+
+ expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test');
+ });
+
+ it('resets currentDiffId after timeout', () => {
+ const state = {
+ treeEntries: {
+ path: {
+ fileHash: 'test',
+ },
+ },
+ };
+
+ scrollToFile({ state, commit }, 'path');
+
+ jasmine.clock().tick(1000);
+
+ expect(commit.calls.argsFor(1)).toEqual([types.UPDATE_CURRENT_DIFF_FILE_ID, '']);
+ });
+ });
+
+ describe('toggleShowTreeList', () => {
+ it('commits toggle', done => {
+ testAction(toggleShowTreeList, null, {}, [{ type: types.TOGGLE_SHOW_TREE_LIST }], [], done);
+ });
+
+ it('updates localStorage', () => {
+ spyOn(localStorage, 'setItem');
+
+ toggleShowTreeList({ commit() {}, state: { showTreeList: true } });
+
+ expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 4747e437c4e..cfeaaec6980 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -291,4 +291,31 @@ describe('Diffs Module Getters', () => {
expect(getters.getDiffFileByHash(localState)('123')).toBeUndefined();
});
});
+
+ describe('allBlobs', () => {
+ it('returns an array of blobs', () => {
+ localState.treeEntries = {
+ file: {
+ type: 'blob',
+ },
+ tree: {
+ type: 'tree',
+ },
+ };
+
+ expect(getters.allBlobs(localState)).toEqual([
+ {
+ type: 'blob',
+ },
+ ]);
+ });
+ });
+
+ describe('diffFilesLength', () => {
+ it('returns length of diff files', () => {
+ localState.diffFiles.push('test', 'test 2');
+
+ expect(getters.diffFilesLength(localState)).toBe(2);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 9a5d8dfbd15..cc8d5dc4bac 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -1,3 +1,4 @@
+import createState from '~/diffs/store/modules/diff_state';
import mutations from '~/diffs/store/mutations';
import * as types from '~/diffs/store/mutation_types';
import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
@@ -356,4 +357,44 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(0);
});
});
+
+ describe('TOGGLE_FOLDER_OPEN', () => {
+ it('toggles entry opened prop', () => {
+ const state = {
+ treeEntries: {
+ path: {
+ opened: false,
+ },
+ },
+ };
+
+ mutations[types.TOGGLE_FOLDER_OPEN](state, 'path');
+
+ expect(state.treeEntries.path.opened).toBe(true);
+ });
+ });
+
+ describe('TOGGLE_SHOW_TREE_LIST', () => {
+ it('toggles showTreeList', () => {
+ const state = createState();
+
+ mutations[types.TOGGLE_SHOW_TREE_LIST](state);
+
+ expect(state.showTreeList).toBe(false, 'Failed to toggle showTreeList to false');
+
+ mutations[types.TOGGLE_SHOW_TREE_LIST](state);
+
+ expect(state.showTreeList).toBe(true, 'Failed to toggle showTreeList to true');
+ });
+ });
+
+ describe('UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ it('updates currentDiffFileId', () => {
+ const state = createState();
+
+ mutations[types.UPDATE_CURRENT_DIFF_FILE_ID](state, 'somefileid');
+
+ expect(state.currentDiffFileId).toBe('somefileid');
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 897cd1483aa..e660f94c72e 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -421,4 +421,113 @@ describe('DiffsStoreUtils', () => {
).toBe(false);
});
});
+
+ describe('generateTreeList', () => {
+ let files;
+
+ beforeAll(() => {
+ files = [
+ {
+ newPath: 'app/index.js',
+ deletedFile: false,
+ newFile: false,
+ removedLines: 10,
+ addedLines: 0,
+ fileHash: 'test',
+ },
+ {
+ newPath: 'app/test/index.js',
+ deletedFile: false,
+ newFile: true,
+ removedLines: 0,
+ addedLines: 0,
+ fileHash: 'test',
+ },
+ {
+ newPath: 'package.json',
+ deletedFile: true,
+ newFile: false,
+ removedLines: 0,
+ addedLines: 0,
+ fileHash: 'test',
+ },
+ ];
+ });
+
+ it('creates a tree of files', () => {
+ const { tree } = utils.generateTreeList(files);
+
+ expect(tree).toEqual([
+ {
+ key: 'app',
+ path: 'app',
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'app/index.js',
+ name: 'index.js',
+ path: 'app/index.js',
+ removedLines: 10,
+ tempFile: false,
+ type: 'blob',
+ tree: [],
+ },
+ {
+ key: 'app/test',
+ path: 'app/test',
+ name: 'test',
+ type: 'tree',
+ opened: true,
+ tree: [
+ {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'app/test/index.js',
+ name: 'index.js',
+ path: 'app/test/index.js',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ opened: true,
+ },
+ {
+ key: 'package.json',
+ path: 'package.json',
+ name: 'package.json',
+ type: 'blob',
+ changed: true,
+ tempFile: false,
+ deleted: true,
+ fileHash: 'test',
+ addedLines: 0,
+ removedLines: 0,
+ tree: [],
+ },
+ ]);
+ });
+
+ it('creates flat list of blobs & folders', () => {
+ const { treeEntries } = utils.generateTreeList(files);
+
+ expect(Object.keys(treeEntries)).toEqual([
+ 'app',
+ 'app/index.js',
+ 'app/test',
+ 'app/test/index.js',
+ 'package.json',
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 8792e99d461..68bbbf838da 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -288,13 +288,13 @@ describe('Dropdown Utils', () => {
describe('setDataValueIfSelected', () => {
beforeEach(() => {
- spyOn(FilteredSearchDropdownManager, 'addWordToInput')
- .and.callFake(() => {});
+ spyOn(FilteredSearchDropdownManager, 'addWordToInput').and.callFake(() => {});
});
it('calls addWordToInput when dataValue exists', () => {
const selected = {
getAttribute: () => 'value',
+ hasAttribute: () => false,
};
DropdownUtils.setDataValueIfSelected(null, selected);
@@ -304,6 +304,7 @@ describe('Dropdown Utils', () => {
it('returns true when dataValue exists', () => {
const selected = {
getAttribute: () => 'value',
+ hasAttribute: () => false,
};
const result = DropdownUtils.setDataValueIfSelected(null, selected);
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 756a654765b..53a6d1d62b0 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -240,13 +240,17 @@ describe('Filtered Search Visual Tokens', () => {
beforeEach(() => {
setFixtures(`
<div class="test-area">
- ${subject.createVisualTokenElementHTML()}
+ ${subject.createVisualTokenElementHTML('custom-token')}
</div>
`);
tokenElement = document.querySelector('.test-area').firstElementChild;
});
+ it('should add class name to token element', () => {
+ expect(document.querySelector('.test-area .custom-token')).toBeDefined();
+ });
+
it('contains name div', () => {
expect(tokenElement.querySelector('.name')).toEqual(jasmine.anything());
});
@@ -280,7 +284,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('addVisualTokenElement', () => {
it('renders search visual tokens', () => {
- subject.addVisualTokenElement('search term', null, true);
+ subject.addVisualTokenElement('search term', null, { isSearchTerm: true });
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-term')).toEqual(true);
diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js
index 60dabe28045..c93a939ad71 100644
--- a/spec/javascripts/ide/components/file_row_extra_spec.js
+++ b/spec/javascripts/ide/components/file_row_extra_spec.js
@@ -107,14 +107,14 @@ describe('IDE extra file row component', () => {
describe('changes file icon', () => {
it('hides when file is not changed', () => {
- expect(vm.$el.querySelector('.ide-file-changed-icon')).toBe(null);
+ expect(vm.$el.querySelector('.file-changed-icon')).toBe(null);
});
it('shows when file is changed', done => {
vm.file.changed = true;
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
done();
});
@@ -124,7 +124,7 @@ describe('IDE extra file row component', () => {
vm.file.staged = true;
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
done();
});
@@ -134,7 +134,7 @@ describe('IDE extra file row component', () => {
vm.file.tempFile = true;
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
done();
});
diff --git a/spec/javascripts/ide/components/repo_tab_spec.js b/spec/javascripts/ide/components/repo_tab_spec.js
index 278a0753322..3b52f279bf2 100644
--- a/spec/javascripts/ide/components/repo_tab_spec.js
+++ b/spec/javascripts/ide/components/repo_tab_spec.js
@@ -93,13 +93,13 @@ describe('RepoTab', () => {
Vue.nextTick()
.then(() => {
- expect(vm.$el.querySelector('.ide-file-modified')).toBeNull();
+ expect(vm.$el.querySelector('.file-modified')).toBeNull();
vm.$el.dispatchEvent(new Event('mouseout'));
})
.then(Vue.nextTick)
.then(() => {
- expect(vm.$el.querySelector('.ide-file-modified')).not.toBeNull();
+ expect(vm.$el.querySelector('.file-modified')).not.toBeNull();
done();
})
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index 2fcb5566ebc..d6b5dec9e47 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -57,25 +57,6 @@ describe('Job', () => {
expect(job.buildStage).toBe('test');
expect(job.state).toBe('');
});
-
- it('only shows the jobs matching the current stage', () => {
- expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false);
- expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true);
- expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
- });
-
- it('selects the current stage in the build dropdown menu', () => {
- expect($('.stage-selection').text()).toBe('test');
- });
-
- it('updates the jobs when the build dropdown changes', () => {
- $('.stage-item:contains("build")').click();
-
- expect($('.stage-selection').text()).toBe('build');
- expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
- expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
- expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
- });
});
describe('running build', () => {
diff --git a/spec/javascripts/jobs/components/environments_block_spec.js b/spec/javascripts/jobs/components/environments_block_spec.js
index 015c26be9fc..7d836129b13 100644
--- a/spec/javascripts/jobs/components/environments_block_spec.js
+++ b/spec/javascripts/jobs/components/environments_block_spec.js
@@ -5,19 +5,16 @@ import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Environments block', () => {
const Component = Vue.extend(component);
let vm;
- const icon = {
+ const status = {
group: 'success',
icon: 'status_success',
label: 'passed',
text: 'passed',
tooltip: 'passed',
};
- const deployment = {
- path: 'deployment',
- name: 'deployment name',
- };
+
const environment = {
- path: '/environment',
+ environment_path: '/environment',
name: 'environment',
};
@@ -25,15 +22,14 @@ describe('Environments block', () => {
vm.$destroy();
});
- describe('with latest deployment', () => {
+ describe('with last deployment', () => {
it('renders info for most recent deployment', () => {
vm = mountComponent(Component, {
deploymentStatus: {
- status: 'latest',
- icon,
- deployment,
+ status: 'last',
environment,
},
+ iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@@ -48,17 +44,17 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'out_of_date',
- icon,
- deployment,
environment: Object.assign({}, environment, {
- last_deployment: { name: 'deployment', path: 'last_deployment' },
+ last_deployment: { iid: 'deployment', deployable: { build_path: 'bar' } },
}),
},
+ iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
- 'This job is an out-of-date deployment to environment. View the most recent deployment deployment.',
+ 'This job is an out-of-date deployment to environment. View the most recent deployment #deployment.',
);
+ expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar');
});
});
@@ -67,10 +63,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'out_of_date',
- icon,
- deployment: null,
environment,
},
+ iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@@ -85,10 +80,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'failed',
- icon,
- deployment: null,
environment,
},
+ iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@@ -99,21 +93,24 @@ describe('Environments block', () => {
describe('creating deployment', () => {
describe('with last deployment', () => {
- it('renders info about creating deployment and overriding lastest deployment', () => {
+ it('renders info about creating deployment and overriding latest deployment', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'creating',
- icon,
- deployment,
environment: Object.assign({}, environment, {
- last_deployment: { name: 'deployment', path: 'last_deployment' },
+ last_deployment: {
+ iid: 'deployment',
+ deployable: { build_path: 'foo' },
+ },
}),
},
+ iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
- 'This job is creating a deployment to environment and will overwrite the last deployment.',
+ 'This job is creating a deployment to environment and will overwrite the latest deployment.',
);
+ expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('foo');
});
});
@@ -122,10 +119,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'creating',
- icon,
- deployment: null,
environment,
},
+ iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@@ -133,5 +129,18 @@ describe('Environments block', () => {
);
});
});
+
+ describe('without environment', () => {
+ it('does not render environment link', () => {
+ vm = mountComponent(Component, {
+ deploymentStatus: {
+ status: 'creating',
+ environment: null,
+ },
+ iconStatus: status,
+ });
+ expect(vm.$el.querySelector('.js-environment-link')).toBeNull();
+ });
+ });
});
});
diff --git a/spec/javascripts/jobs/components/header_spec.js b/spec/javascripts/jobs/components/header_spec.js
deleted file mode 100644
index e21e2c6d6e3..00000000000
--- a/spec/javascripts/jobs/components/header_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import Vue from 'vue';
-import headerComponent from '~/jobs/components/header.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Job details header', () => {
- let HeaderComponent;
- let vm;
- let props;
-
- beforeEach(() => {
- HeaderComponent = Vue.extend(headerComponent);
-
- const threeWeeksAgo = new Date();
- threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
-
- const twoDaysAgo = new Date();
- twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
-
- props = {
- job: {
- status: {
- group: 'failed',
- icon: 'status_failed',
- label: 'failed',
- text: 'failed',
- details_path: 'path',
- },
- id: 123,
- created_at: threeWeeksAgo.toISOString(),
- user: {
- web_url: 'path',
- name: 'Foo',
- username: 'foobar',
- email: 'foo@bar.com',
- avatar_url: 'link',
- },
- started: twoDaysAgo.toISOString(),
- new_issue_path: 'path',
- },
- isLoading: false,
- };
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('job reason', () => {
- it('should not render the reason when reason is absent', () => {
- vm = mountComponent(HeaderComponent, props);
-
- expect(vm.shouldRenderReason).toBe(false);
- });
-
- it('should render the reason when reason is present', () => {
- props.job.callout_message = 'There is an unknown failure, please try again';
-
- vm = mountComponent(HeaderComponent, props);
-
- expect(vm.shouldRenderReason).toBe(true);
- });
- });
-
- describe('triggered job', () => {
- beforeEach(() => {
- vm = mountComponent(HeaderComponent, props);
- });
-
- it('should render provided job information', () => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toEqual('failed Job #123 triggered 2 days ago by Foo');
- });
-
- it('should render new issue link', () => {
- expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
- props.job.new_issue_path,
- );
- });
- });
-
- describe('created job', () => {
- it('should render created key', () => {
- props.job.started = false;
- vm = mountComponent(HeaderComponent, props);
-
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toEqual('failed Job #123 created 3 weeks ago by Foo');
- });
- });
-});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
new file mode 100644
index 00000000000..c31fa6f9887
--- /dev/null
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -0,0 +1,185 @@
+import Vue from 'vue';
+import jobApp from '~/jobs/components/job_app.vue';
+import createStore from '~/jobs/store';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+
+describe('Job App ', () => {
+ const Component = Vue.extend(jobApp);
+ let store;
+ let vm;
+
+ const threeWeeksAgo = new Date();
+ threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
+
+ const twoDaysAgo = new Date();
+ twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
+
+ const job = {
+ status: {
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ },
+ id: 123,
+ created_at: threeWeeksAgo.toISOString(),
+ user: {
+ web_url: 'path',
+ name: 'Foo',
+ username: 'foobar',
+ email: 'foo@bar.com',
+ avatar_url: 'link',
+ },
+ started: twoDaysAgo.toISOString(),
+ new_issue_path: 'path',
+ runners: {
+ available: false,
+ },
+ tags: ['docker'],
+ };
+
+ const props = {
+ runnerHelpUrl: 'help/runners',
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('Header section', () => {
+ describe('job callout message', () => {
+ it('should not render the reason when reason is absent', () => {
+ store.dispatch('receiveJobSuccess', job);
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.shouldRenderCalloutMessage).toBe(false);
+ });
+
+ it('should render the reason when reason is present', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ callout_message: 'There is an unknown failure, please try again',
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.shouldRenderCalloutMessage).toBe(true);
+ });
+ });
+
+ describe('triggered job', () => {
+ beforeEach(() => {
+ store.dispatch('receiveJobSuccess', job);
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+ });
+
+ it('should render provided job information', () => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toEqual('failed Job #123 triggered 2 days ago by Foo');
+ });
+
+ it('should render new issue link', () => {
+ expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+ job.new_issue_path,
+ );
+ });
+ });
+
+ describe('created job', () => {
+ it('should render created key', () => {
+ store.dispatch('receiveJobSuccess', Object.assign({}, job, { started: false }));
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toEqual('failed Job #123 created 3 weeks ago by Foo');
+ });
+ });
+ });
+
+ describe('stuck block', () => {
+ it('renders stuck block when there are no runners', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ status: {
+ group: 'pending',
+ icon: 'status_pending',
+ label: 'pending',
+ text: 'pending',
+ details_path: 'path',
+ },
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
+ });
+
+ it('renders tags in stuck block when there are no runners', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ status: {
+ group: 'pending',
+ icon: 'status_pending',
+ label: 'pending',
+ text: 'pending',
+ details_path: 'path',
+ },
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ });
+
+ it(' does not renders stuck block when there are no runners', () => {
+ store.dispatch('receiveJobSuccess', Object.assign({}, job, { runners: { available: true } }));
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/jobs_container_spec.js b/spec/javascripts/jobs/components/jobs_container_spec.js
index f3f8ff0d031..fa3a2c4c266 100644
--- a/spec/javascripts/jobs/components/jobs_container_spec.js
+++ b/spec/javascripts/jobs/components/jobs_container_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import component from '~/jobs/components/jobs_container.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
-describe('Artifacts block', () => {
+describe('Jobs List block', () => {
const Component = Vue.extend(component);
let vm;
@@ -16,8 +16,7 @@ describe('Artifacts block', () => {
text: 'passed',
tooltip: 'passed',
},
- path: 'job/233432756',
- id: '233432756',
+ id: 233432756,
tooltip: 'build - passed',
retried: true,
};
@@ -33,8 +32,7 @@ describe('Artifacts block', () => {
text: 'passed',
tooltip: 'passed',
},
- path: 'job/2322756',
- id: '2322756',
+ id: 2322756,
tooltip: 'build - passed',
active: true,
};
@@ -50,8 +48,7 @@ describe('Artifacts block', () => {
text: 'passed',
tooltip: 'passed',
},
- path: 'job/232153',
- id: '232153',
+ id: 232153,
tooltip: 'build - passed',
};
@@ -62,14 +59,16 @@ describe('Artifacts block', () => {
it('renders list of jobs', () => {
vm = mountComponent(Component, {
jobs: [job, retried, active],
+ jobId: 12313,
});
expect(vm.$el.querySelectorAll('a').length).toEqual(3);
});
- it('renders arrow right when job is active', () => {
+ it('renders arrow right when job id matches `jobId`', () => {
vm = mountComponent(Component, {
jobs: [active],
+ jobId: active.id,
});
expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull();
@@ -78,6 +77,7 @@ describe('Artifacts block', () => {
it('does not render arrow right when job is not active', () => {
vm = mountComponent(Component, {
jobs: [job],
+ jobId: active.id,
});
expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull();
@@ -86,6 +86,7 @@ describe('Artifacts block', () => {
it('renders job name when present', () => {
vm = mountComponent(Component, {
jobs: [job],
+ jobId: active.id,
});
expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name);
@@ -95,6 +96,7 @@ describe('Artifacts block', () => {
it('renders job id when job name is not available', () => {
vm = mountComponent(Component, {
jobs: [retried],
+ jobId: active.id,
});
expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id);
@@ -103,14 +105,16 @@ describe('Artifacts block', () => {
it('links to the job page', () => {
vm = mountComponent(Component, {
jobs: [job],
+ jobId: active.id,
});
- expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.path);
+ expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.status.details_path);
});
it('renders retry icon when job was retried', () => {
vm = mountComponent(Component, {
jobs: [retried],
+ jobId: active.id,
});
expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull();
@@ -119,6 +123,7 @@ describe('Artifacts block', () => {
it('does not render retry icon when job was not retried', () => {
vm = mountComponent(Component, {
jobs: [job],
+ jobId: active.id,
});
expect(vm.$el.querySelector('.js-retry-icon')).toBeNull();
diff --git a/spec/javascripts/jobs/components/sidebar_details_block_spec.js b/spec/javascripts/jobs/components/sidebar_details_block_spec.js
deleted file mode 100644
index ba19534dac2..00000000000
--- a/spec/javascripts/jobs/components/sidebar_details_block_spec.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import Vue from 'vue';
-import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue';
-import job from '../mock_data';
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
-describe('Sidebar details block', () => {
- let SidebarComponent;
- let vm;
-
- function trimWhitespace(element) {
- return element.textContent.replace(/\s+/g, ' ').trim();
- }
-
- beforeEach(() => {
- SidebarComponent = Vue.extend(sidebarDetailsBlock);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('when it is loading', () => {
- it('should render a loading spinner', () => {
- vm = mountComponent(SidebarComponent, {
- job: {},
- isLoading: true,
- });
- expect(vm.$el.querySelector('.fa-spinner')).toBeDefined();
- });
- });
-
- describe('when there is no retry path retry', () => {
- it('should not render a retry button', () => {
- vm = mountComponent(SidebarComponent, {
- job: {},
- isLoading: false,
- });
-
- expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
- });
- });
-
- describe('without terminal path', () => {
- it('does not render terminal link', () => {
- vm = mountComponent(SidebarComponent, {
- job,
- isLoading: false,
- });
-
- expect(vm.$el.querySelector('.js-terminal-link')).toBeNull();
- });
- });
-
- describe('with terminal path', () => {
- it('renders terminal link', () => {
- vm = mountComponent(SidebarComponent, {
- job,
- isLoading: false,
- terminalPath: 'job/43123/terminal',
- });
-
- expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull();
- });
- });
-
- beforeEach(() => {
- vm = mountComponent(SidebarComponent, {
- job,
- isLoading: false,
- });
- });
-
- describe('actions', () => {
- it('should render link to new issue', () => {
- expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
- job.new_issue_path,
- );
- expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue');
- });
-
- it('should render link to retry job', () => {
- expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path);
- });
-
- it('should render link to cancel job', () => {
- expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path);
- });
- });
-
- describe('information', () => {
- it('should render merge request link', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-mr'))).toEqual('Merge Request: !2');
-
- expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual(
- job.merge_request.path,
- );
- });
-
- it('should render job duration', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-duration'))).toEqual(
- 'Duration: 6 seconds',
- );
- });
-
- it('should render erased date', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-erased'))).toEqual('Erased: 3 weeks ago');
- });
-
- it('should render finished date', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-finished'))).toEqual(
- 'Finished: 3 weeks ago',
- );
- });
-
- it('should render queued date', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-queued'))).toEqual('Queued: 9 seconds');
- });
-
- it('should render runner ID', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual(
- 'Runner: local ci runner (#1)',
- );
- });
-
- it('should render timeout information', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-timeout'))).toEqual(
- 'Timeout: 1m 40s (from runner)',
- );
- });
-
- it('should render coverage', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-coverage'))).toEqual('Coverage: 20%');
- });
-
- it('should render tags', () => {
- expect(trimWhitespace(vm.$el.querySelector('.js-job-tags'))).toEqual('Tags: tag');
- });
- });
-});
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
new file mode 100644
index 00000000000..2f5c4245ced
--- /dev/null
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -0,0 +1,196 @@
+import Vue from 'vue';
+import sidebarDetailsBlock from '~/jobs/components/sidebar.vue';
+import createStore from '~/jobs/store';
+import job, { stages, jobsInStage } from '../mock_data';
+import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
+import { trimText } from '../../helpers/vue_component_helper';
+
+describe('Sidebar details block', () => {
+ const SidebarComponent = Vue.extend(sidebarDetailsBlock);
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('when it is loading', () => {
+ it('should render a loading spinner', () => {
+ store.dispatch('requestJob');
+ vm = mountComponentWithStore(SidebarComponent, { store });
+
+ expect(vm.$el.querySelector('.fa-spinner')).toBeDefined();
+ });
+ });
+
+ describe('when there is no retry path retry', () => {
+ it('should not render a retry button', () => {
+ const copy = Object.assign({}, job);
+ delete copy.retry_path;
+
+ store.dispatch('receiveJobSuccess', copy);
+ vm = mountComponentWithStore(SidebarComponent, {
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
+ });
+ });
+
+ describe('without terminal path', () => {
+ it('does not render terminal link', () => {
+ store.dispatch('receiveJobSuccess', job);
+ vm = mountComponentWithStore(SidebarComponent, { store });
+
+ expect(vm.$el.querySelector('.js-terminal-link')).toBeNull();
+ });
+ });
+
+ describe('with terminal path', () => {
+ it('renders terminal link', () => {
+ store.dispatch('receiveJobSuccess', job);
+ vm = mountComponentWithStore(SidebarComponent, {
+ store,
+ props: {
+ terminalPath: 'job/43123/terminal',
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull();
+ });
+ });
+
+ beforeEach(() => {
+ store.dispatch('receiveJobSuccess', job);
+ vm = mountComponentWithStore(SidebarComponent, { store });
+ });
+
+ describe('actions', () => {
+ it('should render link to new issue', () => {
+ expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+ job.new_issue_path,
+ );
+ expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue');
+ });
+
+ it('should render link to retry job', () => {
+ expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path);
+ });
+
+ it('should render link to cancel job', () => {
+ expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path);
+ });
+ });
+
+ describe('information', () => {
+ it('should render merge request link', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-mr').textContent)).toEqual('Merge Request: !2');
+
+ expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual(
+ job.merge_request.path,
+ );
+ });
+
+ it('should render job duration', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual(
+ 'Duration: 6 seconds',
+ );
+ });
+
+ it('should render erased date', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-erased').textContent)).toEqual(
+ 'Erased: 3 weeks ago',
+ );
+ });
+
+ it('should render finished date', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-finished').textContent)).toEqual(
+ 'Finished: 3 weeks ago',
+ );
+ });
+
+ it('should render queued date', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-queued').textContent)).toEqual(
+ 'Queued: 9 seconds',
+ );
+ });
+
+ it('should render runner ID', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-runner').textContent)).toEqual(
+ 'Runner: local ci runner (#1)',
+ );
+ });
+
+ it('should render timeout information', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-timeout').textContent)).toEqual(
+ 'Timeout: 1m 40s (from runner)',
+ );
+ });
+
+ it('should render coverage', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-coverage').textContent)).toEqual(
+ 'Coverage: 20%',
+ );
+ });
+
+ it('should render tags', () => {
+ expect(trimText(vm.$el.querySelector('.js-job-tags').textContent)).toEqual('Tags: tag');
+ });
+ });
+
+ describe('stages dropdown', () => {
+ beforeEach(() => {
+ store.dispatch('receiveJobSuccess', job);
+ });
+
+ describe('while fetching stages', () => {
+ it('renders dropdown with More label', () => {
+ vm = mountComponentWithStore(SidebarComponent, { store });
+
+ expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual('More');
+ });
+ });
+
+ describe('with stages', () => {
+ beforeEach(() => {
+ store.dispatch('receiveStagesSuccess', stages);
+ vm = mountComponentWithStore(SidebarComponent, { store });
+ });
+
+ it('renders first stage as selected', () => {
+ expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual(
+ stages[0].name,
+ );
+ });
+ });
+
+ describe('without jobs for stages', () => {
+ beforeEach(() => {
+ store.dispatch('receiveJobSuccess', job);
+ store.dispatch('receiveStagesSuccess', stages);
+ vm = mountComponentWithStore(SidebarComponent, { store });
+ });
+
+ it('does not render job container', () => {
+ expect(vm.$el.querySelector('.js-jobs-container')).toBeNull();
+ });
+ });
+
+ describe('with jobs for stages', () => {
+ beforeEach(() => {
+ store.dispatch('receiveJobSuccess', job);
+ store.dispatch('receiveStagesSuccess', stages);
+ store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
+ vm = mountComponentWithStore(SidebarComponent, { store });
+ });
+
+ it('renders list of jobs', () => {
+ expect(vm.$el.querySelector('.js-jobs-container')).not.toBeNull();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index 402289345aa..aa6cc0f1b1a 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -8,10 +8,25 @@ describe('Artifacts block', () => {
beforeEach(() => {
vm = mountComponent(Component, {
- pipelineId: 28029444,
- pipelinePath: 'pipeline/28029444',
- pipelineRef: '50101-truncated-job-information',
- pipelineRefPath: 'commits/50101-truncated-job-information',
+ pipeline: {
+ id: 28029444,
+ details: {
+ status: {
+ details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
+ group: 'success',
+ has_details: true,
+ icon: 'status_success',
+ label: 'passed',
+ text: 'passed',
+ tooltip: 'passed',
+ },
+ },
+ path: 'pipeline/28029444',
+ },
+ ref: {
+ path: 'commits/50101-truncated-job-information',
+ name: '50101-truncated-job-information',
+ },
stages: [
{
name: 'build',
@@ -20,15 +35,6 @@ describe('Artifacts block', () => {
name: 'test',
},
],
- pipelineStatus: {
- details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
- group: 'success',
- has_details: true,
- icon: 'status_success',
- label: 'passed',
- text: 'passed',
- tooltip: 'passed',
- },
});
});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 8fdd9b309b7..4269b42e8b6 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -20,7 +20,8 @@ export default {
group: 'success',
has_details: true,
details_path: '/root/ci-mock/-/jobs/4757',
- favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -37,7 +38,8 @@ export default {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
erase_path: '/root/ci-mock/-/jobs/4757/erase',
@@ -54,7 +56,8 @@ export default {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
active: false,
@@ -78,7 +81,8 @@ export default {
group: 'success',
has_details: true,
details_path: '/root/ci-mock/pipelines/140',
- favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
},
duration: 6,
finished_at: '2017-06-01T17:32:00.042Z',
@@ -107,11 +111,14 @@ export default {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
- author_gravatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- commit_url: 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ commit_url:
+ 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
},
},
@@ -125,3 +132,1029 @@ export default {
},
raw_path: '/root/ci-mock/builds/4757/raw',
};
+
+export const stages = [
+ {
+ name: 'build',
+ title: 'build: running',
+ groups: [
+ {
+ name: 'build:linux',
+ size: 1,
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
+ illustration: {
+ image: 'illustrations/pending_job_empty.svg',
+ size: 'svg-430',
+ title: 'This job has not started yet',
+ content: 'This job is in pending state and is waiting to be picked by a runner',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 1180,
+ name: 'build:linux',
+ started: false,
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
+ cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
+ playable: false,
+ created_at: '2018-09-28T11:09:57.229Z',
+ updated_at: '2018-09-28T11:09:57.503Z',
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
+ illustration: {
+ image: 'illustrations/pending_job_empty.svg',
+ size: 'svg-430',
+ title: 'This job has not started yet',
+ content: 'This job is in pending state and is waiting to be picked by a runner',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'build:osx',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/444',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 444,
+ name: 'build:osx',
+ started: '2018-05-18T05:32:20.655Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/444',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.364Z',
+ updated_at: '2018-05-18T15:32:54.364Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/444',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ tooltip: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/pipelines/27#build',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
+ },
+ path: '/gitlab-org/gitlab-shell/pipelines/27#build',
+ dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build',
+ },
+ {
+ name: 'test',
+ title: 'test: passed with warnings',
+ groups: [
+ {
+ name: 'jenkins',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: null,
+ group: 'success',
+ tooltip: null,
+ has_details: false,
+ details_path: null,
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ jobs: [
+ {
+ id: 459,
+ name: 'jenkins',
+ started: '2018-05-18T09:32:20.658Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/459',
+ playable: false,
+ created_at: '2018-05-18T15:32:55.330Z',
+ updated_at: '2018-05-18T15:32:55.330Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: null,
+ group: 'success',
+ tooltip: null,
+ has_details: false,
+ details_path: null,
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ },
+ ],
+ },
+ {
+ name: 'rspec:linux',
+ size: 3,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: false,
+ details_path: null,
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ jobs: [
+ {
+ id: 445,
+ name: 'rspec:linux 0 3',
+ started: '2018-05-18T07:32:20.655Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/445',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.425Z',
+ updated_at: '2018-05-18T15:32:54.425Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/445',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/445/retry',
+ method: 'post',
+ },
+ },
+ },
+ {
+ id: 446,
+ name: 'rspec:linux 1 3',
+ started: '2018-05-18T07:32:20.655Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/446',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.506Z',
+ updated_at: '2018-05-18T15:32:54.506Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/446',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/446/retry',
+ method: 'post',
+ },
+ },
+ },
+ {
+ id: 447,
+ name: 'rspec:linux 2 3',
+ started: '2018-05-18T07:32:20.656Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/447',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.572Z',
+ updated_at: '2018-05-18T15:32:54.572Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/447',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/447/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'rspec:osx',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/452',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/452/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 452,
+ name: 'rspec:osx',
+ started: '2018-05-18T07:32:20.657Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/452',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.920Z',
+ updated_at: '2018-05-18T15:32:54.920Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/452',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/452/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'rspec:windows',
+ size: 3,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: false,
+ details_path: null,
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ jobs: [
+ {
+ id: 448,
+ name: 'rspec:windows 0 3',
+ started: '2018-05-18T07:32:20.656Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/448',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.639Z',
+ updated_at: '2018-05-18T15:32:54.639Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/448',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/448/retry',
+ method: 'post',
+ },
+ },
+ },
+ {
+ id: 449,
+ name: 'rspec:windows 1 3',
+ started: '2018-05-18T07:32:20.656Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/449',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.703Z',
+ updated_at: '2018-05-18T15:32:54.703Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/449',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/449/retry',
+ method: 'post',
+ },
+ },
+ },
+ {
+ id: 451,
+ name: 'rspec:windows 2 3',
+ started: '2018-05-18T07:32:20.657Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/451',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.853Z',
+ updated_at: '2018-05-18T15:32:54.853Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/451',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/451/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'spinach:linux',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/453',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/453/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 453,
+ name: 'spinach:linux',
+ started: '2018-05-18T07:32:20.657Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/453',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.993Z',
+ updated_at: '2018-05-18T15:32:54.993Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/453',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/453/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'spinach:osx',
+ size: 1,
+ status: {
+ icon: 'status_warning',
+ text: 'failed',
+ label: 'failed (allowed to fail)',
+ group: 'failed_with_warnings',
+ tooltip: 'failed - (unknown failure) (allowed to fail)',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 454,
+ name: 'spinach:osx',
+ started: '2018-05-18T07:32:20.657Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/454',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:55.053Z',
+ updated_at: '2018-05-18T15:32:55.053Z',
+ status: {
+ icon: 'status_warning',
+ text: 'failed',
+ label: 'failed (allowed to fail)',
+ group: 'failed_with_warnings',
+ tooltip: 'failed - (unknown failure) (allowed to fail)',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
+ method: 'post',
+ },
+ },
+ callout_message: 'There is an unknown failure, please try again',
+ recoverable: true,
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success_with_warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/pipelines/27#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/gitlab-org/gitlab-shell/pipelines/27#test',
+ dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test',
+ },
+ {
+ name: 'deploy',
+ title: 'deploy: running',
+ groups: [
+ {
+ name: 'production',
+ size: 1,
+ status: {
+ icon: 'status_created',
+ text: 'created',
+ label: 'created',
+ group: 'created',
+ tooltip: 'created',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/457',
+ illustration: {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: 'This job has not been triggered yet',
+ content:
+ 'This job depends on upstream jobs that need to succeed in order for this job to be triggered',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 457,
+ name: 'production',
+ started: false,
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/457',
+ cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel',
+ playable: false,
+ created_at: '2018-05-18T15:32:55.259Z',
+ updated_at: '2018-09-28T11:09:57.454Z',
+ status: {
+ icon: 'status_created',
+ text: 'created',
+ label: 'created',
+ group: 'created',
+ tooltip: 'created',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/457',
+ illustration: {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: 'This job has not been triggered yet',
+ content:
+ 'This job depends on upstream jobs that need to succeed in order for this job to be triggered',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'staging',
+ size: 1,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/455',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/455/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 455,
+ name: 'staging',
+ started: '2018-05-18T09:32:20.658Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/455',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:55.119Z',
+ updated_at: '2018-05-18T15:32:55.119Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/455',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/455/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'stop staging',
+ size: 1,
+ status: {
+ icon: 'status_created',
+ text: 'created',
+ label: 'created',
+ group: 'created',
+ tooltip: 'created',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/456',
+ illustration: {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: 'This job has not been triggered yet',
+ content:
+ 'This job depends on upstream jobs that need to succeed in order for this job to be triggered',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 456,
+ name: 'stop staging',
+ started: false,
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/456',
+ cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel',
+ playable: false,
+ created_at: '2018-05-18T15:32:55.205Z',
+ updated_at: '2018-09-28T11:09:57.396Z',
+ status: {
+ icon: 'status_created',
+ text: 'created',
+ label: 'created',
+ group: 'created',
+ tooltip: 'created',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/456',
+ illustration: {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: 'This job has not been triggered yet',
+ content:
+ 'This job depends on upstream jobs that need to succeed in order for this job to be triggered',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ tooltip: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
+ },
+ path: '/gitlab-org/gitlab-shell/pipelines/27#deploy',
+ dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy',
+ },
+ {
+ name: 'notify',
+ title: 'notify: manual action',
+ groups: [
+ {
+ name: 'slack',
+ size: 1,
+ status: {
+ icon: 'status_manual',
+ text: 'manual',
+ label: 'manual play action',
+ group: 'manual',
+ tooltip: 'manual action',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/458',
+ illustration: {
+ image: 'illustrations/manual_action.svg',
+ size: 'svg-394',
+ title: 'This job requires a manual action',
+ content:
+ 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
+ action: {
+ icon: 'play',
+ title: 'Play',
+ path: '/gitlab-org/gitlab-shell/-/jobs/458/play',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 458,
+ name: 'slack',
+ started: null,
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/458',
+ play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play',
+ playable: true,
+ created_at: '2018-05-18T15:32:55.303Z',
+ updated_at: '2018-05-18T15:34:08.535Z',
+ status: {
+ icon: 'status_manual',
+ text: 'manual',
+ label: 'manual play action',
+ group: 'manual',
+ tooltip: 'manual action',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/458',
+ illustration: {
+ image: 'illustrations/manual_action.svg',
+ size: 'svg-394',
+ title: 'This job requires a manual action',
+ content:
+ 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
+ action: {
+ icon: 'play',
+ title: 'Play',
+ path: '/gitlab-org/gitlab-shell/-/jobs/458/play',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'status_manual',
+ text: 'manual',
+ label: 'manual action',
+ group: 'manual',
+ tooltip: 'manual action',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
+ },
+ path: '/gitlab-org/gitlab-shell/pipelines/27#notify',
+ dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify',
+ },
+];
+
+export const jobsInStage = {
+ name: 'build',
+ title: 'build: running',
+ latest_statuses: [
+ {
+ id: 1180,
+ name: 'build:linux',
+ started: false,
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
+ cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
+ playable: false,
+ created_at: '2018-09-28T11:09:57.229Z',
+ updated_at: '2018-09-28T11:09:57.503Z',
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
+ illustration: {
+ image: 'illustrations/pending_job_empty.svg',
+ size: 'svg-430',
+ title: 'This job has not started yet',
+ content: 'This job is in pending state and is waiting to be picked by a runner',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ action: {
+ icon: 'cancel',
+ title: 'Cancel',
+ path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
+ method: 'post',
+ },
+ },
+ },
+ {
+ id: 444,
+ name: 'build:osx',
+ started: '2018-05-18T05:32:20.655Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/444',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.364Z',
+ updated_at: '2018-05-18T15:32:54.364Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/444',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ retried: [
+ {
+ id: 443,
+ name: 'build:linux',
+ started: '2018-05-18T06:32:20.655Z',
+ build_path: '/gitlab-org/gitlab-shell/-/jobs/443',
+ retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry',
+ playable: false,
+ created_at: '2018-05-18T15:32:54.296Z',
+ updated_at: '2018-05-18T15:32:54.296Z',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed (retried)',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/-/jobs/443',
+ illustration: {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/gitlab-org/gitlab-shell/-/jobs/443/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ tooltip: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-shell/pipelines/27#build',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
+ },
+ path: '/gitlab-org/gitlab-shell/pipelines/27#build',
+ dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build',
+};
diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js
index 5042718dfa0..5ab1f75d0d6 100644
--- a/spec/javascripts/jobs/store/actions_spec.js
+++ b/spec/javascripts/jobs/store/actions_spec.js
@@ -27,7 +27,6 @@ import {
receiveStagesSuccess,
receiveStagesError,
requestJobsForStage,
- setSelectedStage,
fetchJobsForStage,
receiveJobsForStageSuccess,
receiveJobsForStageError,
@@ -236,7 +235,8 @@ describe('Job State actions', () => {
},
{
payload: {
- html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', complete: true,
+ html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
+ complete: true,
},
type: 'receiveTraceSuccess',
},
@@ -421,7 +421,9 @@ describe('Job State actions', () => {
let mock;
beforeEach(() => {
- mockedState.stagesEndpoint = `${TEST_HOST}/endpoint.json`;
+ mockedState.job.pipeline = {
+ path: `${TEST_HOST}/endpoint.json/stages`,
+ };
mock = new MockAdapter(axios);
});
@@ -430,8 +432,10 @@ describe('Job State actions', () => {
});
describe('success', () => {
- it('dispatches requestStages and receiveStagesSuccess ', done => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]);
+ it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
+ mock
+ .onGet(`${TEST_HOST}/endpoint.json/stages`)
+ .replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } });
testAction(
fetchStages,
@@ -446,6 +450,10 @@ describe('Job State actions', () => {
payload: [{ id: 121212, name: 'build' }],
type: 'receiveStagesSuccess',
},
+ {
+ payload: { id: 121212, name: 'build' },
+ type: 'fetchJobsForStage',
+ },
],
done,
);
@@ -516,24 +524,10 @@ describe('Job State actions', () => {
});
});
- describe('setSelectedStage', () => {
- it('should commit SET_SELECTED_STAGE mutation ', done => {
- testAction(
- setSelectedStage,
- { name: 'build' },
- mockedState,
- [{ type: types.SET_SELECTED_STAGE, payload: { name: 'build' } }],
- [],
- done,
- );
- });
- });
-
describe('fetchJobsForStage', () => {
let mock;
beforeEach(() => {
- mockedState.stageJobsEndpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
@@ -542,20 +536,18 @@ describe('Job State actions', () => {
});
describe('success', () => {
- it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageSuccess ', done => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]);
+ it('dispatches requestJobsForStage and receiveJobsForStageSuccess ', done => {
+ mock
+ .onGet(`${TEST_HOST}/jobs.json`)
+ .replyOnce(200, { latest_statuses: [{ id: 121212, name: 'build' }], retried: [] });
testAction(
fetchJobsForStage,
- null,
+ { dropdown_path: `${TEST_HOST}/jobs.json` },
mockedState,
[],
[
{
- type: 'setSelectedStage',
- payload: null,
- },
- {
type: 'requestJobsForStage',
},
{
@@ -570,21 +562,17 @@ describe('Job State actions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+ mock.onGet(`${TEST_HOST}/jobs.json`).reply(500);
});
- it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageError', done => {
+ it('dispatches requestJobsForStage and receiveJobsForStageError', done => {
testAction(
fetchJobsForStage,
- null,
+ { dropdown_path: `${TEST_HOST}/jobs.json` },
mockedState,
[],
[
{
- payload: null,
- type: 'setSelectedStage',
- },
- {
type: 'requestJobsForStage',
},
{
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js
new file mode 100644
index 00000000000..63ef4135d83
--- /dev/null
+++ b/spec/javascripts/jobs/store/getters_spec.js
@@ -0,0 +1,121 @@
+import * as getters from '~/jobs/store/getters';
+import state from '~/jobs/store/state';
+
+describe('Job Store Getters', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('headerActions', () => {
+ describe('with new issue path', () => {
+ it('returns an array with action to create a new issue', () => {
+ localState.job.new_issue_path = 'issues/new';
+
+ expect(getters.headerActions(localState)).toEqual([
+ {
+ label: 'New issue',
+ path: localState.job.new_issue_path,
+ cssClass:
+ 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
+ type: 'link',
+ },
+ ]);
+ });
+ });
+
+ describe('without new issue path', () => {
+ it('returns an empty array', () => {
+ expect(getters.headerActions(localState)).toEqual([]);
+ });
+ });
+ });
+
+ describe('headerTime', () => {
+ describe('when the job has started key', () => {
+ it('returns started key', () => {
+ const started = '2018-08-31T16:20:49.023Z';
+ localState.job.started = started;
+
+ expect(getters.headerTime(localState)).toEqual(started);
+ });
+ });
+
+ describe('when the job does not have started key', () => {
+ it('returns created_at key', () => {
+ const created = '2018-08-31T16:20:49.023Z';
+ localState.job.created_at = created;
+ expect(getters.headerTime(localState)).toEqual(created);
+ });
+ });
+ });
+
+ describe('shouldRenderCalloutMessage', () => {
+ describe('with status and callout message', () => {
+ it('returns true', () => {
+ localState.job.callout_message = 'Callout message';
+ localState.job.status = { icon: 'passed' };
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true);
+ });
+ });
+
+ describe('without status & with callout message', () => {
+ it('returns false', () => {
+ localState.job.callout_message = 'Callout message';
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
+ });
+ });
+
+ describe('with status & without callout message', () => {
+ it('returns false', () => {
+ localState.job.status = { icon: 'passed' };
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('jobHasStarted', () => {
+ describe('when started equals false', () => {
+ it('returns false', () => {
+ localState.job.started = false;
+ expect(getters.jobHasStarted(localState)).toEqual(false);
+ });
+ });
+
+ describe('when started equals string', () => {
+ it('returns true', () => {
+ localState.job.started = '2018-08-31T16:20:49.023Z';
+ expect(getters.jobHasStarted(localState)).toEqual(true);
+ });
+ });
+ });
+
+ describe('hasEnvironment', () => {
+ describe('without `deployment_status`', () => {
+ it('returns false', () => {
+ expect(getters.hasEnvironment(localState)).toEqual(false);
+ });
+ });
+ describe('with an empty object for `deployment_status`', () => {
+ it('returns false', () => {
+ localState.job.deployment_status = {};
+ expect(getters.hasEnvironment(localState)).toEqual(false);
+ });
+ });
+ describe('when `deployment_status` is defined and not empty', () => {
+ it('returns true', () => {
+ localState.job.deployment_status = {
+ status: 'creating',
+ environment: {
+ last_deployment: {},
+ },
+ };
+
+ expect(getters.hasEnvironment(localState)).toEqual(true);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 2a01bd85520..40b5f009ceb 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -133,4 +133,29 @@ describe('noteable_discussion component', () => {
});
});
});
+
+ describe('componentData', () => {
+ it('should return first note object for placeholder note', () => {
+ const data = {
+ isPlaceholderNote: true,
+ notes: [
+ { body: 'hello world!' },
+ ],
+ };
+
+ const note = vm.componentData(data);
+ expect(note).toEqual(data.notes[0]);
+ });
+
+ it('should return given note for nonplaceholder notes', () => {
+ const data = {
+ notes: [
+ { id: 12 },
+ ],
+ };
+
+ const note = vm.componentData(data);
+ expect(note).toEqual(data);
+ });
+ });
});
diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
index 9dff52a9d49..0e30759c41d 100644
--- a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
@@ -8,7 +8,10 @@ describe('Issuable Time Tracker', () => {
let initialData;
let vm;
- const initTimeTrackingComponent = opts => {
+ const initTimeTrackingComponent = ({ timeEstimate,
+ timeSpent,
+ timeEstimateHumanReadable,
+ timeSpentHumanReadable }) => {
setFixtures(`
<div>
<div id="mock-container"></div>
@@ -16,10 +19,10 @@ describe('Issuable Time Tracker', () => {
`);
initialData = {
- time_estimate: opts.timeEstimate,
- time_spent: opts.timeSpent,
- human_time_estimate: opts.timeEstimateHumanReadable,
- human_time_spent: opts.timeSpentHumanReadable,
+ timeEstimate,
+ timeSpent,
+ humanTimeEstimate: timeEstimateHumanReadable,
+ humanTimeSpent: timeSpentHumanReadable,
rootPath: '/',
};
@@ -43,8 +46,8 @@ describe('Issuable Time Tracker', () => {
describe('Initialization', () => {
beforeEach(() => {
initTimeTrackingComponent({
- timeEstimate: 100000,
- timeSpent: 5000,
+ timeEstimate: 10000, // 2h 46m
+ timeSpent: 5000, // 1h 23m
timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '1h 23m',
});
@@ -56,14 +59,14 @@ describe('Issuable Time Tracker', () => {
it('should correctly set timeEstimate', done => {
Vue.nextTick(() => {
- expect(vm.timeEstimate).toBe(initialData.time_estimate);
+ expect(vm.timeEstimate).toBe(initialData.timeEstimate);
done();
});
});
it('should correctly set time_spent', done => {
Vue.nextTick(() => {
- expect(vm.timeSpent).toBe(initialData.time_spent);
+ expect(vm.timeSpent).toBe(initialData.timeSpent);
done();
});
});
@@ -74,8 +77,8 @@ describe('Issuable Time Tracker', () => {
describe('Comparison pane', () => {
beforeEach(() => {
initTimeTrackingComponent({
- timeEstimate: 100000,
- timeSpent: 5000,
+ timeEstimate: 100000, // 1d 3h
+ timeSpent: 5000, // 1h 23m
timeEstimateHumanReadable: '',
timeSpentHumanReadable: '',
});
@@ -106,8 +109,8 @@ describe('Issuable Time Tracker', () => {
});
it('should display the remaining meter with the correct background color when over estimate', done => {
- vm.time_estimate = 100000;
- vm.time_spent = 20000000;
+ vm.timeEstimate = 10000; // 2h 46m
+ vm.timeSpent = 20000000; // 231 days
Vue.nextTick(() => {
expect(vm.$el.querySelector('.time-tracking-comparison-pane .progress[variant="danger"]')).not.toBeNull();
done();
@@ -119,7 +122,7 @@ describe('Issuable Time Tracker', () => {
describe('Estimate only pane', () => {
beforeEach(() => {
initTimeTrackingComponent({
- timeEstimate: 100000,
+ timeEstimate: 10000, // 2h 46m
timeSpent: 0,
timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '',
@@ -142,7 +145,7 @@ describe('Issuable Time Tracker', () => {
beforeEach(() => {
initTimeTrackingComponent({
timeEstimate: 0,
- timeSpent: 5000,
+ timeSpent: 5000, // 1h 23m
timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '1h 23m',
});
diff --git a/spec/javascripts/ide/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
index 7308219f705..5b1038840c7 100644
--- a/spec/javascripts/ide/components/changed_file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
-import changedFileIcon from '~/ide/components/changed_file_icon.vue';
+import changedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import createComponent from 'spec/helpers/vue_mount_component_helper';
-describe('IDE changed file icon', () => {
+describe('Changed file icon', () => {
let vm;
beforeEach(() => {
@@ -33,14 +33,14 @@ describe('IDE changed file icon', () => {
});
describe('changedIconClass', () => {
- it('includes ide-file-modified when not a temp file', () => {
- expect(vm.changedIconClass).toContain('ide-file-modified');
+ it('includes file-modified when not a temp file', () => {
+ expect(vm.changedIconClass).toContain('file-modified');
});
- it('includes ide-file-addition when a temp file', () => {
+ it('includes file-addition when a temp file', () => {
vm.file.tempFile = true;
- expect(vm.changedIconClass).toContain('ide-file-addition');
+ expect(vm.changedIconClass).toContain('file-addition');
});
});
});
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 0735ebd6dcb..5dce3fcbcb6 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
+ include GitHelpers
+
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
@@ -9,11 +11,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
let(:merge_request) { merge_requests.create!(iid: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master').becomes(MergeRequest) }
let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff }
let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
- let(:rugged) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
- end
+ let(:rugged) { rugged_repo(project.repository) }
before do
allow_any_instance_of(MergeRequestDiff)
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 9095ffbfd52..1bd077ddbdf 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -1,9 +1,11 @@
require 'spec_helper'
describe Gitlab::Conflict::File do
+ include GitHelpers
+
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
- let(:rugged) { Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged } }
+ let(:rugged) { rugged_repo(repository) }
let(:their_commit) { rugged.branches['conflict-start'].target }
let(:our_commit) { rugged.branches['conflict-resolvable'].target }
let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) }
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index ea49502ae2e..b243f0dacae 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -4,6 +4,9 @@ require "spec_helper"
describe Gitlab::Git::Blob, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:rugged) do
+ Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
+ end
describe 'initialize' do
let(:blob) { Gitlab::Git::Blob.new(name: 'test') }
@@ -139,9 +142,7 @@ describe Gitlab::Git::Blob, :seed_helper do
it 'limits the size of a large file' do
blob_size = Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE + 1
buffer = Array.new(blob_size, 0)
- rugged_blob = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Rugged::Blob.from_buffer(repository.rugged, buffer.join(''))
- end
+ rugged_blob = Rugged::Blob.from_buffer(rugged, buffer.join(''))
blob = Gitlab::Git::Blob.raw(repository, rugged_blob)
expect(blob.size).to eq(blob_size)
@@ -156,9 +157,7 @@ describe Gitlab::Git::Blob, :seed_helper do
context 'when sha references a tree' do
it 'returns nil' do
- tree = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.rev_parse('master^{tree}')
- end
+ tree = rugged.rev_parse('master^{tree}')
blob = Gitlab::Git::Blob.raw(repository, tree.oid)
@@ -262,11 +261,7 @@ describe Gitlab::Git::Blob, :seed_helper do
end
describe '.batch_lfs_pointers' do
- let(:tree_object) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.rev_parse('master^{tree}')
- end
- end
+ let(:tree_object) { rugged.rev_parse('master^{tree}') }
let(:non_lfs_blob) do
Gitlab::Git::Blob.find(
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 79ccbb79966..0df282d0ae3 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -3,9 +3,7 @@ require "spec_helper"
describe Gitlab::Git::Branch, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:rugged) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged
- end
+ Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path))
end
subject { repository.branches }
@@ -74,9 +72,7 @@ describe Gitlab::Git::Branch, :seed_helper do
Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
let(:params) do
- parents = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- [repository.rugged.head.target]
- end
+ parents = [rugged.head.target]
tree = parents.first.tree
{
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 2718a3c5e49..9ef27081f98 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -1,19 +1,17 @@
require "spec_helper"
describe Gitlab::Git::Commit, :seed_helper do
+ include GitHelpers
+
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
- let(:rugged_commit) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.lookup(SeedRepo::Commit::ID)
- end
+ let(:rugged_repo) do
+ Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
end
+ let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
+ let(:rugged_commit) { rugged_repo.lookup(SeedRepo::Commit::ID) }
+
describe "Commit info" do
before do
- repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- end
-
@committer = {
email: 'mike@smith.com',
name: "Mike Smith",
@@ -26,12 +24,12 @@ describe Gitlab::Git::Commit, :seed_helper do
time: Time.now
}
- @parents = [repo.head.target]
+ @parents = [rugged_repo.head.target]
@gitlab_parents = @parents.map { |c| described_class.find(repository, c.oid) }
@tree = @parents.first.tree
sha = Rugged::Commit.create(
- repo,
+ rugged_repo,
author: @author,
committer: @committer,
tree: @tree,
@@ -40,7 +38,7 @@ describe Gitlab::Git::Commit, :seed_helper do
update_ref: "HEAD"
)
- @raw_commit = repo.lookup(sha)
+ @raw_commit = rugged_repo.lookup(sha)
@commit = described_class.find(repository, sha)
end
@@ -61,10 +59,7 @@ describe Gitlab::Git::Commit, :seed_helper do
after do
# Erase the new commit so other tests get the original repo
- repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- end
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
+ rugged_repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
end
@@ -120,9 +115,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '.find' do
it "should return first head commit if without params" do
expect(described_class.last(repository).id).to eq(
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.head.target.oid
- end
+ rugged_repo.head.target.oid
)
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 27d803e0117..8a4415506c4 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -64,9 +64,22 @@ EOT
end
end
- context 'using a Rugged::Patch' do
+ context 'using a GitalyClient::Diff' do
+ let(:gitaly_diff) do
+ Gitlab::GitalyClient::Diff.new(
+ to_path: ".gitmodules",
+ from_path: ".gitmodules",
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '357406f3075a57708d0163752905cc1576fceacc',
+ to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
+ patch: raw_patch
+ )
+ end
+ let(:diff) { described_class.new(gitaly_diff) }
+
context 'with a small diff' do
- let(:diff) { described_class.new(gitaly_diff) }
+ let(:raw_patch) { @raw_diff_hash[:diff] }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
@@ -78,16 +91,17 @@ EOT
end
context 'using a diff that is too large' do
- it 'prunes the diff' do
- gitaly_diff.too_large = true
- diff = described_class.new(gitaly_diff)
+ let(:raw_patch) { 'a' * 204800 }
+ it 'prunes the diff' do
expect(diff.diff).to be_empty
expect(diff).to be_too_large
end
end
context 'using a collapsable diff that is too large' do
+ let(:raw_patch) { 'a' * 204800 }
+
it 'prunes the diff as a large diff instead of as a collapsed diff' do
gitaly_diff.too_large = true
diff = described_class.new(gitaly_diff, expanded: false)
@@ -97,43 +111,6 @@ EOT
expect(diff).not_to be_collapsed
end
end
- end
-
- context 'using a GitalyClient::Diff' do
- let(:diff) do
- described_class.new(
- Gitlab::GitalyClient::Diff.new(
- to_path: ".gitmodules",
- from_path: ".gitmodules",
- old_mode: 0100644,
- new_mode: 0100644,
- from_id: '357406f3075a57708d0163752905cc1576fceacc',
- to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
- patch: raw_patch
- )
- )
- end
-
- context 'with a small diff' do
- let(:raw_patch) { @raw_diff_hash[:diff] }
-
- it 'initializes the diff' do
- expect(diff.to_hash).to eq(@raw_diff_hash)
- end
-
- it 'does not prune the diff' do
- expect(diff).not_to be_too_large
- end
- end
-
- context 'using a diff that is too large' do
- let(:raw_patch) { 'a' * 204800 }
-
- it 'prunes the diff' do
- expect(diff.diff).to be_empty
- expect(diff).to be_too_large
- end
- end
context 'when the patch passed is not UTF-8-encoded' do
let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index d02536a2fb4..51eb997a325 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -19,7 +19,10 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
+ let(:repository_rugged) { Rugged::Repository.new(repository_path) }
let(:storage_path) { TestEnv.repos_path }
let(:user) { build(:user) }
@@ -71,7 +74,6 @@ describe Gitlab::Git::Repository, :seed_helper do
describe "Respond to" do
subject { repository }
- it { is_expected.to respond_to(:rugged) }
it { is_expected.to respond_to(:root_ref) }
it { is_expected.to respond_to(:tags) }
end
@@ -91,57 +93,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe "#rugged" do
- describe 'when storage is broken', :broken_storage do
- it 'raises a storage exception when storage is not available' do
- broken_repo = described_class.new('broken', 'a/path.git', '')
-
- expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible)
- end
- end
-
- it 'raises a no repository exception when there is no repo' do
- broken_repo = described_class.new('default', 'a/path.git', '')
-
- expect do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access { broken_repo.rugged }
- end.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- describe 'alternates keyword argument' do
- context 'with no Git env stored' do
- before do
- allow(Gitlab::Git::HookEnv).to receive(:all).and_return({})
- end
-
- it "is passed an empty array" do
- expect(Rugged::Repository).to receive(:new).with(repository_path, alternates: [])
-
- repository_rugged
- end
- end
-
- context 'with absolute and relative Git object dir envvars stored' do
- before do
- allow(Gitlab::Git::HookEnv).to receive(:all).and_return({
- 'GIT_OBJECT_DIRECTORY_RELATIVE' => './objects/foo',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['./objects/bar', './objects/baz'],
- 'GIT_OBJECT_DIRECTORY' => 'ignored',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[ignored ignored],
- 'GIT_OTHER' => 'another_env'
- })
- end
-
- it "is passed the relative object dir envvars after being converted to absolute ones" do
- alternates = %w[foo bar baz].map { |d| File.join(repository_path, './objects', d) }
- expect(Rugged::Repository).to receive(:new).with(repository_path, alternates: alternates)
-
- repository_rugged
- end
- end
- end
- end
-
describe '#branch_names' do
subject { repository.branch_names }
@@ -284,7 +235,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#submodule_url_for' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:ref) { 'master' }
def submodule_url(path)
@@ -336,7 +286,7 @@ describe Gitlab::Git::Repository, :seed_helper do
it { expect(repository.has_local_branches?).to eq(true) }
context 'mutable' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:repository) { mutable_repository }
after do
ensure_seeds
@@ -369,7 +319,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe "#delete_branch" do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:repository) { mutable_repository }
after do
ensure_seeds
@@ -393,7 +343,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe "#create_branch" do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:repository) { mutable_repository }
after do
ensure_seeds
@@ -418,39 +368,33 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#delete_refs' do
- let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- def repo_rugged
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repo.rugged
- end
- end
+ let(:repository) { mutable_repository }
after do
ensure_seeds
end
it 'deletes the ref' do
- repo.delete_refs('refs/heads/feature')
+ repository.delete_refs('refs/heads/feature')
- expect(repo_rugged.references['refs/heads/feature']).to be_nil
+ expect(repository_rugged.references['refs/heads/feature']).to be_nil
end
it 'deletes all refs' do
refs = %w[refs/heads/wip refs/tags/v1.1.0]
- repo.delete_refs(*refs)
+ repository.delete_refs(*refs)
refs.each do |ref|
- expect(repo_rugged.references[ref]).to be_nil
+ expect(repository_rugged.references[ref]).to be_nil
end
end
it 'does not fail when deleting an empty list of refs' do
- expect { repo.delete_refs(*[]) }.not_to raise_error
+ expect { repository.delete_refs(*[]) }.not_to raise_error
end
it 'raises an error if it failed' do
- expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
+ expect { repository.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
end
end
@@ -528,9 +472,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
def new_repository_path
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- new_repository.path
- end
+ File.join(TestEnv.repos_path, new_repository.relative_path)
end
end
@@ -577,18 +519,16 @@ describe Gitlab::Git::Repository, :seed_helper do
Gitlab::Git::Commit.find(repository, @rename_commit_id)
end
- before(:context) do
+ before do
# Add new commits so that there's a renamed file in the commit history
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- @commit_with_old_name_id = new_commit_edit_old_file(repo).oid
- @rename_commit_id = new_commit_move_file(repo).oid
- @commit_with_new_name_id = new_commit_edit_new_file(repo).oid
+ @commit_with_old_name_id = new_commit_edit_old_file(repository_rugged).oid
+ @rename_commit_id = new_commit_move_file(repository_rugged).oid
+ @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged).oid
end
- after(:context) do
+ after do
# Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
+ repository_rugged.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
context "where 'follow' == true" do
@@ -1010,12 +950,10 @@ describe Gitlab::Git::Repository, :seed_helper do
subject { repository.branches }
context 'with local and remote branches' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
before do
- create_remote_branch(repository, 'joe', 'remote_branch', 'master')
+ create_remote_branch('joe', 'remote_branch', 'master')
repository.create_branch('local_branch', 'master')
end
@@ -1038,12 +976,10 @@ describe Gitlab::Git::Repository, :seed_helper do
end
context 'with local and remote branches' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
before do
- create_remote_branch(repository, 'joe', 'remote_branch', 'master')
+ create_remote_branch('joe', 'remote_branch', 'master')
repository.create_branch('local_branch', 'master')
end
@@ -1303,24 +1239,24 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#local_branches' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
+ let(:repository) { mutable_repository }
+
+ before do
+ create_remote_branch('joe', 'remote_branch', 'master')
+ repository.create_branch('local_branch', 'master')
end
- after(:all) do
+ after do
ensure_seeds
end
it 'returns the local branches' do
- create_remote_branch(@repo, 'joe', 'remote_branch', 'master')
- @repo.create_branch('local_branch', 'master')
-
- expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
- expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
+ expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
+ expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end
it 'returns a Branch with UTF-8 fields' do
- branches = @repo.local_branches.to_a
+ branches = repository.local_branches.to_a
expect(branches.size).to be > 0
branches.each do |branch|
expect(branch.name).to be_utf8
@@ -1331,11 +1267,11 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'gets the branches from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:local_branches)
.and_return([])
- @repo.local_branches
+ repository.local_branches
end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :local_branches do
- subject { @repo.local_branches }
+ subject { repository.local_branches }
end
end
@@ -1391,8 +1327,7 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#fetch_source_branch!' do
let(:local_ref) { 'refs/merge-requests/1/head' }
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- let(:source_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:source_repository) { mutable_repository }
after do
ensure_seeds
@@ -1401,7 +1336,8 @@ describe Gitlab::Git::Repository, :seed_helper do
context 'when the branch exists' do
context 'when the commit does not exist locally' do
let(:source_branch) { 'new-branch-for-fetch-source-branch' }
- let(:source_rugged) { Gitlab::GitalyClient::StorageSettings.allow_disk_access { source_repository.rugged } }
+ let(:source_path) { File.join(TestEnv.repos_path, source_repository.relative_path) }
+ let(:source_rugged) { Rugged::Repository.new(source_path) }
let(:new_oid) { new_commit_edit_old_file(source_rugged).oid }
before do
@@ -1513,8 +1449,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#set_config' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- let(:rugged) { repository_rugged }
+ let(:repository) { mutable_repository }
let(:entries) do
{
'test.foo1' => 'bla bla',
@@ -1526,19 +1461,18 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'can set config settings' do
expect(repository.set_config(entries)).to be_nil
- expect(rugged.config['test.foo1']).to eq('bla bla')
- expect(rugged.config['test.foo2']).to eq('1234')
- expect(rugged.config['test.foo3']).to eq('true')
+ expect(repository_rugged.config['test.foo1']).to eq('bla bla')
+ expect(repository_rugged.config['test.foo2']).to eq('1234')
+ expect(repository_rugged.config['test.foo3']).to eq('true')
end
after do
- entries.keys.each { |k| rugged.config.delete(k) }
+ entries.keys.each { |k| repository_rugged.config.delete(k) }
end
end
describe '#delete_config' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- let(:rugged) { repository_rugged }
+ let(:repository) { mutable_repository }
let(:entries) do
{
'test.foo1' => 'bla bla',
@@ -1549,21 +1483,19 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'can delete config settings' do
entries.each do |key, value|
- rugged.config[key] = value
+ repository_rugged.config[key] = value
end
expect(repository.delete_config(*%w[does.not.exist test.foo1 test.foo2])).to be_nil
- config_keys = rugged.config.each_key.to_a
+ config_keys = repository_rugged.config.each_key.to_a
expect(config_keys).not_to include('test.foo1')
expect(config_keys).not_to include('test.foo2')
end
end
describe '#merge' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
let(:target_branch) { 'test-merge-target-branch' }
@@ -1602,9 +1534,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#ff_merge' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:target_branch) { 'test-ff-target-branch' }
@@ -1667,9 +1597,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#delete_all_refs_except' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
before do
repository.write_ref("refs/delete/a", "0b4bc9a49b562e85de7cc9e834518ea6828729b9")
@@ -1693,12 +1621,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe 'remotes' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
- let(:rugged) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged }
- end
+ let(:repository) { mutable_repository }
let(:remote_name) { 'my-remote' }
let(:url) { 'http://my-repo.git' }
@@ -1711,26 +1634,26 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'added the remote' do
begin
- rugged.remotes.delete(remote_name)
+ repository_rugged.remotes.delete(remote_name)
rescue Rugged::ConfigError
end
repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
- expect(rugged.remotes[remote_name]).not_to be_nil
- expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
- expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true')
- expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
+ expect(repository_rugged.remotes[remote_name]).not_to be_nil
+ expect(repository_rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
+ expect(repository_rugged.config["remote.#{remote_name}.prune"]).to eq('true')
+ expect(repository_rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
end
end
describe '#remove_remote' do
it 'removes the remote' do
- rugged.remotes.create(remote_name, url)
+ repository_rugged.remotes.create(remote_name, url)
repository.remove_remote(remote_name)
- expect(rugged.remotes[remote_name]).to be_nil
+ expect(repository_rugged.remotes[remote_name]).to be_nil
end
end
end
@@ -1901,13 +1824,11 @@ describe Gitlab::Git::Repository, :seed_helper do
end
context 'when the diff contains a rename' do
- let(:repo) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged }
- let(:end_sha) { new_commit_move_file(repo).oid }
+ let(:end_sha) { new_commit_move_file(repository_rugged).oid }
after do
# Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- repo.references.update('refs/heads/master', SeedRepo::LastCommit::ID)
+ repository_rugged.references.update('refs/heads/master', SeedRepo::LastCommit::ID)
end
it 'does not include the renamed file in the sparse checkout' do
@@ -1954,10 +1875,9 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- def create_remote_branch(repository, remote_name, branch_name, source_branch_name)
+ def create_remote_branch(remote_name, branch_name, source_branch_name)
source_branch = repository.branches.find { |branch| branch.name == source_branch_name }
- rugged = repository_rugged
- rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
+ repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
end
# Build the options hash that's passed to Rugged::Commit#create
@@ -2035,16 +1955,4 @@ describe Gitlab::Git::Repository, :seed_helper do
line.split("\t").last
end
end
-
- def repository_rugged
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged
- end
- end
-
- def repository_path
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.path
- end
- end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index dbd64c4bec0..e7da5565c26 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GitAccess do
include TermsHelper
+ include GitHelpers
let(:user) { create(:user) }
@@ -736,21 +737,19 @@ describe Gitlab::GitAccess do
def merge_into_protected_branch
@protected_branch_merge_commit ||= begin
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.add_branch(user, unprotected_branch, 'feature')
- rugged = project.repository.rugged
- target_branch = rugged.rev_parse('feature')
- source_branch = project.repository.create_file(
- user,
- 'filename',
- 'This is the file content',
- message: 'This is a good commit message',
- branch_name: unprotected_branch)
- author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
-
- merge_index = rugged.merge_commits(target_branch, source_branch)
- Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
- end
+ project.repository.add_branch(user, unprotected_branch, 'feature')
+ rugged = rugged_repo(project.repository)
+ target_branch = rugged.rev_parse('feature')
+ source_branch = project.repository.create_file(
+ user,
+ 'filename',
+ 'This is the file content',
+ message: 'This is a good commit message',
+ branch_name: unprotected_branch)
+ author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
+
+ merge_index = rugged.merge_commits(target_branch, source_branch)
+ Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
end
end
diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
index 5f67fe6b952..d82c9c28da0 100644
--- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
@@ -39,6 +39,10 @@ describe Gitlab::GitalyClient::WikiService do
expect(wiki_page.title).to eq('My Page')
expect(wiki_page.raw_data).to eq('ab')
expect(wiki_page_version.format).to eq('markdown')
+
+ expect(wiki_page.title).to be_utf8
+ expect(wiki_page.path).to be_utf8
+ expect(wiki_page.name).to be_utf8
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 1ec1fe10744..d669c42ab4a 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -46,6 +46,7 @@ describe Gitlab::UsageData do
git
database
avg_cycle_analytics
+ web_ide_commits
))
end
diff --git a/spec/lib/gitlab/web_ide_commits_counter_spec.rb b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
new file mode 100644
index 00000000000..c51889a1c63
--- /dev/null
+++ b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do
+ describe '.increment' do
+ it 'increments the web ide commits counter by 1' do
+ expect do
+ described_class.increment
+ end.to change { described_class.total_count }.from(0).to(1)
+ end
+ end
+
+ describe '.total_count' do
+ it 'returns the total amount of web ide commits' do
+ expect(described_class.total_count).to eq(0)
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 48f4e53b93e..666d7e69f89 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -746,7 +746,7 @@ describe MergeRequest do
end
describe "#wipless_title" do
- ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', '[WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
it "removes the '#{wip_prefix}' prefix" do
wipless_title = subject.title
subject.title = "#{wip_prefix}#{subject.title}"
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 22dc81acfda..8913644a3ce 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Namespace do
include ProjectForksHelper
+ include GitHelpers
let!(:namespace) { create(:namespace) }
let(:gitlab_shell) { Gitlab::Shell.new }
@@ -361,9 +362,7 @@ describe Namespace do
end
def project_rugged(project)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
+ rugged_repo(project.repository)
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d15ba89efb5..8b71919544e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Project do
include ProjectForksHelper
+ include GitHelpers
describe 'associations' do
it { is_expected.to belong_to(:group) }
@@ -4039,8 +4040,6 @@ describe Project do
end
def rugged_config
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged.config
- end
+ rugged_repo(project.repository).config
end
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 269d5deca20..3d316fb3c5b 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe RemoteMirror do
+ include GitHelpers
+
describe 'URL validation' do
context 'with a valid URL' do
it 'should be valid' do
@@ -74,9 +76,7 @@ describe RemoteMirror do
mirror.update_attribute(:url, 'http://foo:baz@test.com')
- config = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repo.raw_repository.rugged.config
- end
+ config = rugged_repo(repo).config
expect(config["remote.#{mirror.remote_name}.url"]).to eq('http://foo:baz@test.com')
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 784d17e271e..77e549d9528 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
describe Repository do
include RepoHelpers
+ include GitHelpers
+
TestBlob = Struct.new(:path)
let(:project) { create(:project, :repository) }
@@ -137,9 +139,7 @@ describe Repository do
options = { message: 'test tag message\n',
tagger: { name: 'John Smith', email: 'john@gmail.com' } }
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)
- end
+ rugged_repo(repository).tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)
double_first = double(committed_date: Time.now - 1.second)
double_last = double(committed_date: Time.now)
@@ -151,9 +151,7 @@ describe Repository do
it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) }
after do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.tags.delete(annotated_tag_name)
- end
+ rugged_repo(repository).tags.delete(annotated_tag_name)
end
end
end
@@ -1678,10 +1676,7 @@ describe Repository do
it 'returns the number of branches' do
expect(repository.branch_count).to be_an(Integer)
- # NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
- rugged_count = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.raw_repository.rugged.branches.count
- end
+ rugged_count = rugged_repo(repository).branches.count
expect(repository.branch_count).to eq(rugged_count)
end
@@ -1691,10 +1686,7 @@ describe Repository do
it 'returns the number of tags' do
expect(repository.tag_count).to be_an(Integer)
- # NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
- rugged_count = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.raw_repository.rugged.tags.count
- end
+ rugged_count = rugged_repo(repository).tags.count
expect(repository.tag_count).to eq(rugged_count)
end
@@ -2184,9 +2176,7 @@ describe Repository do
end
def create_remote_branch(remote_name, branch_name, target)
- rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged
- end
+ rugged = rugged_repo(repository)
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id)
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 63850939be1..b4732ec0cd5 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -77,7 +77,7 @@ describe WikiPage do
end
describe "#initialize" do
- context "when initialized with an existing gollum page" do
+ context "when initialized with an existing page" do
before do
create_page("test page", "test content")
@page = wiki.wiki.page(title: "test page")
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index f3fb88474a4..06ccf383362 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -278,6 +278,12 @@ describe API::Commits do
}
end
+ it 'does not increment the usage counters using access token authentication' do
+ expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment)
+
+ post api(url, user), valid_c_params
+ end
+
it 'a new file in project repo' do
post api(url, user), valid_c_params
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e987eee6e91..07d19e3ad29 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -81,6 +81,35 @@ describe API::MergeRequests do
let(:user2) { create(:user) }
it 'returns an array of all merge requests except unauthorized ones' do
+ get api('/merge_requests', user), scope: :all
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |mr| mr['id'] })
+ .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request_locked.id, merge_request2.id)
+ end
+
+ it "returns an array of no merge_requests when wip=yes" do
+ get api("/merge_requests", user), wip: 'yes'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it "returns an array of no merge_requests when wip=no" do
+ get api("/merge_requests", user), wip: 'no'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |mr| mr['id'] })
+ .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request_locked.id, merge_request2.id)
+ end
+
+ it 'does not return unauthorized merge requests' do
private_project = create(:project, :private)
merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch')
@@ -244,6 +273,15 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(404)
end
+ it "returns an array of no merge_requests when wip=yes" do
+ get api("/projects/#{project.id}/merge_requests", user), wip: 'yes'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
it 'returns merge_request by "iids" array' do
get api(endpoint_path, user), iids: [merge_request.iid, merge_request_closed.iid]
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index 92159e1e372..2699f6e7bcd 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe GitTagPushService do
include RepoHelpers
+ include GitHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -118,9 +119,7 @@ describe GitTagPushService do
before do
# Create the lightweight tag
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.raw_repository.rugged.tags.create(tag_name, newrev)
- end
+ rugged_repo(project.repository).tags.create(tag_name, newrev)
# Clear tag list cache
project.repository.expire_tags_cache
diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb
index 8ab09412f55..53bce15735c 100644
--- a/spec/services/merge_requests/squash_service_spec.rb
+++ b/spec/services/merge_requests/squash_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe MergeRequests::SquashService do
+ include GitHelpers
+
let(:service) { described_class.new(project, user, {}) }
let(:user) { project.owner }
let(:project) { create(:project, :repository) }
@@ -63,9 +65,7 @@ describe MergeRequests::SquashService do
end
it 'has the same diff as the merge request, but a different SHA' do
- rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
+ rugged = rugged_repo(project.repository)
mr_diff = rugged.diff(merge_request.diff_base_sha, merge_request.diff_head_sha)
squash_diff = rugged.diff(merge_request.diff_start_sha, squash_sha)
diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb
index cd52bc88f4c..4dd6c6dab86 100644
--- a/spec/services/projects/after_import_service_spec.rb
+++ b/spec/services/projects/after_import_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::AfterImportService do
+ include GitHelpers
+
subject { described_class.new(project) }
let(:project) { create(:project, :repository) }
@@ -53,7 +55,7 @@ describe Projects::AfterImportService do
end
def rugged
- Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged }
+ rugged_repo(repository)
end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index bb3f1501f0e..a80c8a7fe51 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::CreateService, '#execute' do
+ include GitHelpers
+
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create :user }
let(:opts) do
@@ -295,9 +297,7 @@ describe Projects::CreateService, '#execute' do
it 'writes project full path to .git/config' do
project = create_project(user, opts)
- rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
+ rugged = rugged_repo(project.repository)
expect(rugged.config['gitlab.fullpath']).to eq project.full_path
end
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index 5f67c325223..0e82194e9ee 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::HashedStorage::MigrateRepositoryService do
+ include GitHelpers
+
let(:gitlab_shell) { Gitlab::Shell.new }
let(:project) { create(:project, :legacy_storage, :repository, :wiki_repo) }
let(:legacy_storage) { Storage::LegacyProject.new(project) }
@@ -38,9 +40,7 @@ describe Projects::HashedStorage::MigrateRepositoryService do
it 'writes project full path to .git/config' do
service.execute
- rugged_config = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged.config['gitlab.fullpath']
- end
+ rugged_config = rugged_repo(project.repository).config['gitlab.fullpath']
expect(rugged_config).to eq project.full_path
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 92c5ac7354a..1411723fb9e 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::TransferService do
+ include GitHelpers
+
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) }
let(:group) { create(:group) }
@@ -291,8 +293,6 @@ describe Projects::TransferService do
end
def rugged_config
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged.config
- end
+ rugged_repo(project.repository).config
end
end
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index e0fceae88de..83035788a56 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -1,4 +1,6 @@
module CycleAnalyticsHelpers
+ include GitHelpers
+
def create_commit_referencing_issue(issue, branch_name: generate(:branch))
project.repository.add_branch(user, branch_name, 'master')
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
@@ -9,7 +11,7 @@ module CycleAnalyticsHelpers
oldrev = repository.commit(branch_name)&.sha || Gitlab::Git::BLANK_SHA
if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files)
- mock_gitaly_multi_action_dates(repository.raw, commit_time)
+ mock_gitaly_multi_action_dates(repository, commit_time)
end
commit_shas = Array.new(count) do |index|
@@ -118,18 +120,15 @@ module CycleAnalyticsHelpers
protected: false)
end
- def mock_gitaly_multi_action_dates(raw_repository, commit_time)
- allow(raw_repository).to receive(:multi_action).and_wrap_original do |m, *args|
+ def mock_gitaly_multi_action_dates(repository, commit_time)
+ allow(repository.raw).to receive(:multi_action).and_wrap_original do |m, *args|
new_date = commit_time || Time.now
branch_update = m.call(*args)
if branch_update.newrev
_, opts = args
- commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- rugged = raw_repository.rugged
- rugged.rev_parse(branch_update.newrev)
- end
+ commit = rugged_repo(repository).rev_parse(branch_update.newrev)
branch_update.newrev = commit.amend(
update_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{opts[:branch_name]}",
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
index fc92bc38561..99a7c39852e 100644
--- a/spec/support/helpers/git_helpers.rb
+++ b/spec/support/helpers/git_helpers.rb
@@ -1,6 +1,12 @@
# frozen_string_literal: true
module GitHelpers
+ def rugged_repo(repository)
+ path = File.join(TestEnv.repos_path, repository.disk_path + '.git')
+
+ Rugged::Repository.new(path)
+ end
+
def project_hook_exists?(project)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project_path = project.repository.raw_repository.path
diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
index 93c21a99e59..1190863d88e 100644
--- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
@@ -33,6 +33,14 @@ shared_examples "builds correct paths" do |**patterns|
it_behaves_like "matches the method pattern", :upload_path
end
+ describe "#relative_path" do
+ it 'is relative' do
+ skip 'Path not set, skipping.' unless subject.path
+
+ expect(Pathname.new(subject.relative_path)).to be_relative
+ end
+ end
+
describe ".absolute_path" do
it_behaves_like "matches the method pattern", :absolute_path do
let(:target) { subject.class }
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 496646dc623..e06a9ecb98b 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -18,161 +18,6 @@ describe 'projects/jobs/show' do
allow(view).to receive(:can?).and_return(true)
end
- describe 'environment info in job view' do
- context 'job with latest deployment' do
- let(:build) do
- create(:ci_build, :success, :trace_artifact, environment: 'staging')
- end
-
- before do
- create(:environment, name: 'staging')
- create(:deployment, deployable: build)
- end
-
- it 'shows deployment message' do
- expected_text = 'This job is the most recent deployment'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- end
- end
-
- context 'job with outdated deployment' do
- let(:build) do
- create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
- end
-
- let(:second_build) do
- create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
- end
-
- let(:environment) do
- create(:environment, name: 'staging', project: project)
- end
-
- let!(:first_deployment) do
- create(:deployment, environment: environment, deployable: build)
- end
-
- let!(:second_deployment) do
- create(:deployment, environment: environment, deployable: second_build)
- end
-
- it 'shows deployment message' do
- expected_text = 'This job is an out-of-date deployment ' \
- "to staging.\nView the most recent deployment ##{second_deployment.iid}."
- render
-
- expect(rendered).to have_css('.environment-information', text: expected_text)
- end
- end
-
- context 'job failed to deploy' do
- let(:build) do
- create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
- end
-
- let!(:environment) do
- create(:environment, name: 'staging', project: project)
- end
-
- it 'shows deployment message' do
- expected_text = 'The deployment of this job to staging did not succeed.'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- end
- end
-
- context 'job will deploy' do
- let(:build) do
- create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
- end
-
- context 'when environment exists' do
- let!(:environment) do
- create(:environment, name: 'staging', project: project)
- end
-
- it 'shows deployment message' do
- expected_text = 'This job is creating a deployment to staging'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- end
-
- context 'when it has deployment' do
- let!(:deployment) do
- create(:deployment, environment: environment)
- end
-
- it 'shows that deployment will be overwritten' do
- expected_text = 'This job is creating a deployment to staging'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- expect(rendered).to have_css(
- '.environment-information', text: 'latest deployment')
- end
- end
- end
-
- context 'when environment does not exist' do
- it 'shows deployment message' do
- expected_text = 'This job is creating a deployment to staging'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- expect(rendered).not_to have_css(
- '.environment-information', text: 'latest deployment')
- end
- end
- end
-
- context 'job that failed to deploy and environment has not been created' do
- let(:build) do
- create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
- end
-
- let!(:environment) do
- create(:environment, name: 'staging', project: project)
- end
-
- it 'shows deployment message' do
- expected_text = 'The deployment of this job to staging did not succeed'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- end
- end
-
- context 'job that will deploy and environment has not been created' do
- let(:build) do
- create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
- end
-
- let!(:environment) do
- create(:environment, name: 'staging', project: project)
- end
-
- it 'shows deployment message' do
- expected_text = 'This job is creating a deployment to staging'
- render
-
- expect(rendered).to have_css(
- '.environment-information', text: expected_text)
- expect(rendered).not_to have_css(
- '.environment-information', text: 'latest deployment')
- end
- end
- end
-
context 'when job is running' do
let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) }
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 30e67e67e0e..a159f24f876 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -3,6 +3,8 @@ require 'fileutils'
require 'spec_helper'
describe GitGarbageCollectWorker do
+ include GitHelpers
+
let(:project) { create(:project, :repository) }
let(:shell) { Gitlab::Shell.new }
let!(:lease_uuid) { SecureRandom.uuid }
@@ -197,9 +199,7 @@ describe GitGarbageCollectWorker do
# Create a new commit on a random new branch
def create_objects(project)
- rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
+ rugged = rugged_repo(project.repository)
old_commit = rugged.branches.first.target
new_commit_sha = Rugged::Commit.create(
rugged,
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index a653f6f926c..6ddb653d142 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe RepositoryRemoveRemoteWorker do
include ExclusiveLeaseHelpers
+ include GitHelpers
describe '#perform' do
let!(:project) { create(:project, :repository) }
@@ -50,9 +51,7 @@ describe RepositoryRemoveRemoteWorker do
end
def create_remote_branch(remote_name, branch_name, target)
- rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
+ rugged = rugged_repo(project.repository)
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id)
end
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index b36aabb9b3d..6adcb28f736 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -438,7 +438,6 @@ github-linguist,5.3.3,MIT
github-markup,1.7.0,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
gitlab-gollum-lib,4.2.7.5,MIT
-gitlab-gollum-rugged_adapter,0.4.4.1,MIT
gitlab-grit,2.8.2,MIT
gitlab-markup,1.6.4,MIT
gitlab_omniauth-ldap,2.0.4,MIT