diff options
author | jerasmus <jerasmus@gitlab.com> | 2018-10-15 12:42:24 +0200 |
---|---|---|
committer | jerasmus <jerasmus@gitlab.com> | 2018-10-15 12:42:24 +0200 |
commit | 58f29d5f855ea2e1411b99804a74710483a13f90 (patch) | |
tree | 0b06ecd3c0bb1bcf3d3c6660c2bc49eefaff08b2 | |
parent | 0572da24c990fc01d88acfbd32728221e3e3a711 (diff) | |
parent | a9827357186e38e5732d8dae23d9d02b1f4c7218 (diff) | |
download | gitlab-ce-48746-fix-files-uploaded-in-base64.tar.gz |
Merge branch 'master' into 48746-fix-files-uploaded-in-base6448746-fix-files-uploaded-in-base64
258 files changed, 1343 insertions, 290 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 242e7615211..0f4018326a1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -50,7 +50,8 @@ Style/FrozenStringLiteralComment: - 'danger/**/*' - 'db/**/*' - 'ee/**/*' - - 'lib/**/*' + - 'lib/gitlab/**/*' + - 'lib/tasks/**/*' - 'qa/**/*' - 'rubocop/**/*' - 'scripts/**/*' @@ -84,6 +85,7 @@ Naming/FileName: - EE - JSON - LDAP + - SAML - IO - HMAC - QA diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 9084fa2f716..6085e946503 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.1.0 +1.2.1 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index d127a0ff9f1..9da0a092a0d 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -8.3.3 +8.4.0
\ No newline at end of file diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index a1069985178..6e7b5eb5526 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -1,6 +1,6 @@ <script> import _ from 'underscore'; -import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg'; +import helmInstallIllustration from '@gitlab-org/gitlab-svgs/dist/illustrations/kubernetes-installation.svg'; import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png'; import gitlabLogo from 'images/cluster_app_logos/gitlab.png'; import helmLogo from 'images/cluster_app_logos/helm.png'; diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 15b37243030..dcf1057eb84 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -20,6 +20,11 @@ export default { Tooltip, }, props: { + discussionPath: { + type: String, + required: false, + default: '', + }, diffFile: { type: Object, required: true, @@ -65,8 +70,7 @@ export default { if (this.diffFile.submodule) { return this.diffFile.submoduleTreeUrl || this.diffFile.submoduleLink; } - - return `#${this.diffFile.fileHash}`; + return this.discussionPath; }, filePath() { if (this.diffFile.submodule) { @@ -152,7 +156,7 @@ export default { v-once ref="titleWrapper" :href="titleLink" - class="append-right-4" + class="append-right-4 js-title-wrapper" > <file-icon :file-name="filePath" diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js index ae8930c8968..1c5c35071de 100644 --- a/app/assets/javascripts/diffs/store/modules/diff_state.js +++ b/app/assets/javascripts/diffs/store/modules/diff_state.js @@ -1,5 +1,6 @@ import Cookies from 'js-cookie'; import { getParameterValues } from '~/lib/utils/url_utility'; +import bp from '~/breakpoints'; import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants'; const viewTypeFromQueryString = getParameterValues('view')[0]; @@ -20,6 +21,7 @@ export default () => ({ diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType, tree: [], treeEntries: {}, - showTreeList: storedTreeShow === null ? true : storedTreeShow === 'true', + showTreeList: + storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true', currentDiffFileId: '', }); diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index 047e55866ce..4e8d3ad24cc 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -18,7 +18,7 @@ StuckBlock, }, props: { - runnerHelpUrl: { + runnerSettingsUrl: { type: String, required: false, default: null, @@ -30,7 +30,7 @@ 'headerActions', 'headerTime', 'shouldRenderCalloutMessage', - 'jobHasStarted', + 'shouldRenderTriggeredLabel', 'hasEnvironment', 'isJobStuck', 'hasTrace', @@ -58,7 +58,7 @@ :user="job.user" :actions="headerActions" :has-sidebar-button="true" - :should-render-triggered-label="jobHasStarted" + :should-render-triggered-label="shouldRenderTriggeredLabel" :item-name="__('Job')" /> </div> @@ -76,7 +76,7 @@ class="js-job-stuck" :has-no-runners-for-project="job.runners.available" :tags="job.tags" - :runners-path="runnerHelpUrl" + :runners-path="runnerSettingsUrl" /> <environments-block @@ -87,8 +87,8 @@ /> <erased-block - v-if="job.erased" - class="js-job-erased" + v-if="job.erased_at" + class="js-job-erased-block" :user="job.erased_by" :erased-at="job.erased_at" /> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index 35d40c6898e..cc885ea8e1b 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -65,7 +65,7 @@ export default { }; </script> <template> - <div class="top-bar"> + <div class="top-bar affix js-top-bar"> <!-- truncate information --> <div class="js-truncated-info truncated-info d-none d-sm-block float-left"> <template v-if="isTraceSizeVisible"> diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index 7f0f301d72a..8f3c6aced23 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -36,7 +36,7 @@ export default { }, }, computed: { - ...mapState(['job', 'isLoading', 'stages', 'jobs']), + ...mapState(['job', 'isLoading', 'stages', 'jobs', 'selectedStage']), coverage() { return `${this.job.coverage}%`; }, @@ -110,7 +110,7 @@ export default { </script> <template> <aside - class="right-sidebar right-sidebar-expanded build-sidebar" + class="js-build-sidebar right-sidebar right-sidebar-expanded build-sidebar" data-offset-top="101" data-spy="affix" > @@ -276,6 +276,7 @@ export default { <stages-dropdown :stages="stages" :pipeline="job.pipeline" + :selected-stage="selectedStage" @requestSidebarStageDropdown="fetchJobsForStage" /> diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue index 34d47b3a3bb..e5e1d56e287 100644 --- a/app/assets/javascripts/jobs/components/stages_dropdown.vue +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -2,7 +2,6 @@ import _ from 'underscore'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; -import { __ } from '~/locale'; export default { components: { @@ -18,30 +17,20 @@ export default { type: Array, required: true, }, + selectedStage: { + type: String, + required: true, + }, }, - data() { - return { - selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'), - }; - }, + computed: { 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) { this.$emit('requestSidebarStageDropdown', stage); - this.selectedStage = stage.name; }, }, }; diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js index 3eb75e72506..15cd79b1c50 100644 --- a/app/assets/javascripts/jobs/job_details_bundle.js +++ b/app/assets/javascripts/jobs/job_details_bundle.js @@ -9,8 +9,7 @@ import createStore from './store'; export default () => { const { dataset } = document.getElementById('js-job-details-vue'); - // eslint-disable-next-line no-new - new Job(); + const store = createStore(); store.dispatch('setJobEndpoint', dataset.endpoint); @@ -33,7 +32,7 @@ export default () => { props: { isLoading: this.isLoading, job: this.job, - runnerHelpUrl: dataset.runnerHelpUrl, + runnerSettingsUrl: dataset.runnerSettingsUrl, }, }); }, @@ -71,4 +70,7 @@ export default () => { }); }, }); + + // eslint-disable-next-line no-new + new Job(); }; diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index 298367c9342..d0040161dc3 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -139,10 +139,12 @@ export const fetchStages = ({ state, dispatch }) => { dispatch('requestStages'); axios - .get(state.job.pipeline.path) + .get(`${state.job.pipeline.path}.json`) .then(({ data }) => { + // Set selected stage dispatch('receiveStagesSuccess', data.details.stages); - dispatch('fetchJobsForStage', data.details.stages[0]); + const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage); + dispatch('fetchJobsForStage', selectedStage); }) .catch(() => dispatch('receiveStagesError')); }; @@ -156,11 +158,12 @@ export const receiveStagesError = ({ commit }) => { /** * Jobs list on sidebar - depend on stages dropdown */ -export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE); +export const requestJobsForStage = ({ commit }, stage) => + commit(types.REQUEST_JOBS_FOR_STAGE, stage); // On stage click, set selected stage + fetch job export const fetchJobsForStage = ({ dispatch }, stage) => { - dispatch('requestJobsForStage'); + dispatch('requestJobsForStage', stage); axios .get(stage.dropdown_path, { diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index afe5f88b292..9f4f372e3d2 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -22,10 +22,10 @@ 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 has not started the key will be null * When job started the key will be a string with a date. */ -export const jobHasStarted = state => !(state.job.started === false); +export const shouldRenderTriggeredLabel = state => _.isString(state.job.started); export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index c3f2359fa4d..f00e06e1a6c 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -53,6 +53,16 @@ export default { state.isLoading = false; state.hasError = false; state.job = job; + + /** + * We only update it on the first request + * The dropdown can be changed by the user + * after the first request, + * and we do not want to hijack that + */ + if (state.selectedStage === 'More' && job.stage) { + state.selectedStage = job.stage; + } }, [types.RECEIVE_JOB_ERROR](state) { state.isLoading = false; @@ -81,8 +91,9 @@ export default { state.stages = []; }, - [types.REQUEST_JOBS_FOR_STAGE](state) { + [types.REQUEST_JOBS_FOR_STAGE](state, stage) { state.isLoadingJobs = true; + state.selectedStage = stage.name; }, [types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) { state.isLoadingJobs = false; diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index 509cb69a5d3..afbc959bb71 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -1,3 +1,5 @@ +import { __ } from '~/locale'; + export default () => ({ jobEndpoint: null, traceEndpoint: null, @@ -34,7 +36,7 @@ export default () => ({ // sidebar dropdown isLoadingStages: false, isLoadingJobs: false, - selectedStage: null, + selectedStage: __('More'), stages: [], jobs: [], }); diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index 353aa790743..d9e99603238 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -94,6 +94,7 @@ export default { class="diff-file file-holder" > <diff-file-header + :discussion-path="discussion.discussionPath" :diff-file="diffFile" :can-current-user-fork="false" :discussions-expanded="isDiscussionsExpanded" diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index 1522e2227e4..300d453c174 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -1,5 +1,6 @@ <script> import $ from 'jquery'; +import { glEmojiTag } from '~/emoji'; import detailedMetric from './detailed_metric.vue'; import requestSelector from './request_selector.vue'; @@ -64,6 +65,16 @@ export default { lineProfileModal() { return $('#modal-peek-line-profile'); }, + hasHost() { + return this.currentRequest && this.currentRequest.details && this.currentRequest.details.host; + }, + birdEmoji() { + if (this.hasHost && this.currentRequest.details.host.canary) { + return glEmojiTag('baby_chick'); + } + + return ''; + }, }, mounted() { this.currentRequest = this.requestId; @@ -93,9 +104,11 @@ export default { class="view" > <span - v-if="currentRequest.details" + v-if="hasHost" class="current-host" + :class="{ 'canary' : currentRequest.details.host.canary }" > + <span v-html="birdEmoji"></span> {{ currentRequest.details.host.hostname }} </span> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 8184ef33022..c19b67f00fa 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -162,18 +162,20 @@ <span class="label-branch"> <a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a> </span> - with - <a - :href="mr.mergeCommitPath" - class="commit-sha js-mr-merged-commit-sha" - v-text="mr.shortMergeCommitSha" - > - </a> - <clipboard-button - :title="__('Copy commit SHA to clipboard')" - :text="mr.mergeCommitSha" - css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha" - /> + <template v-if="mr.mergeCommitSha"> + with + <a + :href="mr.mergeCommitPath" + class="commit-sha js-mr-merged-commit-sha" + v-text="mr.shortMergeCommitSha" + > + </a> + <clipboard-button + :title="__('Copy commit SHA to clipboard')" + :text="mr.mergeCommitSha" + css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha" + /> + </template> </p> <p v-if="mr.sourceBranchRemoved"> {{ s__("mrWidget|The source branch has been removed") }} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index be41dbfc61f..1c84baf68ed 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -12,6 +12,15 @@ max-width: $max-width; } +/** + * Mixin for fixed width container + */ +@mixin fixed-width-container { + max-width: $limited-layout-width - ($gl-padding * 2); + margin-left: auto; + margin-right: auto; +} + /* * Mixin for markdown tables */ diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b7a95f604b8..0fde6e18cc7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -605,6 +605,7 @@ $perf-bar-development: #4c1210; $perf-bar-bucket-bg: #111; $perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2); $perf-bar-bucket-box-shadow-to: rgba($black, 0.25); +$perf-bar-canary-text: $orange-400; /* Issuable warning diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 17b02c6e31e..cba5324ce53 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1046,3 +1046,19 @@ left: auto; line-height: 0; } + +@media (max-width: map-get($grid-breakpoints, md)-1) { + .diffs .files { + @include fixed-width-container; + flex-direction: column; + + .diff-tree-list { + width: 100%; + } + + .tree-list-holder { + max-height: calc(50px + 50vh); + padding-right: 0; + } + } +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 62a9f97caa9..00b06aea898 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -1,8 +1,6 @@ // Limit MR description for side-by-side diff view .fixed-width-container { - max-width: $limited-layout-width - ($gl-padding * 2); - margin-left: auto; - margin-right: auto; + @include fixed-width-container; } .issuable-warning-icon { diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 59fdbf31fe9..9c01a2f8bda 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -68,6 +68,10 @@ } } + .current-host.canary { + color: $perf-bar-canary-text; + } + strong { color: $white-light; } diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb index 970efa79dfb..45d5591e81b 100644 --- a/app/finders/branches_finder.rb +++ b/app/finders/branches_finder.rb @@ -7,8 +7,9 @@ class BranchesFinder end def execute - branches = @repository.branches_sorted_by(sort) - filter_by_name(branches) + branches = repository.branches_sorted_by(sort) + branches = by_search(branches) + branches end private @@ -23,11 +24,39 @@ class BranchesFinder @params[:sort].presence || 'name' end - def filter_by_name(branches) - if search - branches.select { |branch| branch.name.upcase.include?(search.upcase) } + def by_search(branches) + return branches unless search + + case search + when ->(v) { v.starts_with?('^') } + filter_branches_with_prefix(branches, search.slice(1..-1).upcase) + when ->(v) { v.ends_with?('$') } + filter_branches_with_suffix(branches, search.chop.upcase) else - branches + matches = filter_branches_by_name(branches, search.upcase) + set_exact_match_as_first_result(matches, search) end end + + def filter_branches_with_prefix(branches, prefix) + branches.select { |branch| branch.name.upcase.starts_with?(prefix) } + end + + def filter_branches_with_suffix(branches, suffix) + branches.select { |branch| branch.name.upcase.ends_with?(suffix) } + end + + def filter_branches_by_name(branches, term) + branches.select { |branch| branch.name.upcase.include?(term) } + end + + def set_exact_match_as_first_result(matches, term) + exact_match_index = find_exact_match_index(matches, term) + matches.insert(0, matches.delete_at(exact_match_index)) if exact_match_index + matches + end + + def find_exact_match_index(matches, term) + matches.index { |branch| branch.name.casecmp(term) == 0 } + end end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index c2404412006..6ececcd4152 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -42,17 +42,7 @@ class ProjectsFinder < UnionFinder init_collection end - collection = by_ids(collection) - collection = by_personal(collection) - collection = by_starred(collection) - collection = by_trending(collection) - collection = by_visibilty_level(collection) - collection = by_tags(collection) - collection = by_search(collection) - collection = by_archived(collection) - collection = by_custom_attributes(collection) - collection = by_deleted_status(collection) - + collection = filter_projects(collection) sort(collection) end @@ -66,6 +56,21 @@ class ProjectsFinder < UnionFinder end end + # EE would override this to add more filters + def filter_projects(collection) + collection = by_ids(collection) + collection = by_personal(collection) + collection = by_starred(collection) + collection = by_trending(collection) + collection = by_visibilty_level(collection) + collection = by_tags(collection) + collection = by_search(collection) + collection = by_archived(collection) + collection = by_custom_attributes(collection) + collection = by_deleted_status(collection) + collection + end + # rubocop: disable CodeReuse/ActiveRecord def collection_with_user if owned_projects? diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index ff9842d4cd9..f4f46b0fe96 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -18,22 +18,20 @@ module PreferencesHelper groups: _("Your Groups"), todos: _("Your Todos"), issues: _("Assigned Issues"), - merge_requests: _("Assigned Merge Requests") + merge_requests: _("Assigned Merge Requests"), + operations: _("Operations Dashboard") }.with_indifferent_access.freeze # Returns an Array usable by a select field for more user-friendly option text def dashboard_choices - defined = User.dashboards + dashboards = User.dashboards.keys - if defined.size != DASHBOARD_CHOICES.size - # Ensure that anyone adding new options updates this method too - raise "`User` defines #{defined.size} dashboard choices," \ - " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}." - else - defined.map do |key, _| - # Use `fetch` so `KeyError` gets raised when a key is missing - [DASHBOARD_CHOICES.fetch(key), key] - end + validate_dashboard_choices!(dashboards) + dashboards -= excluded_dashboard_choices + + dashboards.map do |key| + # Use `fetch` so `KeyError` gets raised when a key is missing + [DASHBOARD_CHOICES.fetch(key), key] end end @@ -52,4 +50,20 @@ module PreferencesHelper def user_color_scheme Gitlab::ColorSchemes.for_user(current_user).css_class end + + private + + # Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too + def validate_dashboard_choices!(user_dashboards) + if user_dashboards.size != DASHBOARD_CHOICES.size + raise "`User` defines #{user_dashboards.size} dashboard choices," \ + " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}." + end + end + + # List of dashboard choice to be excluded from CE. + # EE would override this. + def excluded_dashboard_choices + ['operations'] + end end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 53bd43d4861..8ed2a2ec9f4 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -48,15 +48,21 @@ module SortingHelper def groups_sort_options_hash { - sort_value_name => sort_title_name, - sort_value_name_desc => sort_title_name_desc, + sort_value_name => sort_title_name, + sort_value_name_desc => sort_title_name_desc, sort_value_recently_created => sort_title_recently_created, - sort_value_oldest_created => sort_title_oldest_created, + sort_value_oldest_created => sort_title_oldest_created, sort_value_recently_updated => sort_title_recently_updated, - sort_value_oldest_updated => sort_title_oldest_updated + sort_value_oldest_updated => sort_title_oldest_updated } end + def subgroups_sort_options_hash + groups_sort_options_hash.merge( + sort_value_most_stars => sort_title_most_stars + ) + end + def admin_groups_sort_options_hash groups_sort_options_hash.merge( sort_value_largest_group => sort_title_largest_group diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 6962b54441b..62dc0f2cbeb 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -19,6 +19,17 @@ class Deployment < ActiveRecord::Base after_create :create_ref after_create :invalidate_cache + scope :for_environment, -> (environment) { where(environment_id: environment) } + + def self.last_for_environment(environment) + ids = self + .for_environment(environment) + .select('MAX(id) AS id') + .group(:environment_id) + .map(&:id) + find(ids) + end + def commit project.commit(sha) end diff --git a/app/models/environment.rb b/app/models/environment.rb index 309bd4f37c9..0816c395185 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,6 +48,8 @@ class Environment < ActiveRecord::Base order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC')) end scope :in_review_folder, -> { where(environment_type: "review") } + scope :for_name, -> (name) { where(name: name) } + scope :for_project, -> (project) { where(project_id: project) } state_machine :state, initial: :available do event :start do diff --git a/app/models/project.rb b/app/models/project.rb index 05e14c578b5..c7ca322853f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1789,7 +1789,7 @@ class Project < ActiveRecord::Base return unless export_file_exists? import_export_upload.remove_export_file! - import_export_upload.save + import_export_upload.save unless import_export_upload.destroyed? end def export_file_exists? diff --git a/app/models/user.rb b/app/models/user.rb index 8a7acfb73b1..a0665518cf5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -217,7 +217,7 @@ class User < ActiveRecord::Base # User's Dashboard preference # Note: When adding an option, it MUST go on the end of the array. - enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests] + enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests, :operations] # User's Project preference # Note: When adding an option, it MUST go on the end of the array. diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 3d508a9a407..7bdcfcc38f7 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -4,6 +4,7 @@ class BuildDetailsEntity < JobEntity expose :coverage, :erased_at, :duration expose :tag_list, as: :tags expose :has_trace?, as: :has_trace + expose :stage expose :user, using: UserEntity expose :runner, using: RunnerEntity expose :pipeline, using: PipelineEntity diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml index e69143abe45..df3eeba907c 100644 --- a/app/views/admin/applications/show.html.haml +++ b/app/views/admin/applications/show.html.haml @@ -22,7 +22,7 @@ .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") + = clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default") %tr %td = _('Callback URL') diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 269a3721e06..12271ee5adb 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -5,7 +5,7 @@ .d-flex.justify-content-between.flex-wrap - providers.each do |provider| - has_icon = provider_has_icon?(provider) - = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login', id: "oauth-login-#{provider}" do + = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login qa-saml-login-button', id: "oauth-login-#{provider}" do - if has_icon = provider_image_tag(provider) %span diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml index 776bbc36ec2..cac00f9c854 100644 --- a/app/views/doorkeeper/applications/show.html.haml +++ b/app/views/doorkeeper/applications/show.html.haml @@ -25,7 +25,7 @@ .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") + = clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default") %tr %td = _('Callback URL') diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 6a293daaf95..cc294f6a931 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -53,7 +53,7 @@ = _("Archived projects") .nav-controls - = render "shared/groups/dropdown" + = render "shared/groups/dropdown", options_hash: subgroups_sort_options_hash .tab-content #subgroups_and_projects.tab-pane diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 5e467c862ab..8f8b6b454d9 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -66,6 +66,7 @@ - if Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics) %li.line-separator.d-none.d-sm-block + = render_if_exists 'dashboard/operations/nav_link' - if can?(current_user, :read_instance_statistics) = nav_link(controller: [:conversational_development_index, :cohorts]) do = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 48025f9bd20..3625224fbcd 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -309,7 +309,7 @@ %span = _('General') = nav_link(controller: :project_members) do - = link_to project_project_members_path(@project), title: _('Members') do + = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings' do %span = _('Members') - if can_edit diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index a5f814b722d..02a088d338b 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -47,4 +47,6 @@ .js-build-options{ data: javascript_build_options } -#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner') } } +#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), + runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'), + runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings') } } diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 517fd249f6e..5e21442bb60 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -3,7 +3,7 @@ = form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f| .form-group = label_tag :user_ids, "Select members to invite", class: "label-bold" - = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite") + = users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite") .form-group = label_tag :access_level, "Choose a role permission", class: "label-bold" .select-wrapper @@ -17,5 +17,5 @@ = label_tag :expires_at, 'Access expiration date', class: 'label-bold' = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - = f.submit "Add to project", class: "btn btn-success" + = f.submit "Add to project", class: "btn btn-success qa-add-member-button" = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project" diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 0c5a187f208..9682f8ac922 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -14,5 +14,5 @@ %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") = render 'shared/members/sort_dropdown' - %ul.content-list.members-list + %ul.content-list.members-list.qa-members-list = render partial: 'shared/members/member', collection: members, as: :member diff --git a/changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml b/changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml new file mode 100644 index 00000000000..ab64a1387d9 --- /dev/null +++ b/changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml @@ -0,0 +1,5 @@ +--- +title: "fix duplicated key in license management job auto devops gitlab ci template" +merge_request: 22311 +author: Adam Lemanski +type: fixed diff --git a/changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml b/changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml new file mode 100644 index 00000000000..01681adab24 --- /dev/null +++ b/changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml @@ -0,0 +1,4 @@ +--- +title: Add new sort option "most_stars" to "Group > Children" pages +merge_request: 22121 +author: Rene Hennig diff --git a/changelogs/unreleased/48889-message-for-were-merged-into.yml b/changelogs/unreleased/48889-message-for-were-merged-into.yml new file mode 100644 index 00000000000..552b8826829 --- /dev/null +++ b/changelogs/unreleased/48889-message-for-were-merged-into.yml @@ -0,0 +1,5 @@ +--- +title: Fix 'merged with' UI being displayed when merge request has no merge commit +merge_request: 22022 +author: +type: fixed diff --git a/changelogs/unreleased/50185-fix-broken-file-name-navigation.yml b/changelogs/unreleased/50185-fix-broken-file-name-navigation.yml new file mode 100644 index 00000000000..d1b341af457 --- /dev/null +++ b/changelogs/unreleased/50185-fix-broken-file-name-navigation.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken file name navigation on MRs +merge_request: 22109 +author: +type: fixed diff --git a/changelogs/unreleased/52361-fix-file-tree-mobile.yml b/changelogs/unreleased/52361-fix-file-tree-mobile.yml new file mode 100644 index 00000000000..fe978eeca2d --- /dev/null +++ b/changelogs/unreleased/52361-fix-file-tree-mobile.yml @@ -0,0 +1,5 @@ +--- +title: Improve MR file tree in smaller screens +merge_request: 22273 +author: +type: fixed diff --git a/changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml b/changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml new file mode 100644 index 00000000000..20e32a2e987 --- /dev/null +++ b/changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml @@ -0,0 +1,5 @@ +--- +title: Show canary status in the performance bar +merge_request: 22222 +author: +type: changed diff --git a/changelogs/unreleased/52472-pipeline-endpoint-json.yml b/changelogs/unreleased/52472-pipeline-endpoint-json.yml new file mode 100644 index 00000000000..feff195beb8 --- /dev/null +++ b/changelogs/unreleased/52472-pipeline-endpoint-json.yml @@ -0,0 +1,5 @@ +--- +title: Fix caching issue with pipelines URL +merge_request: 22293 +author: +type: fixed diff --git a/changelogs/unreleased/52519-runners-link.yml b/changelogs/unreleased/52519-runners-link.yml new file mode 100644 index 00000000000..5d904a8b340 --- /dev/null +++ b/changelogs/unreleased/52519-runners-link.yml @@ -0,0 +1,5 @@ +--- +title: Fixes stuck block URL linking to documentation instead of settings page +merge_request: 22286 +author: +type: fixed diff --git a/changelogs/unreleased/52570-erased-block.yml b/changelogs/unreleased/52570-erased-block.yml new file mode 100644 index 00000000000..6ec295bf81b --- /dev/null +++ b/changelogs/unreleased/52570-erased-block.yml @@ -0,0 +1,5 @@ +--- +title: Fix erased block not being rendered when job was erased +merge_request: 22294 +author: +type: fixed diff --git a/changelogs/unreleased/52608-sidebar.yml b/changelogs/unreleased/52608-sidebar.yml new file mode 100644 index 00000000000..9eca30f7b95 --- /dev/null +++ b/changelogs/unreleased/52608-sidebar.yml @@ -0,0 +1,5 @@ +--- +title: Hides sidebar for job page in mobile +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/52614-update-job-started-check.yml b/changelogs/unreleased/52614-update-job-started-check.yml new file mode 100644 index 00000000000..60ea237dbf3 --- /dev/null +++ b/changelogs/unreleased/52614-update-job-started-check.yml @@ -0,0 +1,5 @@ +--- +title: Fixes triggered/created labeled in job header +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml b/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml new file mode 100644 index 00000000000..fdbde709e77 --- /dev/null +++ b/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml @@ -0,0 +1,5 @@ +--- +title: Load correct stage in the stages dropdown +merge_request: 22317 +author: +type: fixed diff --git a/changelogs/unreleased/enable-frozen-string-lib-gitlab.yml b/changelogs/unreleased/enable-frozen-string-lib-gitlab.yml new file mode 100644 index 00000000000..4a216c46d38 --- /dev/null +++ b/changelogs/unreleased/enable-frozen-string-lib-gitlab.yml @@ -0,0 +1,5 @@ +--- +title: Enable some frozen string in lib/gitlab +merge_request: +author: gfyoung +type: performance diff --git a/changelogs/unreleased/even-more-frozen-string-lib.yml b/changelogs/unreleased/even-more-frozen-string-lib.yml new file mode 100644 index 00000000000..3f5fd7710aa --- /dev/null +++ b/changelogs/unreleased/even-more-frozen-string-lib.yml @@ -0,0 +1,5 @@ +--- +title: Enable even more frozen string in lib/**/*.rb +merge_request: +author: gfyoung +type: performance diff --git a/changelogs/unreleased/feature-improved-branch-filter-sorting.yml b/changelogs/unreleased/feature-improved-branch-filter-sorting.yml new file mode 100644 index 00000000000..539c297e0dd --- /dev/null +++ b/changelogs/unreleased/feature-improved-branch-filter-sorting.yml @@ -0,0 +1,6 @@ +--- +title: Improving branch filter sorting by listing exact matches first and added support + for begins_with (^) and ends_with ($) matching. +merge_request: 22166 +author: Jason Rutherford +type: changed diff --git a/changelogs/unreleased/fl-update-svgs.yml b/changelogs/unreleased/fl-update-svgs.yml new file mode 100644 index 00000000000..e6e76617df1 --- /dev/null +++ b/changelogs/unreleased/fl-update-svgs.yml @@ -0,0 +1,5 @@ +--- +title: Updates svg dependency +merge_request: +author: +type: other diff --git a/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml b/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml new file mode 100644 index 00000000000..7205c138777 --- /dev/null +++ b/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml @@ -0,0 +1,5 @@ +--- +title: Update copy to clipboard button data for application secret +merge_request: 22268 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/sh-fix-project-deletion-with-export.yml b/changelogs/unreleased/sh-fix-project-deletion-with-export.yml new file mode 100644 index 00000000000..b9437f8ad6a --- /dev/null +++ b/changelogs/unreleased/sh-fix-project-deletion-with-export.yml @@ -0,0 +1,5 @@ +--- +title: Fix project deletion when there is a export available +merge_request: 22276 +author: +type: fixed diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index 020e9a00d87..e06cce3e97a 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -95,7 +95,7 @@ end before_fork do |server, worker| # the following is highly recommended for Rails + "preload_app true" # as there's no need for the master process to hold a connection - defined?(ActiveRecord::Base) and + defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect! # The following is only recommended for memory/DB-constrained @@ -133,7 +133,7 @@ after_fork do |server, worker| # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) # the following is *required* for Rails + "preload_app true", - defined?(ActiveRecord::Base) and + defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection # reset prometheus client, this will cause any opened metrics files to be closed diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development index 5712549a66d..f31df66015a 100644 --- a/config/unicorn.rb.example.development +++ b/config/unicorn.rb.example.development @@ -7,7 +7,7 @@ check_client_connection false before_fork do |server, worker| # the following is highly recommended for Rails + "preload_app true" # as there's no need for the master process to hold a connection - defined?(ActiveRecord::Base) and + defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect! if /darwin/ =~ RUBY_PLATFORM @@ -27,6 +27,6 @@ after_fork do |server, worker| require 'rbtrace' if ENV['ENABLE_RBTRACE'] # the following is *required* for Rails + "preload_app true", - defined?(ActiveRecord::Base) and + defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection end diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index b5e2b5448f7..e1b2a0a24eb 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -25,15 +25,13 @@ gitaly['prometheus_listen_addr'] = 'localhost:9236' ``` To change a Gitaly setting in installations from source you can edit -`/home/git/gitaly/config.toml`. +`/home/git/gitaly/config.toml`. Changes will be applied when you run +`service gitlab restart`. ```toml prometheus_listen_addr = "localhost:9236" ``` -Changes to `/home/git/gitaly/config.toml` are applied when you run `service -gitlab restart`. - ## Client-side GRPC logs Gitaly uses the [gRPC](https://grpc.io/) RPC framework. The Ruby gRPC diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md index 293036f2f4b..b61c5409a56 100644 --- a/doc/administration/integration/plantuml.md +++ b/doc/administration/integration/plantuml.md @@ -80,10 +80,10 @@ our AsciiDoc snippets, wikis and repos using delimited blocks: ``` [plantuml, format="png", id="myDiagram", width="200px"] - -- + ---- Bob->Alice : hello Alice -> Bob : Go Away - -- + ---- ``` - **reStructuredText** diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 757865ea2c5..2feac1fd3b0 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -98,7 +98,7 @@ _The artifacts are stored by default in If you don't want to use the local disk where GitLab is installed to store the artifacts, you can use an object storage like AWS S3 instead. This configuration relies on valid AWS credentials to be configured already. -Use an [Object storage option][os] like AWS S3 to store job artifacts. +Use an object storage option like AWS S3 to store job artifacts. ### Object Storage Settings @@ -315,4 +315,3 @@ memory and disk I/O. [reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab" [restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab" [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" -[os]: https://docs.gitlab.com/administration/job_artifacts.html#using-object-storage diff --git a/doc/api/events.md b/doc/api/events.md index cd84b32029e..ccac5b8bb60 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -71,7 +71,7 @@ Parameters: Example request: ``` -curl --header "PRIVATE-TOKEN 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01 ``` Example response: @@ -276,7 +276,7 @@ Parameters: Example request: ``` -curl --header "PRIVATE-TOKEN 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:project_id/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:project_id/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01 ``` Example response: diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md index ab429e0ded3..70020d461d9 100644 --- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md +++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md @@ -125,7 +125,7 @@ They can be added per project by navigating to the project's **Settings** > **CI To the field **KEY**, add the name `SSH_PRIVATE_KEY`, and to the **VALUE** field, paste the private key you've copied earlier. We'll use this variable in the `.gitlab-ci.yml` later, to easily connect to our remote server as the deployer user without entering its password. -We also need to add the public key to **Project** > **Settings** > **Repository** as [Deploy Keys](../../../ssh/README.md/#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md/#start-working-on-your-project). +We also need to add the public key to **Project** > **Settings** > **Repository** as [Deploy Keys](../../../ssh/README.md#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project). ```bash @@ -378,7 +378,7 @@ These are persistent data and will be shared to every new release. Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../environments.md), which will be described [later](#setting-up-gitlab-ci-cd) in this tutorial. Now it's time to commit [Envoy.blade.php](https://gitlab.com/mehranrasulian/laravel-sample/blob/master/Envoy.blade.php) and push it to the `master` branch. -To keep things simple, we commit directly to `master`, without using [feature-branches](../../../workflow/gitlab_flow.md/#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial. +To keep things simple, we commit directly to `master`, without using [feature-branches](../../../workflow/gitlab_flow.md#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial. In a real world project, teams may use [Issue Tracker](../../../user/project/issues/index.md) and [Merge Requests](../../../user/project/merge_requests/index.md) to move their code across branches: ```bash @@ -398,7 +398,7 @@ In the case you're not familiar with Docker, refer to [How to Automate Docker De To be able to build, test, and deploy our app with GitLab CI/CD, we need to prepare our work environment. To do that, we'll use a Docker image which has the minimum requirements that a Laravel app needs to run. -[There are other ways](../php.md/#test-php-projects-using-the-docker-executor) to do that as well, but they may lead our builds run slowly, which is not what we want when there are faster options to use. +[There are other ways](../php.md#test-php-projects-using-the-docker-executor) to do that as well, but they may lead our builds run slowly, which is not what we want when there are faster options to use. With Docker images our builds run incredibly faster! @@ -536,7 +536,7 @@ That's a lot to take in, isn't it? Let's run through it step by step. [GitLab Runners](../../runners/README.md) run the script defined by `.gitlab-ci.yml`. The `image` keyword tells the Runners which image to use. -The `services` keyword defines additional images [that are linked to the main image](../../docker/using_docker_images.md/#what-is-a-service). +The `services` keyword defines additional images [that are linked to the main image](../../docker/using_docker_images.md#what-is-a-service). Here we use the container image we created before as our main image and also use MySQL 5.7 as a service. ```yaml @@ -560,7 +560,7 @@ So we should adjust the configuration of MySQL instance by defining `MYSQL_DATAB Find out more about MySQL variables at the [official MySQL Docker Image](https://hub.docker.com/r/_/mysql/). Also set the variables `DB_HOST` to `mysql` and `DB_USERNAME` to `root`, which are Laravel specific variables. -We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md/#how-services-are-linked-to-the-build). +We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md#how-services-are-linked-to-the-build). ```yaml ... @@ -602,7 +602,7 @@ unit_test: #### Deploy to production The job `deploy_production` will deploy the app to the production server. -To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../ssh_keys/README.md/#ssh-keys-when-using-the-docker-executor). +To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../ssh_keys/README.md#ssh-keys-when-using-the-docker-executor). If the SSH keys have added successfully, we can run Envoy. As mentioned before, GitLab supports [Continuous Delivery](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery) methods as well. diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index df4805ea7ac..c1048f3d2e3 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -20,7 +20,7 @@ build environment. Let's first specify the PHP image that will be used for the job process (you can read more about what an image means in the Runner's lingo reading -about [Using Docker images](../docker/using_docker_images.md#what-is-image)). +about [Using Docker images](../docker/using_docker_images.md#what-is-an-image)). Start by adding the image to your `.gitlab-ci.yml`: diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index cf92d90ba30..bffb0121603 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -2,7 +2,7 @@ > **Notes**: > -> - [Introduced][ci-229] in GitLab CE 7.14. +> - [Introduced](https://about.gitlab.com/2015/08/22/gitlab-7-14-released/) in GitLab 7.14. > - GitLab 8.12 has a completely redesigned job permissions system. Read all > about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers). @@ -154,10 +154,10 @@ This information is also exposed in the UI. Using trigger variables can be proven useful for a variety of reasons: -* Identifiable jobs. Since the variable is exposed in the UI you can know +- Identifiable jobs. Since the variable is exposed in the UI you can know why the rebuild was triggered if you pass a variable that explains the purpose. -* Conditional job processing. You can have conditional jobs that run whenever +- Conditional job processing. You can have conditional jobs that run whenever a certain variable is present. Consider the following `.gitlab-ci.yml` where we set three @@ -221,7 +221,6 @@ removed with one of the future versions of GitLab. You are advised to [take ownership](#taking-ownership) of any legacy triggers. [ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017 -[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229 [ee]: https://about.gitlab.com/pricing/ [variables]: ../variables/README.md [predef]: ../variables/README.md#predefined-variables-environment-variables diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index f0738252640..24d60a0cdcc 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -415,7 +415,7 @@ Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`. Refs strategy equals to simplified only/except configuration, whereas kubernetes strategy accepts only `active` keyword. -### `variables` +### `only:variables` `variables` keyword is used to define variables expressions. In other words you can use predefined variables / project / group or @@ -460,7 +460,7 @@ end-to-end: Learn more about variables expressions on [a separate page][variables-expressions]. -### `changes` +### `only:changes` Using `changes` keyword with `only` or `except` makes it possible to define if a job should be created based on files modified by a git push event. diff --git a/doc/development/README.md b/doc/development/README.md index 43d3865da0e..d8604a4f3e5 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -50,6 +50,7 @@ description: 'Learn how to contribute to GitLab.' - [Permissions](permissions.md) - [Prometheus metrics](prometheus_metrics.md) - [Guidelines for reusing abstractions](reusing_abstractions.md) +- [DeclarativePolicy framework](policies.md) ## Performance guides diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 2db78e4a365..ce2424eca3b 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -321,7 +321,7 @@ The following sample `markdownlint` configuration modifies the available default } ``` -For [`markdownlint`](https://gitahub.com/DavidAnson/markdownlint/), this configuration must be +For [`markdownlint`](https://github.com/DavidAnson/markdownlint/), this configuration must be placed in a [valid location](https://github.com/igorshubovych/markdownlint-cli#configuration). For example, `~/.markdownlintrc`. diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md index 417298205f5..0f1f079bdb4 100644 --- a/doc/development/feature_flags.md +++ b/doc/development/feature_flags.md @@ -69,6 +69,37 @@ For more information about rolling out changes using feature flags, refer to the [Rolling out changes using feature flags](rolling_out_changes_using_feature_flags.md) guide. +### Frontend + +For frontend code you can use the method `push_frontend_feature_flag`, which is +available to all controllers that inherit from `ApplicationController`. Using +this method you can expose the state of a feature flag as follows: + +```ruby +before_action do + push_frontend_feature_flag(:vim_bindings) +end + +def index + # ... +end + +def edit + # ... +end +``` + +You can then check for the state of the feature flag in JavaScript as follows: + +```javascript +if ( gon.features.vimBindings ) { + // ... +} +``` + +The name of the feature flag in JavaScript will always be camelCased, meaning +that checking for `gon.features.vim_bindings` would not work. + ### Specs In the test environment `Feature.enabled?` is stubbed to always respond to `true`, diff --git a/doc/install/installation.md b/doc/install/installation.md index 25aa5d3369d..9e2e58657f1 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -103,7 +103,7 @@ Is the system packaged Git too old? Remove it and compile from source. # When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git -**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://github.com/gitlabhq/gitlabhq/issues/4866#issuecomment-32726573) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with: +**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://gitlab.com/gitlab-org/gitlab-ce/issues/12754) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with: sudo apt-get install -y postfix diff --git a/doc/update/11.3-to-11.4.md b/doc/update/11.3-to-11.4.md index 985239369d7..b50e21f27dd 100644 --- a/doc/update/11.3-to-11.4.md +++ b/doc/update/11.3-to-11.4.md @@ -80,8 +80,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/ ### 5. Update Go -NOTE: GitLab 11.0 and higher only supports Go 1.9.x and newer, and dropped support for Go -1.5.x through 1.8.x. Be sure to upgrade your installation if necessary. +NOTE: GitLab 11.4 and higher only supports Go 1.10.x and newer, and dropped support for Go +1.9.x. Be sure to upgrade your installation if necessary. You can check which version you are running with `go version`. diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 1b3fb9db4ec..097b18ad496 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -281,7 +281,7 @@ Additionally locked issues can not be reopened. [ce-14053]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14053 [ce-14061]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14061 [ce-14531]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14531 -[ce-31847]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31847 +[ce-31847]: https://gitlab.com/gitlab-org/gitlab-ce/issues/31847 [resolve-discussion-button]: img/resolve_discussion_button.png [resolve-comment-button]: img/resolve_comment_button.png [discussion-view]: img/discussion_view.png diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md index 22ef11e4049..06b3779668b 100644 --- a/doc/user/project/merge_requests/cherry_pick_changes.md +++ b/doc/user/project/merge_requests/cherry_pick_changes.md @@ -12,9 +12,11 @@ to cherry-pick the changes introduced by that merge request. ![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png) -After you click that button, a modal will appear where you can choose to -cherry-pick the changes directly into the selected branch or you can opt to -create a new merge request with the cherry-pick changes +After you click that button, a modal will appear showing a [branch filter search box](../repository/branches/index.md#branch-filter-search-box) +where you can choose to either: + +- Cherry-pick the changes directly into the selected branch. +- Create a new merge request with the cherry-picked changes. ## Cherry-picking a Commit diff --git a/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png b/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png Binary files differindex ac766c99935..3b3bf88df31 100644 --- a/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png +++ b/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 43ca498d006..f9ebf277125 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -205,9 +205,9 @@ have been marked as a **Work In Progress**. ## Merge request diff file navigation -The diff view has a persistent dropdown for file navigation. As you scroll through -diffs with a large number of files and/or many changes in those files, you can -easily jump to any changed file through the dropdown navigation. +The diff view has a file tree for file navigation. As you scroll through +diffs with a large number of files, you can easily jump to any changed file +using the file tree. ![Merge request diff file navigation](img/merge_request_diff_file_navigation.png) diff --git a/doc/user/project/repository/branches/img/branch_filter_search_box.png b/doc/user/project/repository/branches/img/branch_filter_search_box.png Binary files differnew file mode 100644 index 00000000000..c4364ef39f4 --- /dev/null +++ b/doc/user/project/repository/branches/img/branch_filter_search_box.png diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md index 19417d91fec..e1d8345f415 100644 --- a/doc/user/project/repository/branches/index.md +++ b/doc/user/project/repository/branches/index.md @@ -6,6 +6,7 @@ Read through GiLab's branching documentation: - [Default branch](#default-branch) - [Protected branches](../../protected_branches.md#protected-branches) - [Delete merged branches](#delete-merged-branches) +- [Branch filter search box](#branch-filter-search-box) See also: @@ -40,5 +41,22 @@ this operation. It's particularly useful to clean up old branches that were not deleted automatically when a merge request was merged. + +## Branch filter search box + +> [Introduced][https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166] in GitLab 11.5. + +![Branch filter search box](img/branch_filter_search_box.png) + +This feature allows you to search and select branches quickly. Search results appear in the following order: + +- Branches with names that matched search terms exactly. +- Other branches with names that include search terms, sorted alphabetically. + +Sometimes when you have hundreds of branches you may want a more flexible matching pattern. In such cases you can use the following: + +- `^feature` will only match branch names that begin with 'feature'. +- `feature$` will only match branch names that end with 'feature'. + [ce-6449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449 "Add button to delete all merged branches" [protected]: ../../protected_branches.md diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 4d016277824..ce79bfc0a03 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -85,12 +85,13 @@ You can live preview changes submitted to a new branch with With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers. -To create, delete, and [branches](branches/index.md) via GitLab's UI: +To create, delete, and view [branches](branches/index.md) via GitLab's UI: - [Default branches](branches/index.md#default-branch) - [Create a branch](web_editor.md#create-a-new-branch) - [Protected branches](../protected_branches.md#protected-branches) - [Delete merged branches](branches/index.md#delete-merged-branches) +- [Branch filter search box](branches/index.md#branch-filter-search-box) Alternatively, you can use the [command line](../../../gitlab-basics/start-using-git.md#create-a-branch). @@ -169,7 +170,7 @@ vendored code, and most markup languages are excluded. ## Compare -Select branches to compare and view the changes inline: +Select branches to compare using the [branch filter search box](branches/index.md#branch-filter-search-box), then click the **Compare** button to view the changes inline: ![compare branches](img/compare_branches.png) diff --git a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb index 91175b49c79..15cdd25e711 100644 --- a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb +++ b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails/generators' module Rails diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb index f95e423ef22..7b238623418 100644 --- a/lib/gitaly/server.rb +++ b/lib/gitaly/server.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitaly class Server def self.all diff --git a/lib/gitlab/auth/activity.rb b/lib/gitlab/auth/activity.rb index 761f0819c60..558628b5422 100644 --- a/lib/gitlab/auth/activity.rb +++ b/lib/gitlab/auth/activity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth ## diff --git a/lib/gitlab/auth/database/authentication.rb b/lib/gitlab/auth/database/authentication.rb index 1234ace0334..c0dc2b0875f 100644 --- a/lib/gitlab/auth/database/authentication.rb +++ b/lib/gitlab/auth/database/authentication.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These calls help to authenticate to OAuth provider by providing username and password # diff --git a/lib/gitlab/auth/ip_rate_limiter.rb b/lib/gitlab/auth/ip_rate_limiter.rb index e6173d45af3..81e616fa20a 100644 --- a/lib/gitlab/auth/ip_rate_limiter.rb +++ b/lib/gitlab/auth/ip_rate_limiter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth class IpRateLimiter diff --git a/lib/gitlab/auth/ldap/access.rb b/lib/gitlab/auth/ldap/access.rb index f323d2e0f7a..c875bba4bcb 100644 --- a/lib/gitlab/auth/ldap/access.rb +++ b/lib/gitlab/auth/ldap/access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # LDAP authorization model # # * Check if we are allowed access (not blocked) diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb index 82ff1e77e5c..42c657afe6a 100644 --- a/lib/gitlab/auth/ldap/adapter.rb +++ b/lib/gitlab/auth/ldap/adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module LDAP diff --git a/lib/gitlab/auth/ldap/auth_hash.rb b/lib/gitlab/auth/ldap/auth_hash.rb index ac5c14d374d..83fdc8a8c76 100644 --- a/lib/gitlab/auth/ldap/auth_hash.rb +++ b/lib/gitlab/auth/ldap/auth_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Class to parse and transform the info provided by omniauth # module Gitlab diff --git a/lib/gitlab/auth/ldap/authentication.rb b/lib/gitlab/auth/ldap/authentication.rb index 7c134fb6438..174e81dd603 100644 --- a/lib/gitlab/auth/ldap/authentication.rb +++ b/lib/gitlab/auth/ldap/authentication.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These calls help to authenticate to LDAP by providing username and password # # Since multiple LDAP servers are supported, it will loop through all of them diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb index d4415eaa6dc..7ceb96f502b 100644 --- a/lib/gitlab/auth/ldap/config.rb +++ b/lib/gitlab/auth/ldap/config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Load a specific server configuration module Gitlab module Auth diff --git a/lib/gitlab/auth/ldap/dn.rb b/lib/gitlab/auth/ldap/dn.rb index 1fa5338f5a6..5df914aa367 100644 --- a/lib/gitlab/auth/ldap/dn.rb +++ b/lib/gitlab/auth/ldap/dn.rb @@ -1,4 +1,5 @@ # -*- ruby encoding: utf-8 -*- +# frozen_string_literal: true # Based on the `ruby-net-ldap` gem's `Net::LDAP::DN` # diff --git a/lib/gitlab/auth/ldap/ldap_connection_error.rb b/lib/gitlab/auth/ldap/ldap_connection_error.rb index ef0a695742b..d0e5f24d203 100644 --- a/lib/gitlab/auth/ldap/ldap_connection_error.rb +++ b/lib/gitlab/auth/ldap/ldap_connection_error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module LDAP diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb index 8dfae3ee541..a0244a3cea1 100644 --- a/lib/gitlab/auth/ldap/person.rb +++ b/lib/gitlab/auth/ldap/person.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module LDAP diff --git a/lib/gitlab/auth/ldap/user.rb b/lib/gitlab/auth/ldap/user.rb index 3c21ddf3241..9c71671f409 100644 --- a/lib/gitlab/auth/ldap/user.rb +++ b/lib/gitlab/auth/ldap/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # LDAP extension for User model # # * Find or create user from omniauth.auth data diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb index ed8fba94305..4a5f9d2839d 100644 --- a/lib/gitlab/auth/o_auth/auth_hash.rb +++ b/lib/gitlab/auth/o_auth/auth_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Class to parse and transform the info provided by omniauth # module Gitlab diff --git a/lib/gitlab/auth/o_auth/authentication.rb b/lib/gitlab/auth/o_auth/authentication.rb index d4e7f35c857..5f008678bd1 100644 --- a/lib/gitlab/auth/o_auth/authentication.rb +++ b/lib/gitlab/auth/o_auth/authentication.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These calls help to authenticate to OAuth provider by providing username and password # diff --git a/lib/gitlab/auth/o_auth/identity_linker.rb b/lib/gitlab/auth/o_auth/identity_linker.rb index de92d7a214d..e69c2bb54dc 100644 --- a/lib/gitlab/auth/o_auth/identity_linker.rb +++ b/lib/gitlab/auth/o_auth/identity_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module OAuth diff --git a/lib/gitlab/auth/o_auth/provider.rb b/lib/gitlab/auth/o_auth/provider.rb index 26da9d09ccc..9fdf3324db3 100644 --- a/lib/gitlab/auth/o_auth/provider.rb +++ b/lib/gitlab/auth/o_auth/provider.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module OAuth diff --git a/lib/gitlab/auth/o_auth/session.rb b/lib/gitlab/auth/o_auth/session.rb index 8f2b4d58552..4925b107042 100644 --- a/lib/gitlab/auth/o_auth/session.rb +++ b/lib/gitlab/auth/o_auth/session.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # :nocov: module Gitlab module Auth diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb index 2b4f6ed75e5..a4e8a41b246 100644 --- a/lib/gitlab/auth/o_auth/user.rb +++ b/lib/gitlab/auth/o_auth/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # OAuth extension for User model # # * Find GitLab user based on omniauth uid and provider diff --git a/lib/gitlab/auth/omniauth_identity_linker_base.rb b/lib/gitlab/auth/omniauth_identity_linker_base.rb index 8ae29a02a13..253445570f2 100644 --- a/lib/gitlab/auth/omniauth_identity_linker_base.rb +++ b/lib/gitlab/auth/omniauth_identity_linker_base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth class OmniauthIdentityLinkerBase diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb index 66de52506ce..cb9f2582936 100644 --- a/lib/gitlab/auth/request_authenticator.rb +++ b/lib/gitlab/auth/request_authenticator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Use for authentication only, in particular for Rack::Attack. # Does not perform authorization of scopes, etc. module Gitlab diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index 00cdc94a9ef..78fa25c5516 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -1,4 +1,7 @@ -module Gitlab # rubocop:disable Naming/FileName +# rubocop:disable Naming/FileName +# frozen_string_literal: true + +module Gitlab module Auth Result = Struct.new(:actor, :project, :type, :authentication_abilities) do def ci?(for_project) diff --git a/lib/gitlab/auth/saml/auth_hash.rb b/lib/gitlab/auth/saml/auth_hash.rb index 3bc5e2864df..316354fd50c 100644 --- a/lib/gitlab/auth/saml/auth_hash.rb +++ b/lib/gitlab/auth/saml/auth_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module Saml diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb index 625dab7c6f4..8cb999f50d4 100644 --- a/lib/gitlab/auth/saml/config.rb +++ b/lib/gitlab/auth/saml/config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module Saml diff --git a/lib/gitlab/auth/saml/identity_linker.rb b/lib/gitlab/auth/saml/identity_linker.rb index 7e4b191d512..ae0d6dded4e 100644 --- a/lib/gitlab/auth/saml/identity_linker.rb +++ b/lib/gitlab/auth/saml/identity_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth module Saml diff --git a/lib/gitlab/auth/saml/user.rb b/lib/gitlab/auth/saml/user.rb index 6c3b75f3eb0..ec95bc46791 100644 --- a/lib/gitlab/auth/saml/user.rb +++ b/lib/gitlab/auth/saml/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # SAML extension for User model # # * Find GitLab user based on SAML uid and provider diff --git a/lib/gitlab/auth/too_many_ips.rb b/lib/gitlab/auth/too_many_ips.rb index ed862791551..ee4d80e6b89 100644 --- a/lib/gitlab/auth/too_many_ips.rb +++ b/lib/gitlab/auth/too_many_ips.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth class TooManyIps < StandardError diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb index baa1f802d8a..31dd61ae6cf 100644 --- a/lib/gitlab/auth/unique_ips_limiter.rb +++ b/lib/gitlab/auth/unique_ips_limiter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth class UniqueIpsLimiter diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb index 1893cb001b2..fd09fe76c02 100644 --- a/lib/gitlab/auth/user_access_denied_reason.rb +++ b/lib/gitlab/auth/user_access_denied_reason.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth class UserAccessDeniedReason diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb index 064cba43278..5df6db6f366 100644 --- a/lib/gitlab/auth/user_auth_finders.rb +++ b/lib/gitlab/auth/user_auth_finders.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth AuthenticationError = Class.new(StandardError) diff --git a/lib/gitlab/badge/base.rb b/lib/gitlab/badge/base.rb index 909fa24fa90..fb55b9e2f1f 100644 --- a/lib/gitlab/badge/base.rb +++ b/lib/gitlab/badge/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge class Base diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb index e898f5d790e..9181ba2d4b0 100644 --- a/lib/gitlab/badge/coverage/metadata.rb +++ b/lib/gitlab/badge/coverage/metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge module Coverage diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb index 16fd6f01495..a7fcb6b0fca 100644 --- a/lib/gitlab/badge/coverage/report.rb +++ b/lib/gitlab/badge/coverage/report.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge module Coverage diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb index afbf9dd17e3..817dc28f84a 100644 --- a/lib/gitlab/badge/coverage/template.rb +++ b/lib/gitlab/badge/coverage/template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge module Coverage diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb index 8ad6f3cb986..b9ae68134b0 100644 --- a/lib/gitlab/badge/metadata.rb +++ b/lib/gitlab/badge/metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge ## diff --git a/lib/gitlab/badge/pipeline/metadata.rb b/lib/gitlab/badge/pipeline/metadata.rb index db1e9f8cfb8..d4d789558c9 100644 --- a/lib/gitlab/badge/pipeline/metadata.rb +++ b/lib/gitlab/badge/pipeline/metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge module Pipeline diff --git a/lib/gitlab/badge/pipeline/status.rb b/lib/gitlab/badge/pipeline/status.rb index d1d9b7949f5..37e61f07e5b 100644 --- a/lib/gitlab/badge/pipeline/status.rb +++ b/lib/gitlab/badge/pipeline/status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge module Pipeline diff --git a/lib/gitlab/badge/pipeline/template.rb b/lib/gitlab/badge/pipeline/template.rb index e09db32262d..64c3dfcd10b 100644 --- a/lib/gitlab/badge/pipeline/template.rb +++ b/lib/gitlab/badge/pipeline/template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge module Pipeline diff --git a/lib/gitlab/badge/template.rb b/lib/gitlab/badge/template.rb index bfeb0052642..ed2ec50b197 100644 --- a/lib/gitlab/badge/template.rb +++ b/lib/gitlab/badge/template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Badge ## diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb index 04aa6aab771..3cd327f5109 100644 --- a/lib/gitlab/bare_repository_import/importer.rb +++ b/lib/gitlab/bare_repository_import/importer.rb @@ -1,10 +1,15 @@ +# frozen_string_literal: true + module Gitlab module BareRepositoryImport class Importer NoAdminError = Class.new(StandardError) def self.execute(import_path) - import_path << '/' unless import_path.ends_with?('/') + unless import_path.ends_with?('/') + import_path = "#{import_path}/" + end + repos_to_import = Dir.glob(import_path + '**/*.git') unless user = User.admins.order_id_asc.first diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb index c0c666dfb7b..b903c581aac 100644 --- a/lib/gitlab/bare_repository_import/repository.rb +++ b/lib/gitlab/bare_repository_import/repository.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module BareRepositoryImport class Repository @@ -6,9 +8,12 @@ module Gitlab attr_reader :group_path, :project_name, :repo_path def initialize(root_path, repo_path) + unless root_path.ends_with?('/') + root_path = "#{root_path}/" + end + @root_path = root_path @repo_path = repo_path - @root_path << '/' unless root_path.ends_with?('/') full_path = if hashed? && !wiki? diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index a7dfccea2f6..45e550b3450 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module BitbucketImport class Importer diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index d94f70fd1fb..11070a68e02 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module BitbucketImport class ProjectCreator diff --git a/lib/gitlab/bitbucket_server_import/project_creator.rb b/lib/gitlab/bitbucket_server_import/project_creator.rb index 35e8cd7e0ab..48ca4951957 100644 --- a/lib/gitlab/bitbucket_server_import/project_creator.rb +++ b/lib/gitlab/bitbucket_server_import/project_creator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module BitbucketServerImport class ProjectCreator diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb index add048d671e..b369b9e7600 100644 --- a/lib/gitlab/cache/ci/project_pipeline_status.rb +++ b/lib/gitlab/cache/ci/project_pipeline_status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This class is not backed by a table in the main database. # It loads the latest Pipeline for the HEAD of a repository, and caches that # in Redis. diff --git a/lib/gitlab/cache/request_cache.rb b/lib/gitlab/cache/request_cache.rb index b96e161a5b6..4c658dc0b8d 100644 --- a/lib/gitlab/cache/request_cache.rb +++ b/lib/gitlab/cache/request_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Cache # See https://docs.gitlab.com/ee/development/utilities.html#requestcache diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 7a4224e5bbe..49e7f7e1fd7 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class ChangeAccess diff --git a/lib/gitlab/checks/commit_check.rb b/lib/gitlab/checks/commit_check.rb index 7e0c34aada3..6dd74e8fb74 100644 --- a/lib/gitlab/checks/commit_check.rb +++ b/lib/gitlab/checks/commit_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class CommitCheck diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb index 87af4a90572..263972923ed 100644 --- a/lib/gitlab/checks/force_push.rb +++ b/lib/gitlab/checks/force_push.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class ForcePush diff --git a/lib/gitlab/checks/lfs_integrity.rb b/lib/gitlab/checks/lfs_integrity.rb index 3f7adecc621..fa3dc1808df 100644 --- a/lib/gitlab/checks/lfs_integrity.rb +++ b/lib/gitlab/checks/lfs_integrity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class LfsIntegrity diff --git a/lib/gitlab/checks/matching_merge_request.rb b/lib/gitlab/checks/matching_merge_request.rb index 86f4aaeb4d3..71361b12d07 100644 --- a/lib/gitlab/checks/matching_merge_request.rb +++ b/lib/gitlab/checks/matching_merge_request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class MatchingMergeRequest diff --git a/lib/gitlab/checks/post_push_message.rb b/lib/gitlab/checks/post_push_message.rb index 473c0385b34..492dbb5a596 100644 --- a/lib/gitlab/checks/post_push_message.rb +++ b/lib/gitlab/checks/post_push_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class PostPushMessage diff --git a/lib/gitlab/checks/project_created.rb b/lib/gitlab/checks/project_created.rb index cec270d6a58..0058a402a62 100644 --- a/lib/gitlab/checks/project_created.rb +++ b/lib/gitlab/checks/project_created.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class ProjectCreated < PostPushMessage diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb index 3a197078d08..cb3b7acaaad 100644 --- a/lib/gitlab/checks/project_moved.rb +++ b/lib/gitlab/checks/project_moved.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Checks class ProjectMoved < PostPushMessage diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml index 5f9d54ff574..bf7831b937c 100644 --- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml @@ -2,33 +2,33 @@ image: openjdk:8-jdk variables: - ANDROID_COMPILE_SDK: "25" - ANDROID_BUILD_TOOLS: "24.0.0" - ANDROID_SDK_TOOLS: "24.4.1" + ANDROID_COMPILE_SDK: "28" + ANDROID_BUILD_TOOLS: "28.0.3" + ANDROID_SDK_TOOLS: "26.1.1" before_script: - - apt-get --quiet update --yes - - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 - - wget --quiet --output-document=android-sdk.tgz https://dl.google.com/android/android-sdk_r${ANDROID_SDK_TOOLS}-linux.tgz - - tar --extract --gzip --file=android-sdk.tgz - - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter android-${ANDROID_COMPILE_SDK} - - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter platform-tools - - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter build-tools-${ANDROID_BUILD_TOOLS} - - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-android-m2repository - - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-google_play_services - - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-m2repository - - export ANDROID_HOME=$PWD/android-sdk-linux - - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/ - - chmod +x ./gradlew +- apt-get --quiet update --yes +- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 +- wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip +- unzip android-sdk.zip -d android-sdk-linux +- echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null +- echo y | android-sdk-linux/tools/bin/sdkmanager platform-tools > /dev/null +- echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" > /dev/null +- echo y | android-sdk-linux/tools/bin/sdkmanager "extras;google;google_play_services" > /dev/null +- echo y | android-sdk-linux/tools/bin/sdkmanager "extras;google;m2repository" > /dev/null +- export ANDROID_HOME=$PWD/android-sdk-linux +- export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/ +- yes | android-sdk-linux/tools/bin/sdkmanager --licenses & +- chmod +x ./gradlew stages: - - build - - test +- build +- test build: stage: build script: - - ./gradlew assembleDebug + - ./gradlew assembleDebug artifacts: paths: - app/build/outputs/ @@ -36,7 +36,7 @@ build: unitTests: stage: test script: - - ./gradlew test + - ./gradlew test functionalTests: stage: test diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 72547c1b407..6fa59e41d20 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -126,8 +126,8 @@ license_management: artifacts: paths: [gl-license-management-report.json] only: - - branches - only: + refs: + - branches variables: - $GITLAB_FEATURES =~ /\blicense_management\b/ except: diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index deaa14c8434..c1726659a90 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -30,5 +30,20 @@ module Gitlab gon.current_user_avatar_url = current_user.avatar_url end end + + # Exposes the state of a feature flag to the frontend code. + # + # name - The name of the feature flag, e.g. `my_feature`. + # args - Any additional arguments to pass to `Feature.enabled?`. This allows + # you to check if a flag is enabled for a particular user. + def push_frontend_feature_flag(name, *args) + var_name = name.to_s.camelize(:lower) + enabled = Feature.enabled?(name, *args) + + # Here the `true` argument signals gon that the value should be merged + # into any existing ones, instead of overwriting them. This allows you to + # use this method to push multiple feature flags. + gon.push({ features: { var_name => enabled } }, true) + end end end diff --git a/lib/google_api/auth.rb b/lib/google_api/auth.rb index 1aeaa387a49..e724e58e9ca 100644 --- a/lib/google_api/auth.rb +++ b/lib/google_api/auth.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module GoogleApi class Auth attr_reader :access_token, :redirect_uri, :state diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index 77b6610286f..e74ff6a9129 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'google/apis/compute_v1' require 'google/apis/container_v1' require 'google/apis/cloudbilling_v1' diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb index adbed20f152..5ecd6169ecf 100644 --- a/lib/haml_lint/inline_javascript.rb +++ b/lib/haml_lint/inline_javascript.rb @@ -1,4 +1,7 @@ -unless Rails.env.production? # rubocop:disable Naming/FileName +# rubocop:disable Naming/FileName +# frozen_string_literal: true + +unless Rails.env.production? require 'haml_lint/haml_visitor' require 'haml_lint/linter' require 'haml_lint/linter_registry' diff --git a/lib/json_web_token/rsa_token.rb b/lib/json_web_token/rsa_token.rb index d6d6af7089c..160e1e506f1 100644 --- a/lib/json_web_token/rsa_token.rb +++ b/lib/json_web_token/rsa_token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module JSONWebToken class RSAToken < Token attr_reader :key_file diff --git a/lib/json_web_token/token.rb b/lib/json_web_token/token.rb index 5b67715b0b2..ce5d6f248d0 100644 --- a/lib/json_web_token/token.rb +++ b/lib/json_web_token/token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module JSONWebToken class Token attr_accessor :issuer, :subject, :audience, :id diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb index d80cd7d2a4e..293d0c563c5 100644 --- a/lib/mattermost/client.rb +++ b/lib/mattermost/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Mattermost ClientError = Class.new(Mattermost::Error) diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb index 704813dfdf0..a02745486d6 100644 --- a/lib/mattermost/command.rb +++ b/lib/mattermost/command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Mattermost class Command < Client def create(params) diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb index dee6deb7974..054bd5457bd 100644 --- a/lib/mattermost/error.rb +++ b/lib/mattermost/error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Mattermost Error = Class.new(StandardError) end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb index 2aa7a2f64d8..e2083848a8d 100644 --- a/lib/mattermost/session.rb +++ b/lib/mattermost/session.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Mattermost class NoSessionError < Mattermost::Error def message diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb index 95c2f6f9d6b..58120178f50 100644 --- a/lib/mattermost/team.rb +++ b/lib/mattermost/team.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Mattermost class Team < Client # Returns all teams that the current user is a member of diff --git a/lib/microsoft_teams/activity.rb b/lib/microsoft_teams/activity.rb index d2c420efdaf..207e90d2638 100644 --- a/lib/microsoft_teams/activity.rb +++ b/lib/microsoft_teams/activity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MicrosoftTeams class Activity def initialize(title:, subtitle:, text:, image:) diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb index 226ee1373db..c7dec09ba6b 100644 --- a/lib/microsoft_teams/notifier.rb +++ b/lib/microsoft_teams/notifier.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MicrosoftTeams class Notifier def initialize(webhook) diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb index 97f56e10ccf..fd26663fef0 100644 --- a/lib/object_storage/direct_upload.rb +++ b/lib/object_storage/direct_upload.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ObjectStorage # # The DirectUpload c;ass generates a set of presigned URLs diff --git a/lib/omni_auth/strategies/bitbucket.rb b/lib/omni_auth/strategies/bitbucket.rb index ce1bdfe6ee4..6c914b4222a 100644 --- a/lib/omni_auth/strategies/bitbucket.rb +++ b/lib/omni_auth/strategies/bitbucket.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'omniauth-oauth2' module OmniAuth diff --git a/lib/omni_auth/strategies/jwt.rb b/lib/omni_auth/strategies/jwt.rb index ebdb5c7faf0..a792903fde7 100644 --- a/lib/omni_auth/strategies/jwt.rb +++ b/lib/omni_auth/strategies/jwt.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'omniauth' require 'jwt' diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb index 9beb442bfa3..581cc6a37b4 100644 --- a/lib/peek/rblineprof/custom_controller_helpers.rb +++ b/lib/peek/rblineprof/custom_controller_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Peek module Rblineprof module CustomControllerHelpers @@ -41,7 +43,7 @@ module Peek ] end.sort_by{ |a,b,c,d,e,f| -f } - output = "<div class='modal-dialog modal-xl'><div class='modal-content'>" + output = ["<div class='modal-dialog modal-xl'><div class='modal-content'>"] output << "<div class='modal-header'>" output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>" output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>×</span></button>" @@ -93,7 +95,7 @@ module Peek output << "</div></div></div>" - response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output}</div>".html_safe + response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output.join}</div>".html_safe end ret diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb index ab35f7a2258..860963ef94f 100644 --- a/lib/peek/views/gitaly.rb +++ b/lib/peek/views/gitaly.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Peek module Views class Gitaly < View diff --git a/lib/peek/views/host.rb b/lib/peek/views/host.rb index 43c8a35c7ea..b77355ea11b 100644 --- a/lib/peek/views/host.rb +++ b/lib/peek/views/host.rb @@ -1,8 +1,13 @@ +# frozen_string_literal: true + module Peek module Views class Host < View def results - { hostname: Gitlab::Environment.hostname } + { + hostname: Gitlab::Environment.hostname, + canary: Gitlab::Utils.to_boolean(ENV['CANARY']) + } end end end diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index e877ab10248..e2a7d3ef5ba 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rouge module Formatters class HTMLGitlab < Rouge::Formatters::HTML diff --git a/lib/rouge/plugins/common_mark.rb b/lib/rouge/plugins/common_mark.rb index 8f9de061124..d240df5a0e0 100644 --- a/lib/rouge/plugins/common_mark.rb +++ b/lib/rouge/plugins/common_mark.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # A rouge plugin for CommonMark markdown engine. # Used to highlight code generated by CommonMark. diff --git a/lib/rspec_flaky/config.rb b/lib/rspec_flaky/config.rb index 06e96f969f1..55c1d4747b4 100644 --- a/lib/rspec_flaky/config.rb +++ b/lib/rspec_flaky/config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RspecFlaky class Config def self.generate_report? diff --git a/lib/rspec_flaky/example.rb b/lib/rspec_flaky/example.rb index b6e790cbbab..3c1b05257a0 100644 --- a/lib/rspec_flaky/example.rb +++ b/lib/rspec_flaky/example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RspecFlaky # This is a wrapper class for RSpec::Core::Example class Example diff --git a/lib/rspec_flaky/flaky_example.rb b/lib/rspec_flaky/flaky_example.rb index 6be24014d89..da5dbf06bc9 100644 --- a/lib/rspec_flaky/flaky_example.rb +++ b/lib/rspec_flaky/flaky_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RspecFlaky # This represents a flaky RSpec example and is mainly meant to be saved in a JSON file class FlakyExample < OpenStruct diff --git a/lib/rspec_flaky/flaky_examples_collection.rb b/lib/rspec_flaky/flaky_examples_collection.rb index dea23c325be..290a51766e9 100644 --- a/lib/rspec_flaky/flaky_examples_collection.rb +++ b/lib/rspec_flaky/flaky_examples_collection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/hash_with_indifferent_access' require_relative 'flaky_example' diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb index 9cd0c38cb55..19cc0baa2d3 100644 --- a/lib/rspec_flaky/listener.rb +++ b/lib/rspec_flaky/listener.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' require_dependency 'rspec_flaky/config' diff --git a/lib/rspec_flaky/report.rb b/lib/rspec_flaky/report.rb index 1c362fdd20d..9a0fb88c424 100644 --- a/lib/rspec_flaky/report.rb +++ b/lib/rspec_flaky/report.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' require 'time' diff --git a/lib/system_check/app/active_users_check.rb b/lib/system_check/app/active_users_check.rb index 1d72c8d6903..8446c2fc2c8 100644 --- a/lib/system_check/app/active_users_check.rb +++ b/lib/system_check/app/active_users_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class ActiveUsersCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/database_config_exists_check.rb b/lib/system_check/app/database_config_exists_check.rb index d1fae192350..1769145ed63 100644 --- a/lib/system_check/app/database_config_exists_check.rb +++ b/lib/system_check/app/database_config_exists_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class DatabaseConfigExistsCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/git_config_check.rb b/lib/system_check/app/git_config_check.rb index d08a81639e3..4e8d607096c 100644 --- a/lib/system_check/app/git_config_check.rb +++ b/lib/system_check/app/git_config_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class GitConfigCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb index ad41760dff2..6cd53779bfd 100644 --- a/lib/system_check/app/git_user_default_ssh_config_check.rb +++ b/lib/system_check/app/git_user_default_ssh_config_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class GitUserDefaultSSHConfigCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb index 44ec888c197..994af3ab53e 100644 --- a/lib/system_check/app/git_version_check.rb +++ b/lib/system_check/app/git_version_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class GitVersionCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/gitlab_config_exists_check.rb b/lib/system_check/app/gitlab_config_exists_check.rb index 247aa0994e4..1cc5ead0d89 100644 --- a/lib/system_check/app/gitlab_config_exists_check.rb +++ b/lib/system_check/app/gitlab_config_exists_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class GitlabConfigExistsCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/gitlab_config_up_to_date_check.rb b/lib/system_check/app/gitlab_config_up_to_date_check.rb index c609e48e133..58c7e3039c8 100644 --- a/lib/system_check/app/gitlab_config_up_to_date_check.rb +++ b/lib/system_check/app/gitlab_config_up_to_date_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class GitlabConfigUpToDateCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/init_script_exists_check.rb b/lib/system_check/app/init_script_exists_check.rb index d246e058e86..d36dbe7d67d 100644 --- a/lib/system_check/app/init_script_exists_check.rb +++ b/lib/system_check/app/init_script_exists_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class InitScriptExistsCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/init_script_up_to_date_check.rb b/lib/system_check/app/init_script_up_to_date_check.rb index 53a47eb0f42..569c41df6e4 100644 --- a/lib/system_check/app/init_script_up_to_date_check.rb +++ b/lib/system_check/app/init_script_up_to_date_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class InitScriptUpToDateCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/log_writable_check.rb b/lib/system_check/app/log_writable_check.rb index 3e0c436d6ee..e26ad143eb8 100644 --- a/lib/system_check/app/log_writable_check.rb +++ b/lib/system_check/app/log_writable_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class LogWritableCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/migrations_are_up_check.rb b/lib/system_check/app/migrations_are_up_check.rb index 5eedbacce77..b12e9ac6bba 100644 --- a/lib/system_check/app/migrations_are_up_check.rb +++ b/lib/system_check/app/migrations_are_up_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class MigrationsAreUpCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/orphaned_group_members_check.rb b/lib/system_check/app/orphaned_group_members_check.rb index 2b46d36fe51..3e6ffb8190b 100644 --- a/lib/system_check/app/orphaned_group_members_check.rb +++ b/lib/system_check/app/orphaned_group_members_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class OrphanedGroupMembersCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/projects_have_namespace_check.rb b/lib/system_check/app/projects_have_namespace_check.rb index a6ec9f7665c..2bf2529acf1 100644 --- a/lib/system_check/app/projects_have_namespace_check.rb +++ b/lib/system_check/app/projects_have_namespace_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class ProjectsHaveNamespaceCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/redis_version_check.rb b/lib/system_check/app/redis_version_check.rb index a0610e73576..890f8b44d13 100644 --- a/lib/system_check/app/redis_version_check.rb +++ b/lib/system_check/app/redis_version_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class RedisVersionCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/ruby_version_check.rb b/lib/system_check/app/ruby_version_check.rb index 57bbabece1f..d73c39f2c3f 100644 --- a/lib/system_check/app/ruby_version_check.rb +++ b/lib/system_check/app/ruby_version_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class RubyVersionCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/tmp_writable_check.rb b/lib/system_check/app/tmp_writable_check.rb index 99a75e57abf..6687df091d3 100644 --- a/lib/system_check/app/tmp_writable_check.rb +++ b/lib/system_check/app/tmp_writable_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class TmpWritableCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/uploads_directory_exists_check.rb b/lib/system_check/app/uploads_directory_exists_check.rb index 7026d0ba075..940eff9d4cf 100644 --- a/lib/system_check/app/uploads_directory_exists_check.rb +++ b/lib/system_check/app/uploads_directory_exists_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class UploadsDirectoryExistsCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/uploads_path_permission_check.rb b/lib/system_check/app/uploads_path_permission_check.rb index 7df6c060254..4a49f3bc2bb 100644 --- a/lib/system_check/app/uploads_path_permission_check.rb +++ b/lib/system_check/app/uploads_path_permission_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class UploadsPathPermissionCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/app/uploads_path_tmp_permission_check.rb b/lib/system_check/app/uploads_path_tmp_permission_check.rb index b276a81eac1..ae374f4707c 100644 --- a/lib/system_check/app/uploads_path_tmp_permission_check.rb +++ b/lib/system_check/app/uploads_path_tmp_permission_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module App class UploadsPathTmpPermissionCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb index 0f5742dd67f..e06245294c4 100644 --- a/lib/system_check/base_check.rb +++ b/lib/system_check/base_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck # Base class for Checks. You must inherit from here # and implement the methods below when necessary diff --git a/lib/system_check/helpers.rb b/lib/system_check/helpers.rb index 6227e461d24..07d479848fe 100644 --- a/lib/system_check/helpers.rb +++ b/lib/system_check/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module Helpers include ::Gitlab::TaskHelpers diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb index 1db7bf2b782..944913087da 100644 --- a/lib/system_check/incoming_email/foreman_configured_check.rb +++ b/lib/system_check/incoming_email/foreman_configured_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module IncomingEmail class ForemanConfiguredCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/incoming_email/imap_authentication_check.rb b/lib/system_check/incoming_email/imap_authentication_check.rb index 3550c5796b0..613c2296375 100644 --- a/lib/system_check/incoming_email/imap_authentication_check.rb +++ b/lib/system_check/incoming_email/imap_authentication_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module IncomingEmail class ImapAuthenticationCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/incoming_email/initd_configured_check.rb b/lib/system_check/incoming_email/initd_configured_check.rb index ea23b8ef49c..acb4b5a9e74 100644 --- a/lib/system_check/incoming_email/initd_configured_check.rb +++ b/lib/system_check/incoming_email/initd_configured_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module IncomingEmail class InitdConfiguredCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/incoming_email/mail_room_running_check.rb b/lib/system_check/incoming_email/mail_room_running_check.rb index c1807501829..b7aead4624e 100644 --- a/lib/system_check/incoming_email/mail_room_running_check.rb +++ b/lib/system_check/incoming_email/mail_room_running_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module IncomingEmail class MailRoomRunningCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/orphans/namespace_check.rb b/lib/system_check/orphans/namespace_check.rb index 09b57c7b408..53b2d8fd5b3 100644 --- a/lib/system_check/orphans/namespace_check.rb +++ b/lib/system_check/orphans/namespace_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module Orphans class NamespaceCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/orphans/repository_check.rb b/lib/system_check/orphans/repository_check.rb index 2695c658874..ef8fe945f61 100644 --- a/lib/system_check/orphans/repository_check.rb +++ b/lib/system_check/orphans/repository_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck module Orphans class RepositoryCheck < SystemCheck::BaseCheck diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb index 99c9e984107..11818ae54f8 100644 --- a/lib/system_check/simple_executor.rb +++ b/lib/system_check/simple_executor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SystemCheck # Simple Executor is current default executor for GitLab # It is a simple port from display logic in the old check.rake diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8e0324eb194..c1fdad64229 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4236,6 +4236,9 @@ msgstr "" msgid "Operations" msgstr "" +msgid "Operations Dashboard" +msgstr "" + msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab." msgstr "" diff --git a/package.json b/package.json index ac9a73cd2c9..dafb03bf75a 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/preset-env": "^7.1.0", - "@gitlab-org/gitlab-svgs": "^1.29.0", + "@gitlab-org/gitlab-svgs": "^1.32.0", "@gitlab-org/gitlab-ui": "^1.8.0", "autosize": "^4.0.0", "axios": "^0.17.1", @@ -97,6 +97,7 @@ module QA module Integration autoload :Github, 'qa/scenario/test/integration/github' autoload :LDAP, 'qa/scenario/test/integration/ldap' + autoload :InstanceSAML, 'qa/scenario/test/integration/instance_saml' autoload :Kubernetes, 'qa/scenario/test/integration/kubernetes' autoload :Mattermost, 'qa/scenario/test/integration/mattermost' autoload :ObjectStorage, 'qa/scenario/test/integration/object_storage' @@ -180,6 +181,7 @@ module QA autoload :SecretVariables, 'qa/page/project/settings/secret_variables' autoload :Runners, 'qa/page/project/settings/runners' autoload :MergeRequest, 'qa/page/project/settings/merge_request' + autoload :Members, 'qa/page/project/settings/members' end module Issue @@ -267,6 +269,7 @@ module QA autoload :GroupsFilter, 'qa/page/component/groups_filter' autoload :Select2, 'qa/page/component/select2' autoload :DropdownFilter, 'qa/page/component/dropdown_filter' + autoload :UsersSelect, 'qa/page/component/users_select' module Issuable autoload :Common, 'qa/page/component/issuable/common' @@ -300,6 +303,18 @@ module QA autoload :Config, 'qa/specs/config' autoload :Runner, 'qa/specs/runner' end + + ## + # Classes that describe the structure of vendor/third party application pages + # + module Vendor + module SAMLIdp + module Page + autoload :Base, 'qa/vendor/saml_idp/page/base' + autoload :Login, 'qa/vendor/saml_idp/page/login' + end + end + end end QA::Runtime::Release.extend_autoloads! diff --git a/qa/qa/page/component/users_select.rb b/qa/qa/page/component/users_select.rb new file mode 100644 index 00000000000..f88d6450a33 --- /dev/null +++ b/qa/qa/page/component/users_select.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module UsersSelect + def select_user(element, username) + find("#{element_selector_css(element)} input").set(username) + find('.ajax-users-dropdown .user-username', text: "@#{username}").click + end + end + end + end +end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index eab7a85ff04..94b9486b0d5 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -31,6 +31,10 @@ module QA element :register_tab end + view 'app/views/devise/shared/_omniauth_box.html.haml' do + element :saml_login_button + end + def initialize # The login page is usually the entry point for all the scenarios so # we need to wait for the instance to start. That said, in some cases @@ -130,6 +134,11 @@ module QA click_element :sign_in_button end + def sign_in_with_saml + set_initial_password_if_present + click_element :saml_login_button + end + def sign_in_using_gitlab_credentials(user) switch_to_sign_in_tab if has_sign_in_tab? switch_to_standard_tab if has_standard_tab? diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index d9f01c50f19..63c719e5fe1 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -9,6 +9,7 @@ module QA element :settings_link, 'link_to edit_project_path' element :repository_link, "title: _('Repository')" element :link_pipelines + element :link_members_settings element :pipelines_settings_link, "title: _('CI / CD')" element :operations_kubernetes_link, "title: _('Kubernetes')" element :operations_environments_link @@ -51,6 +52,14 @@ module QA end end + def click_members_settings + hover_settings do + within_submenu do + click_element :link_members_settings + end + end + end + def click_operations_kubernetes hover_operations do within_submenu do diff --git a/qa/qa/page/project/settings/members.rb b/qa/qa/page/project/settings/members.rb new file mode 100644 index 00000000000..7fed93ca83f --- /dev/null +++ b/qa/qa/page/project/settings/members.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Settings + class Members < Page::Base + include Page::Component::UsersSelect + + view 'app/views/projects/project_members/_new_project_member.html.haml' do + element :member_select_input + element :add_member_button + end + + view 'app/views/projects/project_members/_team.html.haml' do + element :members_list + end + + def add_member(username) + select_user :member_select_input, username + click_element :add_member_button + end + end + end + end + end +end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 4c64270ce92..9aaf57e8d83 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -51,6 +51,10 @@ module QA } ) + if QA::Runtime::Env.accept_insecure_certs? + capabilities['acceptInsecureCerts'] = true + end + options = Selenium::WebDriver::Chrome::Options.new options.add_argument("window-size=1240,1680") diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 5bebb5ccec0..4a2109799fa 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -8,6 +8,10 @@ module QA enabled?(ENV['CHROME_HEADLESS']) end + def accept_insecure_certs? + enabled?(ENV['ACCEPT_INSECURE_CERTS']) + end + def running_in_ci? ENV['CI'] || ENV['CI_SERVER'] end diff --git a/qa/qa/scenario/test/integration/instance_saml.rb b/qa/qa/scenario/test/integration/instance_saml.rb new file mode 100644 index 00000000000..0697d0c2a0e --- /dev/null +++ b/qa/qa/scenario/test/integration/instance_saml.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class InstanceSAML < Test::Instance::All + tags :instance_saml + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb new file mode 100644 index 00000000000..8d5055aab45 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + context :manage, :orchestrated, :instance_saml do + describe 'Instance wide SAML SSO' do + it 'User logs in to gitlab with SAML SSO' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + + Page::Main::Login.act { sign_in_with_saml } + + Vendor::SAMLIdp::Page::Login.act { login } + + expect(page).to have_content('Welcome to GitLab') + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb new file mode 100644 index 00000000000..b276c7ee579 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + context :manage do + describe 'Add project member' do + it 'user adds project member' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + + user = Factory::Resource::User.fabricate! + + Page::Main::Menu.perform { |main| main.sign_out } + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Resource::Project.fabricate! do |resource| + resource.name = 'add-member-project' + end + + Page::Project::Menu.act { click_members_settings } + Page::Project::Settings::Members.perform do |page| + page.add_member(user.username) + end + + expect(page).to have_content("#{user.name} @#{user.username} Given access") + end + end + end +end diff --git a/qa/qa/vendor/saml_idp/page/base.rb b/qa/qa/vendor/saml_idp/page/base.rb new file mode 100644 index 00000000000..286cb0a8cd8 --- /dev/null +++ b/qa/qa/vendor/saml_idp/page/base.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module QA + module Vendor + module SAMLIdp + module Page + class Base + include Capybara::DSL + include Scenario::Actable + end + end + end + end +end diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb new file mode 100644 index 00000000000..9c1f9904a7a --- /dev/null +++ b/qa/qa/vendor/saml_idp/page/login.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module SAMLIdp + module Page + class Login < Page::Base + def login + fill_in 'username', with: 'user1' + fill_in 'password', with: 'user1pass' + click_on 'Login' + end + end + end + end + end +end diff --git a/qa/spec/scenario/test/integration/instance_saml_spec.rb b/qa/spec/scenario/test/integration/instance_saml_spec.rb new file mode 100644 index 00000000000..cb8a6a630cc --- /dev/null +++ b/qa/spec/scenario/test/integration/instance_saml_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +describe QA::Scenario::Test::Integration::InstanceSAML do + context '#perform' do + it_behaves_like 'a QA scenario class' do + let(:tags) { [:instance_saml] } + end + end +end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 5c8180baf8a..1484676eea3 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -352,6 +352,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(json_response['has_trace']).to be true end end + + it 'exposes the stage the job belongs to' do + expect(json_response['stage']).to eq('test') + end end context 'when requesting JSON job is triggered' do diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index ac961e98a61..4e6f73ef58a 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -101,4 +101,25 @@ describe 'Group show page' do expect(page).to have_emoji('smile') end end + + context 'where group has projects' do + let(:user) { create(:user) } + + before do + group.add_owner(user) + sign_in(user) + end + + it 'allows users to sorts projects by most stars', :js do + project1 = create(:project, namespace: group, star_count: 2) + project2 = create(:project, namespace: group, star_count: 3) + project3 = create(:project, namespace: group, star_count: 0) + + visit group_path(group, sort: :stars_desc) + + expect(find('.group-row:nth-child(1) .namespace-title > a')).to have_content(project2.title) + expect(find('.group-row:nth-child(2) .namespace-title > a')).to have_content(project1.title) + expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title) + end + end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 2076ce7b4f7..6224cbffe9d 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -663,6 +663,56 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do expect(page).to have_content('This job does not have a trace.') end end + + context 'with erased job', :js do + let(:job) { create(:ci_build, :erased, pipeline: pipeline) } + + it 'renders erased job warning' do + visit project_job_path(project, job) + wait_for_requests + + page.within('.js-job-erased-block') do + expect(page).to have_content('Job has been erased') + end + end + end + + context 'without erased job', :js do + let(:job) { create(:ci_build, pipeline: pipeline) } + + it 'does not render erased job warning' do + visit project_job_path(project, job) + wait_for_requests + + expect(page).not_to have_css('.js-job-erased-block') + end + end + + context 'on mobile', :js do + let(:job) { create(:ci_build, pipeline: pipeline) } + + it 'renders collpased sidebar' do + page.current_window.resize_to(600, 800) + + visit project_job_path(project, job) + wait_for_requests + + expect(page).to have_css('.js-build-sidebar.right-sidebar-collapsed', visible: false) + expect(page).not_to have_css('.js-build-sidebar.right-sidebar-expanded', visible: false) + end + end + + context 'on desktop', :js do + let(:job) { create(:ci_build, pipeline: pipeline) } + + it 'renders expanded sidebar' do + visit project_job_path(project, job) + wait_for_requests + + expect(page).to have_css('.js-build-sidebar.right-sidebar-expanded') + expect(page).not_to have_css('.js-build-sidebar.right-sidebar-collpased') + end + end end describe "POST /:project/jobs/:id/cancel", :js do diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index 9e3f2c69606..7d164539d9a 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -66,7 +66,7 @@ describe BranchesFinder do context 'filter and sort' do it 'filters branches by name and sorts by recently_updated' do - params = { sort: 'updated_desc', search: 'feature' } + params = { sort: 'updated_desc', search: 'feat' } branches_finder = described_class.new(repository, params) result = branches_finder.execute @@ -75,6 +75,17 @@ describe BranchesFinder do expect(result.count).to eq(2) end + it 'filters branches by name and sorts by recently_updated, with exact matches first' do + params = { sort: 'updated_desc', search: 'feature' } + branches_finder = described_class.new(repository, params) + + result = branches_finder.execute + + expect(result.first.name).to eq('feature') + expect(result.second.name).to eq('feature_conflict') + expect(result.count).to eq(2) + end + it 'filters branches by name and sorts by last_updated' do params = { sort: 'updated_asc', search: 'feature' } branches_finder = described_class.new(repository, params) @@ -84,6 +95,26 @@ describe BranchesFinder do expect(result.first.name).to eq('feature') expect(result.count).to eq(2) end + + it 'filters branches by name that begins with' do + params = { search: '^feature_' } + branches_finder = described_class.new(repository, params) + + result = branches_finder.execute + + expect(result.first.name).to eq('feature_conflict') + expect(result.count).to eq(1) + end + + it 'filters branches by name that ends with' do + params = { search: 'feature$' } + branches_finder = described_class.new(repository, params) + + result = branches_finder.execute + + expect(result.first.name).to eq('feature') + expect(result.count).to eq(1) + end end end end diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json index 07e674216fa..8218474705c 100644 --- a/spec/fixtures/api/schemas/job/job_details.json +++ b/spec/fixtures/api/schemas/job/job_details.json @@ -7,7 +7,8 @@ "artifact", "runner", "runners", - "has_trace" + "has_trace", + "stage" ], "properties": { "artifact": { "$ref": "artifact.json" }, @@ -16,6 +17,7 @@ "deployment_status": { "$ref": "deployment_status.json" }, "runner": { "$ref": "runner.json" }, "runners": { "$ref": "runners.json" }, - "has_trace": { "type": "boolean" } + "has_trace": { "type": "boolean" }, + "stage": { "type": "string" } } } diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 363ebc88afd..c112c8ed633 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -2,6 +2,13 @@ require 'spec_helper' describe PreferencesHelper do describe '#dashboard_choices' do + let(:user) { build(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + allow(helper).to receive(:can?).and_return(false) + end + it 'raises an exception when defined choices may be missing' do expect(User).to receive(:dashboards).and_return(foo: 'foo') expect { helper.dashboard_choices }.to raise_error(RuntimeError) diff --git a/spec/javascripts/.eslintrc.yml b/spec/javascripts/.eslintrc.yml index 9b2c84ce9f5..1448849a0b7 100644 --- a/spec/javascripts/.eslintrc.yml +++ b/spec/javascripts/.eslintrc.yml @@ -37,7 +37,5 @@ rules: - 'fixtures/blob' # Temporarily disabled to facilitate an upgrade to eslint-plugin-jasmine jasmine/new-line-before-expect: off - jasmine/new-line-between-declarations: off jasmine/no-promise-without-done-fail: off - jasmine/prefer-jasmine-matcher: off jasmine/prefer-toHaveBeenCalledWith: off diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index 0c5d68990d5..3f84a46e8d9 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -55,6 +55,7 @@ import '~/lib/utils/common_utils'; }); }; }); + afterEach(function() { // restore original url root value gon.relative_url_root = urlRoot; @@ -64,6 +65,7 @@ import '~/lib/utils/common_utils'; awardsHandler.destroy(); }); + describe('::showEmojiMenu', function() { it('should show emoji menu when Add emoji button clicked', function(done) { $('.js-add-award') @@ -78,6 +80,7 @@ import '~/lib/utils/common_utils'; return expect($('.js-awards-block.current').length).toBe(1); }); }); + it('should also show emoji menu for the smiley icon in notes', function(done) { $('.js-add-award.note-action-button').click(); return lazyAssert(done, function() { @@ -85,6 +88,7 @@ import '~/lib/utils/common_utils'; return expect($emojiMenu.length).toBe(1); }); }); + it('should remove emoji menu when body is clicked', function(done) { $('.js-add-award') .eq(0) @@ -98,6 +102,7 @@ import '~/lib/utils/common_utils'; return expect($('.js-awards-block.current').length).toBe(0); }); }); + it('should not remove emoji menu when search is clicked', function(done) { $('.js-add-award') .eq(0) @@ -123,6 +128,7 @@ import '~/lib/utils/common_utils'; expect($emojiButton.next('.js-counter').text()).toBe('1'); return expect($votesBlock.hasClass('hidden')).toBe(false); }); + it('should remove the emoji when we click again', function() { var $emojiButton, $votesBlock; $votesBlock = $('.js-awards-block').eq(0); @@ -142,6 +148,7 @@ import '~/lib/utils/common_utils'; return expect($emojiButton.next('.js-counter').text()).toBe('4'); }); }); + describe('::userAuthored', function() { it('should update tooltip to user authored title', function() { var $thumbsUpEmoji, $votesBlock; @@ -153,6 +160,7 @@ import '~/lib/utils/common_utils'; 'You cannot vote on your own issue, MR and note', ); }); + it('should restore tooltip back to initial vote list', function() { var $thumbsUpEmoji, $votesBlock; jasmine.clock().install(); @@ -165,6 +173,7 @@ import '~/lib/utils/common_utils'; return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam'); }); }); + describe('::getAwardUrl', function() { return it('returns the url for request', function() { return expect(awardsHandler.getAwardUrl()).toBe( @@ -172,6 +181,7 @@ import '~/lib/utils/common_utils'; ); }); }); + describe('::addAward and ::checkMutuality', function() { return it('should handle :+1: and :-1: mutuality', function() { var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl; @@ -189,6 +199,7 @@ import '~/lib/utils/common_utils'; return expect($thumbsDownEmoji.hasClass('active')).toBe(true); }); }); + describe('::removeEmoji', function() { return it('should remove emoji', function() { var $votesBlock, awardUrl; @@ -200,6 +211,7 @@ import '~/lib/utils/common_utils'; return expect($votesBlock.find('[data-name=fire]').length).toBe(0); }); }); + describe('::addYouToUserList', function() { it('should prepend "You" to the award tooltip', function() { var $thumbsUpEmoji, $votesBlock, awardUrl; @@ -222,6 +234,7 @@ import '~/lib/utils/common_utils'; return expect($thumbsUpEmoji.data('originalTitle')).toBe('You and sam'); }); }); + describe('::removeYouToUserList', function() { it('removes "You" from the front of the tooltip', function() { var $thumbsUpEmoji, $votesBlock, awardUrl; @@ -246,6 +259,7 @@ import '~/lib/utils/common_utils'; return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam'); }); }); + describe('::searchEmojis', () => { it('should filter the emoji', function(done) { return openAndWaitForEmojiMenu() @@ -263,6 +277,7 @@ import '~/lib/utils/common_utils'; done.fail(`Failed to open and build emoji menu: ${err.message}`); }); }); + it('should clear the search when searching for nothing', function(done) { return openAndWaitForEmojiMenu() .then(() => { @@ -305,6 +320,7 @@ import '~/lib/utils/common_utils'; done.fail(`Failed to open and build emoji menu: ${err.message}`); }); }); + it('should remove already selected emoji', function(done) { return openEmojiMenuAndAddEmoji() .then(() => { diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index d8aa5c636da..b3a5eac8982 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -58,12 +58,14 @@ describe('Quick Submit behavior', function () { expect(submitButton).toBeDisabled(); }); + it('disables button of type submit', () => { const submitButton = $('.js-quick-submit input[type=submit]'); this.textarea.trigger(keydownEvent()); expect(submitButton).toBeDisabled(); }); + it('only clicks one submit', () => { const existingSubmit = $('.js-quick-submit input[type=submit]'); // Add an extra submit button diff --git a/spec/javascripts/bootstrap_jquery_spec.js b/spec/javascripts/bootstrap_jquery_spec.js index 052465d8d88..cd61d920fa0 100644 --- a/spec/javascripts/bootstrap_jquery_spec.js +++ b/spec/javascripts/bootstrap_jquery_spec.js @@ -9,6 +9,7 @@ import '~/commons/bootstrap'; beforeEach(function() { return setFixtures('<input type="text" />'); }); + it('adds the disabled attribute', function() { var $input; $input = $('input').first(); @@ -26,6 +27,7 @@ import '~/commons/bootstrap'; beforeEach(function() { return setFixtures('<input type="text" disabled="disabled" class="disabled" />'); }); + it('removes the disabled attribute', function() { var $input; $input = $('input').first(); diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js index 6c3e73f134e..24e4871ce44 100644 --- a/spec/javascripts/datetime_utility_spec.js +++ b/spec/javascripts/datetime_utility_spec.js @@ -158,9 +158,9 @@ describe('getTimeframeWindowFrom', () => { const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5); expect(timeframe.length).toBe(5); timeframe.forEach((timeframeItem, index) => { - expect(timeframeItem.getFullYear() === mockTimeframe[index].getFullYear()).toBe(true); - expect(timeframeItem.getMonth() === mockTimeframe[index].getMonth()).toBe(true); - expect(timeframeItem.getDate() === mockTimeframe[index].getDate()).toBeTruthy(); + expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear()); + expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth()); + expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate()); }); }); }); diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index c986ea604b2..1f7d5f42322 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -6,6 +6,8 @@ import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +Vue.use(Vuex); + const discussionFixture = 'merge_requests/diff_discussion.json'; describe('diff_file_header', () => { @@ -58,19 +60,19 @@ describe('diff_file_header', () => { describe('titleLink', () => { beforeEach(() => { + props.discussionPath = 'link://to/discussion'; Object.assign(props.diffFile, { - fileHash: 'badc0ffee', submoduleLink: 'link://to/submodule', submoduleTreeUrl: 'some://tree/url', }); }); - it('returns the fileHash for files', () => { + it('returns the discussionPath for files', () => { props.diffFile.submodule = false; vm = mountComponentWithStore(Component, { props, store }); - expect(vm.titleLink).toBe(`#${props.diffFile.fileHash}`); + expect(vm.titleLink).toBe(props.discussionPath); }); it('returns the submoduleTreeUrl for submodules', () => { @@ -91,6 +93,13 @@ describe('diff_file_header', () => { expect(vm.titleLink).toBe(props.diffFile.submoduleLink); }); + + it('sets the correct path to the discussion', () => { + props.discussionPath = 'link://to/discussion'; + vm = mountComponentWithStore(Component, { props, store }); + const href = vm.$el.querySelector('.js-title-wrapper').getAttribute('href'); + expect(href).toBe(vm.discussionPath); + }); }); describe('filePath', () => { diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 13859f43e98..b8d4b31ee04 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -24,14 +24,14 @@ describe('DiffFile', () => { expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0); expect(el.querySelector('.js-file-title')).toBeDefined(); - expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true); + expect(el.querySelector('.file-title-name').innerText.indexOf(filePath)).toBeGreaterThan(-1); expect(el.querySelector('.js-syntax-highlight')).toBeDefined(); expect(vm.file.renderIt).toEqual(false); vm.file.renderIt = true; vm.$nextTick(() => { - expect(el.querySelectorAll('.line_content').length > 5).toEqual(true); + expect(el.querySelectorAll('.line_content').length).toBeGreaterThan(5); }); }); @@ -98,9 +98,7 @@ describe('DiffFile', () => { 'This source diff could not be displayed because it is too large', ); expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined(); - expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK) > -1).toEqual( - true, - ); + expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK)).toBeGreaterThan(-1); done(); }); diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js index 663c0680845..f36454cc23e 100644 --- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js +++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js @@ -94,7 +94,7 @@ describe('DiffLineGutterContent', () => { const component = createComponent({ lineNumber, lineCode }); const link = component.$el.querySelector('a'); - expect(link.href.indexOf(`#${lineCode}`) > -1).toEqual(true); + expect(link.href.indexOf(`#${lineCode}`)).toBeGreaterThan(-1); expect(link.dataset.linenumber).toEqual(lineNumber.toString()); }); diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js index b02328dd359..705558e860b 100644 --- a/spec/javascripts/diffs/components/inline_diff_view_spec.js +++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js @@ -27,7 +27,7 @@ describe('InlineDiffView', () => { expect(el.querySelectorAll('tr.line_holder').length).toEqual(6); expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2); expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1); - expect(el.textContent.indexOf('Bad dates') > -1).toEqual(true); + expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1); }); it('should render discussions', done => { @@ -37,7 +37,7 @@ describe('InlineDiffView', () => { Vue.nextTick(() => { expect(el.querySelectorAll('.notes_holder').length).toEqual(1); expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5); - expect(el.innerText.indexOf('comment 5') > -1).toEqual(true); + expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1); component.$store.dispatch('setInitialNotes', []); done(); diff --git a/spec/javascripts/emoji_spec.js b/spec/javascripts/emoji_spec.js index 124d91f4477..629422780e8 100644 --- a/spec/javascripts/emoji_spec.js +++ b/spec/javascripts/emoji_spec.js @@ -140,6 +140,7 @@ describe('gl_emoji', () => { }, ); }); + it('bomb emoji with sprite fallback', () => { const emojiKey = 'bomb'; const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { @@ -195,24 +196,31 @@ describe('gl_emoji', () => { it('should gracefully handle empty string', () => { expect(isFlagEmoji('')).toBeFalsy(); }); + it('should detect flag_ac', () => { expect(isFlagEmoji('🇦🇨')).toBeTruthy(); }); + it('should detect flag_us', () => { expect(isFlagEmoji('🇺🇸')).toBeTruthy(); }); + it('should detect flag_zw', () => { expect(isFlagEmoji('🇿🇼')).toBeTruthy(); }); + it('should not detect flags', () => { expect(isFlagEmoji('🎏')).toBeFalsy(); }); + it('should not detect triangular_flag_on_post', () => { expect(isFlagEmoji('🚩')).toBeFalsy(); }); + it('should not detect single letter', () => { expect(isFlagEmoji('🇦')).toBeFalsy(); }); + it('should not detect >2 letters', () => { expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy(); }); @@ -222,15 +230,19 @@ describe('gl_emoji', () => { it('should gracefully handle empty string', () => { expect(isRainbowFlagEmoji('')).toBeFalsy(); }); + it('should detect rainbow_flag', () => { expect(isRainbowFlagEmoji('🏳🌈')).toBeTruthy(); }); + it('should not detect flag_white on its\' own', () => { expect(isRainbowFlagEmoji('🏳')).toBeFalsy(); }); + it('should not detect rainbow on its\' own', () => { expect(isRainbowFlagEmoji('🌈')).toBeFalsy(); }); + it('should not detect flag_white with something else', () => { expect(isRainbowFlagEmoji('🏳🔵')).toBeFalsy(); }); @@ -240,15 +252,19 @@ describe('gl_emoji', () => { it('should gracefully handle empty string', () => { expect(isKeycapEmoji('')).toBeFalsy(); }); + it('should detect one(keycap)', () => { expect(isKeycapEmoji('1️⃣')).toBeTruthy(); }); + it('should detect nine(keycap)', () => { expect(isKeycapEmoji('9️⃣')).toBeTruthy(); }); + it('should not detect ten(keycap)', () => { expect(isKeycapEmoji('🔟')).toBeFalsy(); }); + it('should not detect hash(keycap)', () => { expect(isKeycapEmoji('#⃣')).toBeFalsy(); }); @@ -258,24 +274,31 @@ describe('gl_emoji', () => { it('should gracefully handle empty string', () => { expect(isSkinToneComboEmoji('')).toBeFalsy(); }); + it('should detect hand_splayed_tone5', () => { expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy(); }); + it('should not detect hand_splayed', () => { expect(isSkinToneComboEmoji('🖐')).toBeFalsy(); }); + it('should detect lifter_tone1', () => { expect(isSkinToneComboEmoji('🏋🏻')).toBeTruthy(); }); + it('should not detect lifter', () => { expect(isSkinToneComboEmoji('🏋')).toBeFalsy(); }); + it('should detect rowboat_tone4', () => { expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy(); }); + it('should not detect rowboat', () => { expect(isSkinToneComboEmoji('🚣')).toBeFalsy(); }); + it('should not detect individual tone emoji', () => { expect(isSkinToneComboEmoji('🏻')).toBeFalsy(); }); @@ -285,9 +308,11 @@ describe('gl_emoji', () => { it('should gracefully handle empty string', () => { expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy(); }); + it('should detect horse_racing_tone2', () => { expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy(); }); + it('should not detect horse_racing', () => { expect(isHorceRacingSkinToneComboEmoji('🏇')).toBeFalsy(); }); @@ -297,36 +322,47 @@ describe('gl_emoji', () => { it('should gracefully handle empty string', () => { expect(isPersonZwjEmoji('')).toBeFalsy(); }); + it('should detect couple_mm', () => { expect(isPersonZwjEmoji('👨❤️👨')).toBeTruthy(); }); + it('should not detect couple_with_heart', () => { expect(isPersonZwjEmoji('💑')).toBeFalsy(); }); + it('should not detect couplekiss', () => { expect(isPersonZwjEmoji('💏')).toBeFalsy(); }); + it('should detect family_mmb', () => { expect(isPersonZwjEmoji('👨👨👦')).toBeTruthy(); }); + it('should detect family_mwgb', () => { expect(isPersonZwjEmoji('👨👩👧👦')).toBeTruthy(); }); + it('should not detect family', () => { expect(isPersonZwjEmoji('👪')).toBeFalsy(); }); + it('should detect kiss_ww', () => { expect(isPersonZwjEmoji('👩❤️💋👩')).toBeTruthy(); }); + it('should not detect girl', () => { expect(isPersonZwjEmoji('👧')).toBeFalsy(); }); + it('should not detect girl_tone5', () => { expect(isPersonZwjEmoji('👧🏿')).toBeFalsy(); }); + it('should not detect man', () => { expect(isPersonZwjEmoji('👨')).toBeFalsy(); }); + it('should not detect woman', () => { expect(isPersonZwjEmoji('👩')).toBeFalsy(); }); @@ -341,6 +377,7 @@ describe('gl_emoji', () => { ); expect(isSupported).toBeTruthy(); }); + it('should gracefully handle empty string without unicode support', () => { const isSupported = isEmojiUnicodeSupported( {}, @@ -349,6 +386,7 @@ describe('gl_emoji', () => { ); expect(isSupported).toBeFalsy(); }); + it('bomb(6.0) with 6.0 support', () => { const emojiKey = 'bomb'; const unicodeSupportMap = Object.assign({}, emptySupportMap, { diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 68bbbf838da..3dc8089cd83 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -237,7 +237,7 @@ describe('Dropdown Utils', () => { it('should not linear-gradient more than 4 colors', () => { const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD', '#EEEEEE']); - expect(gradient.indexOf('#EEEEEE') === -1).toEqual(true); + expect(gradient.indexOf('#EEEEEE')).toBe(-1); }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js index ab0ab72720e..cf7789a1d57 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js @@ -19,7 +19,7 @@ describe('Filtered Search Token Keys', () => { describe('get', () => { it('should return tokenKeys', () => { - expect(new FilteredSearchTokenKeys().get() !== null).toBe(true); + expect(new FilteredSearchTokenKeys().get()).not.toBeNull(); }); it('should return tokenKeys as an array', () => { @@ -40,7 +40,7 @@ describe('Filtered Search Token Keys', () => { describe('getConditions', () => { it('should return conditions', () => { - expect(new FilteredSearchTokenKeys().getConditions() !== null).toBe(true); + expect(new FilteredSearchTokenKeys().getConditions()).not.toBeNull(); }); it('should return conditions as an array', () => { @@ -51,7 +51,7 @@ describe('Filtered Search Token Keys', () => { describe('searchByKey', () => { it('should return null when key not found', () => { const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKey('notakey'); - expect(tokenKey === null).toBe(true); + expect(tokenKey).toBeNull(); }); it('should return tokenKey when found by key', () => { @@ -63,7 +63,7 @@ describe('Filtered Search Token Keys', () => { describe('searchBySymbol', () => { it('should return null when symbol not found', () => { const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchBySymbol('notasymbol'); - expect(tokenKey === null).toBe(true); + expect(tokenKey).toBeNull(); }); it('should return tokenKey when found by symbol', () => { @@ -75,7 +75,7 @@ describe('Filtered Search Token Keys', () => { describe('searchByKeyParam', () => { it('should return null when key param not found', () => { const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam('notakeyparam'); - expect(tokenKey === null).toBe(true); + expect(tokenKey).toBeNull(); }); it('should return tokenKey when found by key param', () => { @@ -92,7 +92,7 @@ describe('Filtered Search Token Keys', () => { describe('searchByConditionUrl', () => { it('should return null when condition url not found', () => { const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionUrl(null); - expect(condition === null).toBe(true); + expect(condition).toBeNull(); }); it('should return condition when found by url', () => { @@ -106,7 +106,7 @@ describe('Filtered Search Token Keys', () => { it('should return null when condition tokenKey and value not found', () => { const condition = new FilteredSearchTokenKeys([], [], conditions) .searchByConditionKeyValue(null, null); - expect(condition === null).toBe(true); + expect(condition).toBeNull(); }); it('should return condition when found by tokenKey and value', () => { diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 25b819543da..62c87642184 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -168,9 +168,9 @@ describe('glDropdown', function describeDropdown() { it('should show loading indicator while search results are being fetched by backend', () => { const dropdownMenu = document.querySelector('.dropdown-menu'); - expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true); + expect(dropdownMenu.className.indexOf('is-loading')).not.toBe(-1); remoteCallback(); - expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false); + expect(dropdownMenu.className.indexOf('is-loading')).toBe(-1); }); it('should not focus search input while remote task is not complete', () => { diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js index 02d1ca1cc3b..8854d3d554a 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js @@ -205,10 +205,12 @@ describe("ContributorsStatGraphUtil", function () { it("returns true if date_range is null", function () { expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true); }); + it("returns true if date is in range", function () { var date_range = [new Date("2013-01-01"), new Date("2013-12-12")]; expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true); }); + it("returns false if date is not in range", function () { var date_range = [new Date("1999-12-01"), new Date("2000-12-01")]; expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false); diff --git a/spec/javascripts/groups/components/group_item_spec.js b/spec/javascripts/groups/components/group_item_spec.js index d0cac5efc40..49d4f7efd72 100644 --- a/spec/javascripts/groups/components/group_item_spec.js +++ b/spec/javascripts/groups/components/group_item_spec.js @@ -45,7 +45,7 @@ describe('GroupItemComponent', () => { expect(Object.keys(rowClass).length).toBe(classes.length); Object.keys(rowClass).forEach((className) => { - expect(classes.indexOf(className) > -1).toBeTruthy(); + expect(classes.indexOf(className)).toBeGreaterThan(-1); }); }); }); diff --git a/spec/javascripts/groups/components/groups_spec.js b/spec/javascripts/groups/components/groups_spec.js index 793c4909d89..5af86b55532 100644 --- a/spec/javascripts/groups/components/groups_spec.js +++ b/spec/javascripts/groups/components/groups_spec.js @@ -53,7 +53,7 @@ describe('GroupsComponent', () => { expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined(); expect(vm.$el.querySelector('.group-list-tree')).toBeDefined(); expect(vm.$el.querySelector('.gl-pagination')).toBeDefined(); - expect(vm.$el.querySelectorAll('.has-no-search-results').length === 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('.has-no-search-results').length).toBe(0); done(); }); }); diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js index ee7ee18259e..e6a57495eb1 100644 --- a/spec/javascripts/groups/components/item_stats_spec.js +++ b/spec/javascripts/groups/components/item_stats_spec.js @@ -107,7 +107,7 @@ describe('ItemStatsComponent', () => { const visibilityIconEl = vm.$el.querySelector('.item-visibility'); expect(visibilityIconEl).not.toBe(null); expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); - expect(visibilityIconEl.querySelectorAll('svg').length > 0).toBeTruthy(); + expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0); vm.$destroy(); }); @@ -120,10 +120,10 @@ describe('ItemStatsComponent', () => { const vm = createComponent(item); const projectStarIconEl = vm.$el.querySelector('.project-stars'); - expect(projectStarIconEl).not.toBe(null); - expect(projectStarIconEl.querySelectorAll('svg').length > 0).toBeTruthy(); - expect(projectStarIconEl.querySelectorAll('.stat-value').length > 0).toBeTruthy(); - expect(vm.$el.querySelectorAll('.last-updated').length > 0).toBeTruthy(); + expect(projectStarIconEl).not.toBeNull(); + expect(projectStarIconEl.querySelectorAll('svg').length).toBeGreaterThan(0); + expect(projectStarIconEl.querySelectorAll('.stat-value').length).toBeGreaterThan(0); + expect(vm.$el.querySelectorAll('.last-updated').length).toBeGreaterThan(0); vm.$destroy(); }); diff --git a/spec/javascripts/groups/components/item_stats_value_spec.js b/spec/javascripts/groups/components/item_stats_value_spec.js index 5e35ae4d36c..2a995e8efe6 100644 --- a/spec/javascripts/groups/components/item_stats_value_spec.js +++ b/spec/javascripts/groups/components/item_stats_value_spec.js @@ -57,8 +57,8 @@ describe('ItemStatsValueComponent', () => { it('renders component element correctly', () => { expect(vm.$el.classList.contains('number-subgroups')).toBeTruthy(); - expect(vm.$el.querySelectorAll('svg').length > 0).toBeTruthy(); - expect(vm.$el.querySelectorAll('.stat-value').length > 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('svg').length).toBeGreaterThan(0); + expect(vm.$el.querySelectorAll('.stat-value').length).toBeGreaterThan(0); }); it('renders element tooltip correctly', () => { diff --git a/spec/javascripts/groups/store/groups_store_spec.js b/spec/javascripts/groups/store/groups_store_spec.js index d74f38f476e..78caf8f80bf 100644 --- a/spec/javascripts/groups/store/groups_store_spec.js +++ b/spec/javascripts/groups/store/groups_store_spec.js @@ -29,7 +29,7 @@ describe('ProjectsStore', () => { store.setGroups(mockGroups); expect(store.state.groups.length).toBe(mockGroups.length); expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object)); - expect(Object.keys(store.state.groups[0]).indexOf('fullName') > -1).toBeTruthy(); + expect(Object.keys(store.state.groups[0]).indexOf('fullName')).toBeGreaterThan(-1); }); }); @@ -41,8 +41,8 @@ describe('ProjectsStore', () => { store.setSearchedGroups(mockSearchedGroups); expect(store.state.groups.length).toBe(mockSearchedGroups.length); expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object)); - expect(Object.keys(store.state.groups[0]).indexOf('fullName') > -1).toBeTruthy(); - expect(Object.keys(store.state.groups[0].children[0]).indexOf('fullName') > -1).toBeTruthy(); + expect(Object.keys(store.state.groups[0]).indexOf('fullName')).toBeGreaterThan(-1); + expect(Object.keys(store.state.groups[0].children[0]).indexOf('fullName')).toBeGreaterThan(-1); }); }); @@ -54,7 +54,7 @@ describe('ProjectsStore', () => { store.setGroupChildren(mockParentGroupItem, mockRawChildren); expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object)); expect(mockParentGroupItem.children.length).toBe(1); - expect(Object.keys(mockParentGroupItem.children[0]).indexOf('fullName') > -1).toBeTruthy(); + expect(Object.keys(mockParentGroupItem.children[0]).indexOf('fullName')).toBeGreaterThan(-1); expect(mockParentGroupItem.isOpen).toBeTruthy(); expect(mockParentGroupItem.isChildrenLoading).toBeFalsy(); }); @@ -81,14 +81,14 @@ describe('ProjectsStore', () => { store = new GroupsStore(); updatedGroupItem = store.formatGroupItem(mockRawChildren[0]); - expect(Object.keys(updatedGroupItem).indexOf('fullName') > -1).toBeTruthy(); + expect(Object.keys(updatedGroupItem).indexOf('fullName')).toBeGreaterThan(-1); expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].children_count); expect(updatedGroupItem.isChildrenLoading).toBe(false); expect(updatedGroupItem.isBeingRemoved).toBe(false); store = new GroupsStore(true); updatedGroupItem = store.formatGroupItem(mockRawChildren[0]); - expect(Object.keys(updatedGroupItem).indexOf('fullName') > -1).toBeTruthy(); + expect(Object.keys(updatedGroupItem).indexOf('fullName')).toBeGreaterThan(-1); expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].subgroup_count); }); }); diff --git a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js index a284b981d2a..9d05c6859df 100644 --- a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js +++ b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js @@ -32,7 +32,7 @@ describe('commentIndicatorHelper', () => { expect(svgEl).toBeDefined(); const svgLink = svgEl.querySelector('use').getAttribute('xlink:href'); - expect(svgLink.indexOf('image-comment-dark') !== -1).toEqual(true); + expect(svgLink.indexOf('image-comment-dark')).not.toBe(-1); }); }); }); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index e02eb9723fe..6e0bcf801cd 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -41,7 +41,7 @@ describe('Job App ', () => { }; const props = { - runnerHelpUrl: 'help/runners', + runnerSettingsUrl: 'settings/ci-cd/runners', }; beforeEach(() => { @@ -223,7 +223,6 @@ describe('Job App ', () => { store.dispatch( 'receiveJobSuccess', Object.assign({}, job, { - erased: true, erased_by: { username: 'root', web_url: 'gitlab.com/root', @@ -237,18 +236,18 @@ describe('Job App ', () => { store, }); - expect(vm.$el.querySelector('.js-job-erased')).not.toBeNull(); + expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull(); }); it('does not render erased block when `erased` is false', () => { - store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased: false })); + store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased_at: null })); vm = mountComponentWithStore(Component, { props, store, }); - expect(vm.$el.querySelector('.js-job-erased')).toBeNull(); + expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull(); }); }); diff --git a/spec/javascripts/jobs/components/job_log_controllers_spec.js b/spec/javascripts/jobs/components/job_log_controllers_spec.js index 099aca602c4..3dcb57d23ce 100644 --- a/spec/javascripts/jobs/components/job_log_controllers_spec.js +++ b/spec/javascripts/jobs/components/job_log_controllers_spec.js @@ -25,6 +25,7 @@ describe('Job log controllers', () => { beforeEach(() => { vm = mountComponent(Component, props); }); + it('renders size information', () => { expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB'); }); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js index 2f5c4245ced..a113377b19f 100644 --- a/spec/javascripts/jobs/components/sidebar_spec.js +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -161,9 +161,9 @@ describe('Sidebar details block', () => { vm = mountComponentWithStore(SidebarComponent, { store }); }); - it('renders first stage as selected', () => { + it('renders value provided as selectedStage as selected', () => { expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual( - stages[0].name, + vm.selectedStage, ); }); }); diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index aa6cc0f1b1a..fcff78b943e 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import component from '~/jobs/components/stages_dropdown.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('Artifacts block', () => { +describe('Stages Dropdown', () => { const Component = Vue.extend(component); let vm; @@ -23,10 +23,6 @@ describe('Artifacts block', () => { }, path: 'pipeline/28029444', }, - ref: { - path: 'commits/50101-truncated-job-information', - name: '50101-truncated-job-information', - }, stages: [ { name: 'build', @@ -35,6 +31,7 @@ describe('Artifacts block', () => { name: 'test', }, ], + selectedStage: 'deploy' }); }); @@ -53,17 +50,10 @@ describe('Artifacts block', () => { }); it('renders dropdown with stages', () => { - expect(vm.$el.querySelector('.dropdown button').textContent).toContain('build'); + expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build'); }); - it('updates selected stage on click', done => { - vm.$el.querySelectorAll('.stage-item')[1].click(); - vm - .$nextTick() - .then(() => { - expect(vm.$el.querySelector('.dropdown button').textContent).toContain('test'); - }) - .then(done) - .catch(done.fail); + it('rendes selected stage', () => { + expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); }); }); diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js index 5ab1f75d0d6..bc410ae614c 100644 --- a/spec/javascripts/jobs/store/actions_spec.js +++ b/spec/javascripts/jobs/store/actions_spec.js @@ -422,8 +422,9 @@ describe('Job State actions', () => { beforeEach(() => { mockedState.job.pipeline = { - path: `${TEST_HOST}/endpoint.json/stages`, + path: `${TEST_HOST}/endpoint`, }; + mockedState.selectedStage = 'deploy' mock = new MockAdapter(axios); }); @@ -434,8 +435,8 @@ describe('Job State actions', () => { describe('success', () => { it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => { mock - .onGet(`${TEST_HOST}/endpoint.json/stages`) - .replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } }); + .onGet(`${TEST_HOST}/endpoint.json`) + .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } }); testAction( fetchStages, @@ -447,11 +448,11 @@ describe('Job State actions', () => { type: 'requestStages', }, { - payload: [{ id: 121212, name: 'build' }], + payload: [{ name: 'build' }, { name: 'deploy' }], type: 'receiveStagesSuccess', }, { - payload: { id: 121212, name: 'build' }, + payload: { name: 'deploy' }, type: 'fetchJobsForStage', }, ], @@ -515,9 +516,9 @@ describe('Job State actions', () => { it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => { testAction( requestJobsForStage, - null, + { name: 'deploy' }, mockedState, - [{ type: types.REQUEST_JOBS_FOR_STAGE }], + [{ type: types.REQUEST_JOBS_FOR_STAGE, payload: { name: 'deploy' } }], [], done, ); @@ -549,6 +550,7 @@ describe('Job State actions', () => { [ { type: 'requestJobsForStage', + payload: { dropdown_path: `${TEST_HOST}/jobs.json` }, }, { payload: [{ id: 121212, name: 'build' }], @@ -574,6 +576,7 @@ describe('Job State actions', () => { [ { type: 'requestJobsForStage', + payload: { dropdown_path: `${TEST_HOST}/jobs.json` }, }, { type: 'receiveJobsForStageError', diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js index 160b2f4b34a..e262a47b837 100644 --- a/spec/javascripts/jobs/store/getters_spec.js +++ b/spec/javascripts/jobs/store/getters_spec.js @@ -77,18 +77,18 @@ describe('Job Store Getters', () => { }); }); - describe('jobHasStarted', () => { - describe('when started equals false', () => { + describe('shouldRenderTriggeredLabel', () => { + describe('when started equals null', () => { it('returns false', () => { - localState.job.started = false; - expect(getters.jobHasStarted(localState)).toEqual(false); + localState.job.started = null; + expect(getters.shouldRenderTriggeredLabel(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); + expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true); }); }); }); diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js index 9ba543d32a8..701fcc7f4c8 100644 --- a/spec/javascripts/jobs/store/mutations_spec.js +++ b/spec/javascripts/jobs/store/mutations_spec.js @@ -108,21 +108,33 @@ describe('Jobs Store Mutations', () => { }); describe('RECEIVE_JOB_SUCCESS', () => { - beforeEach(() => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - }); - it('sets is loading to false', () => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); expect(stateCopy.isLoading).toEqual(false); }); it('sets hasError to false', () => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); expect(stateCopy.hasError).toEqual(false); }); it('sets job data', () => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); expect(stateCopy.job).toEqual({ id: 1312321 }); }); + + it('sets selectedStage when the selectedStage is More', () => { + expect(stateCopy.selectedStage).toEqual('More'); + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); + expect(stateCopy.selectedStage).toEqual('deploy'); + }); + + it('does not set selectedStage when the selectedStage is not More', () => { + stateCopy.selectedStage = 'notify' + expect(stateCopy.selectedStage).toEqual('notify'); + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); + expect(stateCopy.selectedStage).toEqual('notify'); + }); }); describe('RECEIVE_JOB_ERROR', () => { @@ -200,9 +212,14 @@ describe('Jobs Store Mutations', () => { describe('REQUEST_JOBS_FOR_STAGE', () => { it('sets isLoadingStages to true', () => { - mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy); + mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); expect(stateCopy.isLoadingJobs).toEqual(true); }); + + it('sets selectedStage', () => { + mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); + expect(stateCopy.selectedStage).toEqual('deploy'); + }) }); describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => { diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 28151c7e658..c34622203f7 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -9,6 +9,7 @@ describe('common_utils', () => { it('returns an anchor tag with url', () => { expect(commonUtils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url'); }); + it('url is escaped', () => { // IE11 will return a relative pathname while other browsers will return a full pathname. // parseUrl uses an anchor element for parsing an url. With relative urls, the anchor @@ -42,7 +43,7 @@ describe('common_utils', () => { it('should remove the question mark from the search params', () => { const paramsArray = commonUtils.urlParamsToArray('?test=thing'); - expect(paramsArray[0][0] !== '?').toBe(true); + expect(paramsArray[0][0]).not.toBe('?'); }); }); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index c32ecb17e89..15bb78032b2 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -23,17 +23,20 @@ import LineHighlighter from '~/line_highlighter'; __setLocationHash__: spyOn(this["class"], '__setLocationHash__').and.callFake(function() {}) }; }); + describe('behavior', function() { it('highlights one line given in the URL hash', function() { new LineHighlighter({ hash: '#L13' }); return expect($('#LC13')).toHaveClass(this.css); }); + it('highlights one line given in the URL hash with given CSS class name', function() { const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' }); expect(hiliter.highlightLineClass).toBe('hilite'); expect($('#LC13')).toHaveClass('hilite'); expect($('#LC13')).not.toHaveClass('hll'); }); + it('highlights a range of lines given in the URL hash', function() { var line, results; new LineHighlighter({ hash: '#L5-25' }); @@ -44,18 +47,21 @@ import LineHighlighter from '~/line_highlighter'; } return results; }); + it('scrolls to the first highlighted line on initial load', function() { var spy; spy = spyOn($, 'scrollTo'); new LineHighlighter({ hash: '#L5-25' }); return expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything()); }); + it('discards click events', function() { var spy; spy = spyOnEvent('a[data-line-number]', 'click'); clickLine(13); return expect(spy).toHaveBeenPrevented(); }); + it('handles garbage input from the hash', function() { var func; func = function() { @@ -64,6 +70,7 @@ import LineHighlighter from '~/line_highlighter'; return expect(func).not.toThrow(); }); }); + describe('clickHandler', function() { it('handles clicking on a child icon element', function() { var spy; @@ -72,11 +79,13 @@ import LineHighlighter from '~/line_highlighter'; expect(spy).toHaveBeenCalledWith(13); return expect($('#LC13')).toHaveClass(this.css); }); + describe('without shiftKey', function() { it('highlights one line when clicked', function() { clickLine(13); return expect($('#LC13')).toHaveClass(this.css); }); + it('unhighlights previously highlighted lines', function() { clickLine(13); clickLine(20); @@ -101,6 +110,7 @@ import LineHighlighter from '~/line_highlighter'; expect(spy).toHaveBeenCalledWith(13); return expect(spy).toHaveBeenCalledWith(13, 20); }); + describe('without existing highlight', function() { it('highlights the clicked line', function() { clickLine(13, { @@ -118,6 +128,7 @@ import LineHighlighter from '~/line_highlighter'; return expect(spy).toHaveBeenCalledWith(13); }); }); + describe('with existing single-line highlight', function() { it('uses existing line as last line when target is lesser', function() { var line, results; @@ -155,6 +166,7 @@ import LineHighlighter from '~/line_highlighter'; shiftKey: true }); }); + it('uses target as first line when it is less than existing first line', function() { var line, results; clickLine(5, { @@ -182,13 +194,16 @@ import LineHighlighter from '~/line_highlighter'; }); }); }); + describe('hashToRange', function() { beforeEach(function() { return this.subject = this["class"].hashToRange; }); + it('extracts a single line number from the hash', function() { return expect(this.subject('#L5')).toEqual([5, null]); }); + it('extracts a range of line numbers from the hash', function() { return expect(this.subject('#L5-15')).toEqual([5, 15]); }); @@ -196,10 +211,12 @@ import LineHighlighter from '~/line_highlighter'; return expect(this.subject('#foo')).toEqual([null, null]); }); }); + describe('highlightLine', function() { beforeEach(function() { return this.subject = this["class"].highlightLine; }); + it('highlights the specified line', function() { this.subject(13); return expect($('#LC13')).toHaveClass(this.css); @@ -213,6 +230,7 @@ import LineHighlighter from '~/line_highlighter'; beforeEach(function() { return this.subject = this["class"].setHash; }); + it('sets the location hash for a single line', function() { this.subject(5); return expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5'); diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index e52ac686435..85419e640d8 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -21,18 +21,22 @@ import NewBranchForm from '~/new_branch_form'; }); return this.form = new NewBranchForm($('.js-create-branch-form'), []); }); + it("can't start with a dot", function() { fillNameWith('.foo'); return expectToHaveError("can't start with '.'"); }); + it("can't start with a slash", function() { fillNameWith('/foo'); return expectToHaveError("can't start with '/'"); }); + it("can't have two consecutive dots", function() { fillNameWith('foo..bar'); return expectToHaveError("can't contain '..'"); }); + it("can't have spaces anywhere", function() { fillNameWith(' foo'); expectToHaveError("can't contain spaces"); @@ -41,6 +45,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo '); return expectToHaveError("can't contain spaces"); }); + it("can't have ~ anywhere", function() { fillNameWith('~foo'); expectToHaveError("can't contain '~'"); @@ -49,6 +54,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo~'); return expectToHaveError("can't contain '~'"); }); + it("can't have tilde anwhere", function() { fillNameWith('~foo'); expectToHaveError("can't contain '~'"); @@ -57,6 +63,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo~'); return expectToHaveError("can't contain '~'"); }); + it("can't have caret anywhere", function() { fillNameWith('^foo'); expectToHaveError("can't contain '^'"); @@ -65,6 +72,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo^'); return expectToHaveError("can't contain '^'"); }); + it("can't have : anywhere", function() { fillNameWith(':foo'); expectToHaveError("can't contain ':'"); @@ -73,6 +81,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith(':foo'); return expectToHaveError("can't contain ':'"); }); + it("can't have question mark anywhere", function() { fillNameWith('?foo'); expectToHaveError("can't contain '?'"); @@ -81,6 +90,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo?'); return expectToHaveError("can't contain '?'"); }); + it("can't have asterisk anywhere", function() { fillNameWith('*foo'); expectToHaveError("can't contain '*'"); @@ -89,6 +99,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo*'); return expectToHaveError("can't contain '*'"); }); + it("can't have open bracket anywhere", function() { fillNameWith('[foo'); expectToHaveError("can't contain '['"); @@ -97,6 +108,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo['); return expectToHaveError("can't contain '['"); }); + it("can't have a backslash anywhere", function() { fillNameWith('\\foo'); expectToHaveError("can't contain '\\'"); @@ -105,6 +117,7 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo\\'); return expectToHaveError("can't contain '\\'"); }); + it("can't contain a sequence @{ anywhere", function() { fillNameWith('@{foo'); expectToHaveError("can't contain '@{'"); @@ -113,48 +126,59 @@ import NewBranchForm from '~/new_branch_form'; fillNameWith('foo@{'); return expectToHaveError("can't contain '@{'"); }); + it("can't have consecutive slashes", function() { fillNameWith('foo//bar'); return expectToHaveError("can't contain consecutive slashes"); }); + it("can't end with a slash", function() { fillNameWith('foo/'); return expectToHaveError("can't end in '/'"); }); + it("can't end with a dot", function() { fillNameWith('foo.'); return expectToHaveError("can't end in '.'"); }); + it("can't end with .lock", function() { fillNameWith('foo.lock'); return expectToHaveError("can't end in '.lock'"); }); + it("can't be the single character @", function() { fillNameWith('@'); return expectToHaveError("can't be '@'"); }); + it("concatenates all error messages", function() { fillNameWith('/foo bar?~.'); return expectToHaveError("can't start with '/', can't contain spaces, '?', '~', can't end in '.'"); }); + it("doesn't duplicate error messages", function() { fillNameWith('?foo?bar?zoo?'); return expectToHaveError("can't contain '?'"); }); + it("removes the error message when is a valid name", function() { fillNameWith('foo?bar'); expect($('.js-branch-name-error span').length).toEqual(1); fillNameWith('foobar'); return expect($('.js-branch-name-error span').length).toEqual(0); }); + it("can have dashes anywhere", function() { fillNameWith('-foo-bar-zoo-'); return expect($('.js-branch-name-error span').length).toEqual(0); }); + it("can have underscores anywhere", function() { fillNameWith('_foo_bar_zoo_'); return expect($('.js-branch-name-error span').length).toEqual(0); }); + it("can have numbers anywhere", function() { fillNameWith('1foo2bar3zoo4'); return expect($('.js-branch-name-error span').length).toEqual(0); diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index 147ffcf1b81..eefd9ddd63c 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -100,6 +100,7 @@ describe('issue_note_form component', () => { expect(vm.handleUpdate).toHaveBeenCalled(); }); + it('should save note when ctrl+enter is pressed', () => { spyOn(vm, 'handleUpdate').and.callThrough(); vm.$el.querySelector('textarea').value = 'Foo'; diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index faeedae40e9..7bfbca83c77 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -538,7 +538,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; mockNotesPost(); $('.js-comment-button').click(); - expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true); + expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0); }); it('should remove placeholder note when new comment is done posting', done => { @@ -582,7 +582,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; $('.js-comment-button').click(); setTimeout(() => { - expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true); + expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0); done(); }); @@ -776,7 +776,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; $form.find('textarea.js-note-text').val(sampleComment); const { formData, formContent, formAction } = this.notes.getFormData($form); - expect(formData.indexOf(sampleComment) > -1).toBe(true); + expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1); expect(formContent).toEqual(sampleComment); expect(formAction).toEqual($form.attr('action')); }); diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js index 6f6eb161d14..bf11dbea386 100644 --- a/spec/javascripts/reports/components/report_section_spec.js +++ b/spec/javascripts/reports/components/report_section_spec.js @@ -86,6 +86,7 @@ describe('Report section', () => { }); }); }); + describe('when it is loading', () => { it('should render loading indicator', () => { vm = mountComponent(ReportSection, { diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index f9395eedfea..936e8f16c52 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -60,10 +60,12 @@ import Sidebar from '~/right_sidebar'; $toggle.click(); assertSidebarState('expanded'); }); + it('should float over the page and when sidebar icons clicked', function() { $labelsIcon.click(); return assertSidebarState('expanded'); }); + it('should collapse when the icon arrow clicked while it is floating on page', function() { $labelsIcon.click(); assertSidebarState('expanded'); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index b96023a33c4..bc1bb50dc5c 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -140,6 +140,7 @@ describe('Search autocomplete dropdown', () => { removeBodyAttributes(); window.gon = {}; }); + it('should show Dashboard specific dropdown menu', function() { var list; addBodyAttributes(); @@ -148,6 +149,7 @@ describe('Search autocomplete dropdown', () => { list = widget.wrap.find('.dropdown-menu').find('ul'); return assertLinks(list, dashboardIssuesPath, dashboardMRsPath); }); + it('should show Group specific dropdown menu', function() { var list; addBodyAttributes('group'); @@ -156,6 +158,7 @@ describe('Search autocomplete dropdown', () => { list = widget.wrap.find('.dropdown-menu').find('ul'); return assertLinks(list, groupIssuesPath, groupMRsPath); }); + it('should show Project specific dropdown menu', function() { var list; addBodyAttributes('project'); @@ -164,6 +167,7 @@ describe('Search autocomplete dropdown', () => { list = widget.wrap.find('.dropdown-menu').find('ul'); return assertLinks(list, projectIssuesPath, projectMRsPath); }); + it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() { addBodyAttributes('project'); disableProjectIssues(); @@ -172,6 +176,7 @@ describe('Search autocomplete dropdown', () => { const list = widget.wrap.find('.dropdown-menu').find('ul'); assertLinks(list, null, projectMRsPath); }); + it('should not show category related menu if there is text in the input', function() { var link, list; addBodyAttributes('project'); @@ -182,6 +187,7 @@ describe('Search autocomplete dropdown', () => { link = "a[href='" + projectIssuesPath + '/?assignee_id=' + userId + "']"; return expect(list.find(link).length).toBe(0); }); + it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { var ENTER = 13; var DOWN = 40; diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js index af3a5d58ba7..512be88c24c 100644 --- a/spec/javascripts/syntax_highlight_spec.js +++ b/spec/javascripts/syntax_highlight_spec.js @@ -25,6 +25,7 @@ describe('Syntax Highlighter', function() { beforeEach(function() { return setFixtures("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>"); }); + it('applies highlighting to all applicable children', function() { stubUserColorScheme('monokai'); syntaxHighlight($('.parent')); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js index 91e81a0675a..305cee94f57 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -137,7 +137,7 @@ describe('MemoryUsage', () => { } = vm; expect(hasMetrics).toBeTruthy(); - expect(memoryMetrics.length > 0).toBeTruthy(); + expect(memoryMetrics.length).toBeGreaterThan(0); expect(deploymentTime).toEqual(deployment_time); expect(memoryFrom).toEqual('9.13'); expect(memoryTo).toEqual('4.28'); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index efa5c878678..033cb694249 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -157,6 +157,16 @@ describe('MRWidgetMerged', () => { expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe(vm.mr.mergeCommitSha); }); + it('hides button to copy commit SHA if SHA does not exist', (done) => { + vm.mr.mergeCommitSha = null; + + Vue.nextTick(() => { + expect(selectors.copyMergeShaButton).not.toExist(); + expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with'); + done(); + }); + }); + it('shows merge commit SHA link', () => { expect(selectors.mergeCommitShaLink).toExist(); expect(selectors.mergeCommitShaLink.text).toContain(vm.mr.shortMergeCommitSha); diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js index 65d8ed39ade..0982b3e1f38 100644 --- a/spec/javascripts/vue_shared/components/memory_graph_spec.js +++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js @@ -52,8 +52,8 @@ describe('MemoryGraph', () => { it('should show human readable median value based on provided median timestamp', () => { vm.deploymentTime = mockMedian; const formattedMedian = vm.getFormattedMedian; - expect(formattedMedian.indexOf('Deployed') > -1).toBeTruthy(); - expect(formattedMedian.indexOf('ago') > -1).toBeTruthy(); + expect(formattedMedian.indexOf('Deployed')).toBeGreaterThan(-1); + expect(formattedMedian.indexOf('ago')).toBeGreaterThan(-1); }); }); }); diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index 694d4ce160a..d97fdc01109 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do end context 'when pipeline has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |simple_status| + (HasStatus::AVAILABLE_STATUSES - HasStatus::BLOCKED_STATUS).each do |simple_status| context "when core status is #{simple_status}" do let(:pipeline) { create(:ci_pipeline, status: simple_status) } @@ -23,24 +23,12 @@ describe Gitlab::Ci::Status::Pipeline::Factory do expect(factory.core_status).to be_a expected_status end - if simple_status == 'manual' - it 'matches a correct extended statuses' do - expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Pipeline::Blocked] - end - elsif simple_status == 'scheduled' - it 'matches a correct extended statuses' do - expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Pipeline::Scheduled] - end - else - it 'does not match extended statuses' do - expect(factory.extended_statuses).to be_empty - end - - it "fabricates a core status #{simple_status}" do - expect(status).to be_a expected_status - end + it 'does not match extended statuses' do + expect(factory.extended_statuses).to be_empty + end + + it "fabricates a core status #{simple_status}" do + expect(status).to be_a expected_status end it 'extends core status with common pipeline methods' do @@ -51,6 +39,48 @@ describe Gitlab::Ci::Status::Pipeline::Factory do end end end + + context "when core status is manual" do + let(:pipeline) { create(:ci_pipeline, status: :manual) } + + it "matches manual core status" do + expect(factory.core_status) + .to be_a Gitlab::Ci::Status::Manual + end + + it 'matches a correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Pipeline::Blocked] + end + + it 'extends core status with common pipeline methods' do + expect(status).to have_details + expect(status).not_to have_action + expect(status.details_path) + .to include "pipelines/#{pipeline.id}" + end + end + + context "when core status is scheduled" do + let(:pipeline) { create(:ci_pipeline, status: :scheduled) } + + it "matches scheduled core status" do + expect(factory.core_status) + .to be_a Gitlab::Ci::Status::Scheduled + end + + it 'matches a correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Pipeline::Scheduled] + end + + it 'extends core status with common pipeline methods' do + expect(status).to have_details + expect(status).not_to have_action + expect(status.details_path) + .to include "pipelines/#{pipeline.id}" + end + end end context 'when pipeline has warnings' do diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb new file mode 100644 index 00000000000..c6f09ca2112 --- /dev/null +++ b/spec/lib/gitlab/gon_helper_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::GonHelper do + let(:helper) do + Class.new do + include Gitlab::GonHelper + end.new + end + + describe '#push_frontend_feature_flag' do + it 'pushes a feature flag to the frontend' do + gon = instance_double('gon') + + allow(helper) + .to receive(:gon) + .and_return(gon) + + expect(Feature) + .to receive(:enabled?) + .with(:my_feature_flag, 10) + .and_return(true) + + expect(gon) + .to receive(:push) + .with({ features: { 'myFeatureFlag' => true } }, true) + + helper.push_frontend_feature_flag(:my_feature_flag, 10) + end + end +end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index b335e0fbeb3..182070781dd 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -39,6 +39,29 @@ describe Deployment do end end + describe 'scopes' do + describe 'last_for_environment' do + let(:production) { create(:environment) } + let(:staging) { create(:environment) } + let(:testing) { create(:environment) } + + let!(:deployments) do + [ + create(:deployment, environment: production), + create(:deployment, environment: staging), + create(:deployment, environment: production) + ] + end + + it 'retrieves last deployments for environments' do + last_deployments = described_class.last_for_environment([staging, production, testing]) + + expect(last_deployments.size).to eq(2) + expect(last_deployments).to eq(deployments.last(2)) + end + end + end + describe '#includes_commit?' do let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index beff499f2be..1d31d26f418 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -65,10 +65,12 @@ describe Projects::DestroyService do context 'Sidekiq inline' do before do - # Run sidekiq immediatly to check that renamed repository will be removed + # Run sidekiq immediately to check that renamed repository will be removed perform_enqueued_jobs { destroy_project(project, user, {}) } end + it_behaves_like 'deleting the project' + context 'when has remote mirrors' do let!(:project) do create(:project, :repository, namespace: user.namespace).tap do |project| @@ -82,13 +84,28 @@ describe Projects::DestroyService do end end - it_behaves_like 'deleting the project' - it 'invalidates personal_project_count cache' do expect(user).to receive(:invalidate_personal_projects_count) destroy_project(project, user) end + + context 'when project has exports' do + let!(:project_with_export) do + create(:project, :repository, namespace: user.namespace).tap do |project| + create(:import_export_upload, + project: project, + export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz')) + end + end + let!(:async) { true } + + it 'destroys project and export' do + expect { destroy_project(project_with_export, user) }.to change(ImportExportUpload, :count).by(-1) + + expect(Project.all).not_to include(project_with_export) + end + end end context 'Sidekiq fake' do diff --git a/yarn.lock b/yarn.lock index 25ea8d7557c..5879ccb9267 100644 --- a/yarn.lock +++ b/yarn.lock @@ -616,10 +616,10 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.29.0": - version "1.31.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.31.0.tgz#495b074669f93af40e34f9978ce887773dea470a" - integrity sha512-tJbf99XX/ddFkXCXxQr9a0GJD9rPVoW3qMbU14dkxwG4WBmPEoVg+e7sLvm9OWTD1uUqiVW3qWKp++SGhhcRlw== +"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.32.0": + version "1.32.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.32.0.tgz#a65ab7724fa7d55be8e5cc9b2dbe3f0757432fd3" + integrity sha512-L3o8dFUd2nSkVZBwh2hCJWzNzADJ3dTBZxamND8NLosZK9/ohNhccmsQOZGyMCUHaOzm4vifaaXkAXh04UtMKA== "@gitlab-org/gitlab-ui@^1.8.0": version "1.8.0" |