diff options
37 files changed, 271 insertions, 94 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e6fcab3808..d84725b202a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,6 +67,7 @@ stages: - export CI_NODE_TOTAL=${JOB_NAME[2]} - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true + - export CACHE_CLASSES=true - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - knapsack rspec "--color --format documentation" artifacts: @@ -87,6 +88,7 @@ stages: - export CI_NODE_TOTAL=${JOB_NAME[2]} - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true + - export CACHE_CLASSES=true - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index d8a73b0f5e7..f93208944a1 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -8,6 +8,7 @@ import { glEmojiTag } from './behaviors/gl_emoji'; import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; +const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || @@ -105,8 +106,9 @@ function AwardsHandler() { const $glEmojiElement = $target.find('gl-emoji'); const $spriteIconElement = $target.find('.icon'); const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); + $target.closest('.js-awards-block').addClass('current'); - return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); + this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); }); } @@ -132,12 +134,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { if ($menu.is('.is-visible')) { $addBtn.removeClass('is-active'); $menu.removeClass('is-visible'); - $('#emoji_search').blur(); + $('.js-emoji-menu-search').blur(); } else { $addBtn.addClass('is-active'); this.positionMenu($menu, $addBtn); $menu.addClass('is-visible'); - $('#emoji_search').focus(); + $('.js-emoji-menu-search').focus(); } } else { $addBtn.addClass('is-loading is-active'); @@ -147,7 +149,7 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { this.positionMenu($createdMenu, $addBtn); return setTimeout(() => { $createdMenu.addClass('is-visible'); - $('#emoji_search').focus(); + $('.js-emoji-menu-search').focus(); }, 200); }); } @@ -180,7 +182,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) { const emojiMenuMarkup = ` <div class="emoji-menu"> - <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" /> + <input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" /> <div class="emoji-menu-content"> ${frequentlyUsedCatgegory} @@ -500,24 +502,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj }; AwardsHandler.prototype.setupSearch = function setupSearch() { - this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => { + const $search = $('.js-emoji-menu-search'); + + this.registerEventListener('on', $search, 'input', (e) => { const term = $(e.target).val().trim(); - // Clean previous search results - $('ul.emoji-menu-search, h5.emoji-search-title').remove(); - if (term.length > 0) { - // Generate a search result block - const h5 = $('<h5 class="emoji-search-title"/>').text('Search results'); - const foundEmojis = this.searchEmojis(term).show(); - const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); - $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); - $('.emoji-menu-content').append(h5).append(ul); - } else { - $('.emoji-menu-content').children().show(); + this.searchEmojis(term); + }); + + const $menu = $('.emoji-menu'); + this.registerEventListener('on', $menu, transitionEndEventString, (e) => { + if (e.target === e.currentTarget) { + // Clear the search + this.searchEmojis(''); } }); }; AwardsHandler.prototype.searchEmojis = function searchEmojis(term) { + const $search = $('.js-emoji-menu-search'); + $search.val(term); + + // Clean previous search results + $('ul.emoji-menu-search, h5.emoji-search-title').remove(); + if (term.length > 0) { + // Generate a search result block + const h5 = $('<h5 class="emoji-search-title"/>').text('Search results'); + const foundEmojis = this.findMatchingEmojiElements(term).show(); + const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); + $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); + $('.emoji-menu-content').append(h5).append(ul); + } else { + $('.emoji-menu-content').children().show(); + } +}; + +AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) { const safeTerm = term.toLowerCase(); const namesMatchingAlias = []; diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 4c9ad128e6c..77e92ff8caf 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -22,6 +22,7 @@ $(() => { } $('body').on('click', '.js-toggle-button', function toggleButton(e) { + e.target.classList.toggle('open'); toggleContainer($(this).closest('.js-toggle-container')); const targetTag = e.currentTarget.tagName.toLowerCase(); diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index 312f38ce241..dcfc40c1013 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -20,8 +20,7 @@ import Vue from 'vue'; data: function () { return { discussions: CommentsStore.state, - loading: false, - note: {}, + loading: false }; }, watch: { @@ -34,6 +33,9 @@ import Vue from 'vue'; discussion: function () { return this.discussions[this.discussionId]; }, + note: function () { + return this.discussion ? this.discussion.getNote(this.noteId) : {}; + }, buttonText: function () { if (this.isResolved) { return `Resolved by ${this.resolvedByName}`; @@ -112,8 +114,6 @@ import Vue from 'vue'; authorAvatar: this.authorAvatar, noteTruncated: this.noteTruncated, }); - - this.note = this.discussion.getNote(this.noteId); } }); diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index b6ce8e83729..4d491e70d83 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -1,26 +1,20 @@ import Vue from 'vue'; -import IssueTitle from './issue_title'; +import IssueTitle from './issue_title.vue'; import '../vue_shared/vue_resource_interceptor'; -const vueOptions = () => ({ - el: '.issue-title-entrypoint', - components: { - IssueTitle, - }, - data() { - const issueTitleData = document.querySelector('.issue-title-data').dataset; +(() => { + const issueTitleData = document.querySelector('.issue-title-data').dataset; + const { initialTitle, endpoint } = issueTitleData; - return { - initialTitle: issueTitleData.initialTitle, - endpoint: issueTitleData.endpoint, - }; - }, - template: ` - <IssueTitle - :initialTitle="initialTitle" - :endpoint="endpoint" - /> - `, -}); + const vm = new Vue({ + el: '.issue-title-entrypoint', + render: createElement => createElement(IssueTitle, { + props: { + initialTitle, + endpoint, + }, + }), + }); -(() => new Vue(vueOptions()))(); + return vm; +})(); diff --git a/app/assets/javascripts/issue_show/issue_title.js b/app/assets/javascripts/issue_show/issue_title.vue index 1184c8956dc..ba54178a310 100644 --- a/app/assets/javascripts/issue_show/issue_title.js +++ b/app/assets/javascripts/issue_show/issue_title.vue @@ -1,3 +1,4 @@ +<script> import Visibility from 'visibilityjs'; import Poll from './../lib/utils/poll'; import Service from './services/index'; @@ -72,7 +73,9 @@ export default { created() { this.fetch(); }, - template: ` - <h2 class='title' v-html='title'></h2> - `, }; +</script> + +<template> + <h2 class="title" v-html="title"></h2> +</template> diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 0dad91ba128..9e3142c8aa3 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -135,7 +135,7 @@ .text-expander { display: inline-block; - background: $gray-light; + background: $white-light; color: $gl-text-color-secondary; padding: 0 5px; cursor: pointer; @@ -146,6 +146,11 @@ line-height: $gl-font-size; outline: none; + &.open { + background: $gray-light; + box-shadow: inset 0 0 2px rgba($black, 0.2); + } + &:hover { background-color: darken($gray-light, 10%); text-decoration: none; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1aa1079903c..1b4694377b3 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -106,6 +106,10 @@ span { white-space: pre-wrap; } + + .line { + word-wrap: break-word; + } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 220b6d6f141..2ea2ff8362b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -123,6 +123,9 @@ ul.notes { } .note-emoji-button { + position: relative; + line-height: 1; + .fa-spinner { display: none; } @@ -443,7 +446,8 @@ ul.notes { .award-control-icon-positive, .award-control-icon-super-positive { position: absolute; - margin-left: -20px; + top: 0; + left: 0; opacity: 0; } diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 6b9e4267281..43669b6f356 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -24,7 +24,7 @@ module ProjectsHelper return "(deleted)" unless author - author_html = "" + author_html = "" # Build avatar image tag author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar] @@ -45,7 +45,7 @@ module ProjectsHelper link_to(author_html, user_path(author), class: "author_link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe else title = opts[:title].sub(":name", sanitize(author.name)) - link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe + link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' }).html_safe end end @@ -430,13 +430,22 @@ module ProjectsHelper end def visibility_select_options(project, selected_level) - levels_options_array = Gitlab::VisibilityLevel.values.map do |level| - [ + level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options| + next if restricted_levels.include?(level) + + level_options << [ visibility_level_label(level), { data: { description: visibility_level_description(level, project) } }, level ] end - options_for_select(levels_options_array, selected_level) + + options_for_select(level_options, selected_level) + end + + def restricted_levels + return [] if current_user.admin? + + current_application_settings.restricted_visibility_levels || [] end end diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 2340453831e..0d7c2d20029 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -16,7 +16,7 @@ class AbuseReport < ActiveRecord::Base def remove_user(deleted_by:) user.block - DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true) + DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true) end def notify diff --git a/app/models/repository.rb b/app/models/repository.rb index 2b11ed6128e..7bb874d7744 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -19,7 +19,7 @@ class Repository # # For example, for entry `:readme` there's a method called `readme` which # stores its data in the `readme` cache key. - CACHED_METHODS = %i(size commit_count readme version contribution_guide + CACHED_METHODS = %i(size commit_count readme contribution_guide changelog license_blob license_key gitignore koding_yml gitlab_ci_yml branch_names tag_names branch_count tag_count avatar exists? empty? root_ref).freeze @@ -32,7 +32,6 @@ class Repository changelog: :changelog, license: %i(license_blob license_key), contributing: :contribution_guide, - version: :version, gitignore: :gitignore, koding: :koding_yml, gitlab_ci: :gitlab_ci_yml, @@ -109,7 +108,7 @@ class Repository offset: offset, after: after, before: before, - follow: path.present?, + follow: Array(path).length == 1, skip_merges: skip_merges } @@ -530,11 +529,6 @@ class Repository end cache_method :readme - def version - file_on_head(:version) - end - cache_method :version - def contribution_guide file_on_head(:contributing) end diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index ba58b174cc0..9eb6a600f6b 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -26,7 +26,7 @@ module Users ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end - MigrateToGhostUserService.new(user).execute + MigrateToGhostUserService.new(user).execute unless options[:hard_delete] # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing namespace = user.namespace diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 885795ccb5c..fcbd8829595 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -79,4 +79,5 @@ = render 'shared/issuable/sidebar', issuable: @issue += page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('issue_show') diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index e010f21de5a..fc4385865a4 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -3,6 +3,8 @@ = confidential_icon(issue) = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do %span.term.str-truncated= issue.title + - if issue.closed? + %span.label.label-danger.prepend-left-5 Closed .pull-right ##{issue.iid} - if issue.description.present? .description.term @@ -10,6 +12,3 @@ = search_md_sanitize(issue, :description) %span.light #{issue.project.name_with_namespace} - - if issue.closed? - .pull-right - %span.label.label-danger Closed diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 2e6adf3027c..9b583285d02 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -2,6 +2,10 @@ %h4 = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do %span.term.str-truncated= merge_request.title + - if merge_request.merged? + %span.label.label-primary.prepend-left-5 Merged + - elsif merge_request.closed? + %span.label.label-danger.prepend-left-5 Closed .pull-right= merge_request.to_reference - if merge_request.description.present? .description.term @@ -9,8 +13,3 @@ = search_md_sanitize(merge_request, :description) %span.light #{merge_request.project.name_with_namespace} - .pull-right - - if merge_request.merged? - %span.label.label-primary Merged - - elsif merge_request.closed? - %span.label.label-danger Closed diff --git a/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml b/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml new file mode 100644 index 00000000000..5fd02696323 --- /dev/null +++ b/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml @@ -0,0 +1,4 @@ +--- +title: Clear emoji search in awards menu after picking emoji +merge_request: +author: diff --git a/changelogs/unreleased/28575-expand-collapse-look.yml b/changelogs/unreleased/28575-expand-collapse-look.yml new file mode 100644 index 00000000000..d8943316300 --- /dev/null +++ b/changelogs/unreleased/28575-expand-collapse-look.yml @@ -0,0 +1,4 @@ +--- +title: Expand/collapse button -> Change to make it look like a toggle +merge_request: 10720 +author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/dz-remove-repo-version.yml b/changelogs/unreleased/dz-remove-repo-version.yml new file mode 100644 index 00000000000..f9e51a920f9 --- /dev/null +++ b/changelogs/unreleased/dz-remove-repo-version.yml @@ -0,0 +1,4 @@ +--- +title: Remove Repository#version method and tests +merge_request: 10734 +author: diff --git a/changelogs/unreleased/fix-project-visibility-setting.yml b/changelogs/unreleased/fix-project-visibility-setting.yml new file mode 100644 index 00000000000..0fc219ccf52 --- /dev/null +++ b/changelogs/unreleased/fix-project-visibility-setting.yml @@ -0,0 +1,4 @@ +--- +title: Fix restricted project visibility setting available to users +merge_request: +author: diff --git a/changelogs/unreleased/fix-trace-encoding.yml b/changelogs/unreleased/fix-trace-encoding.yml new file mode 100644 index 00000000000..152610c43f5 --- /dev/null +++ b/changelogs/unreleased/fix-trace-encoding.yml @@ -0,0 +1,4 @@ +--- +title: Fix another case where trace does not have proper encoding set +merge_request: 10728 +author: diff --git a/changelogs/unreleased/move-search-labels.yml b/changelogs/unreleased/move-search-labels.yml new file mode 100644 index 00000000000..3a1d23d622e --- /dev/null +++ b/changelogs/unreleased/move-search-labels.yml @@ -0,0 +1,4 @@ +--- +title: Move labels of search results from bottom to title +merge_request: 10705 +author: dr diff --git a/config/environments/test.rb b/config/environments/test.rb index a25c5016a3b..c3b788c038e 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -8,7 +8,12 @@ Rails.application.configure do # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! - config.cache_classes = false + + # Enabling caching of classes slows start-up time because all controllers + # are loaded at initalization, but it reduces memory and load because files + # are not reloaded with every request. For example, caching is not necessary + # for loading database migrations but useful for handling Knapsack specs. + config.cache_classes = ENV['CACHE_CLASSES'] == 'true' # Configure static asset server for tests with Cache-Control for performance config.assets.digest = false diff --git a/config/webpack.config.js b/config/webpack.config.js index ffb16190093..64a04dc342e 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -125,6 +125,7 @@ var config = { 'notebook_viewer', 'pdf_viewer', 'vue_pipelines', + 'issue_show', ], minChunks: function(module, count) { return module.resource && (/vue_shared/).test(module.resource); diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index aece4ab34ba..8ed1d98d05b 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -10,6 +10,11 @@ in your GitLab instance sitewide. This configuration is optional, users will still be able to import their GitHub repositories with a
[personal access token][gh-token].
+>**Note:**
+Administrators of a GitLab instance (Community or Enterprise Edition) can also
+use the [GitHub rake task][gh-rake] to import projects from GitHub without the
+constrains of a Sidekiq worker.
+
- At its current state, GitHub importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
@@ -112,5 +117,6 @@ You can also choose a different name for the project and a different namespace, if you have the privileges to do so.
[gh-import]: ../../integration/github.md "GitHub integration"
+[gh-rake]: ../../administration/raketasks/github_import.md "GitHub rake task"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index a4cfc1fb8c8..dfd0bc13305 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -87,7 +87,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I search "hand"' do - fill_in 'emoji_search', with: 'hand' + fill_in 'emoji-menu-search', with: 'hand' end step 'I see search result for "hand"' do @@ -101,7 +101,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'The search field is focused' do - expect(page).to have_selector('#emoji_search') - expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search') + expect(page).to have_selector('.js-emoji-menu-search') + expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) end end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 975c879149e..280d70925f7 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -66,12 +66,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps expect(page).not_to have_link('Remove avatar') end - step 'I should see project "Shop" version' do - page.within '.project-side' do - expect(page).to have_content '6.7.0.pre' - end - end - step 'change project default branch' do select 'fix', from: 'project_default_branch' click_button 'Save changes' diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index dabf71d6aeb..c2503fa2adc 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -136,7 +136,8 @@ module Banzai nodes.each_with_object({}) do |node, hash| if node.has_attribute?(attribute) - hash[node] = objects_by_id[node.attr(attribute).to_i] + obj = objects_by_id[node.attr(attribute).to_i] + hash[node] = obj if obj end end end diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 3b335cdfd01..b929bdd55bc 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -14,6 +14,14 @@ module Gitlab def initialize @stream = yield + if @stream + @stream.binmode + # Ci::Ansi2html::Converter would read from @stream directly, + # using @stream.each_line to be specific. It's safe to set + # the encoding here because IO#seek(bytes) and IO#read(bytes) + # are not characters based, so encoding doesn't matter to them. + @stream.set_encoding(Encoding.default_external) + end end def valid? @@ -51,7 +59,7 @@ module Gitlab read_last_lines(last_lines) else stream.read - end + end.force_encoding(Encoding.default_external) end def html_with_state(state = nil) @@ -113,7 +121,6 @@ module Gitlab end chunks.join.lines.last(last_lines).join - .force_encoding(Encoding.default_external) end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 40efab6e4f7..a7fc5d14859 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -265,4 +265,27 @@ describe ProjectsHelper do end end end + + describe "#visibility_select_options" do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it "does not include the Public restricted level" do + expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).not_to include('Public') + end + + it "includes the Internal level" do + expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Internal') + end + + it "includes the Private level" do + expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private') + end + end end diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index d6d50304086..68ad5f66676 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -65,7 +65,7 @@ require('~/lib/utils/common_utils'); $emojiMenu = $('.emoji-menu'); expect($emojiMenu.length).toBe(1); expect($emojiMenu.hasClass('is-visible')).toBe(true); - expect($emojiMenu.find('#emoji_search').length).toBe(1); + expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1); return expect($('.js-awards-block.current').length).toBe(1); }); }); @@ -217,16 +217,35 @@ require('~/lib/utils/common_utils'); return expect($thumbsUpEmoji.data("original-title")).toBe('sam'); }); }); - describe('search', function() { - return it('should filter the emoji', function(done) { + describe('::searchEmojis', () => { + it('should filter the emoji', function(done) { return openAndWaitForEmojiMenu() .then(() => { expect($('[data-name=angel]').is(':visible')).toBe(true); expect($('[data-name=anger]').is(':visible')).toBe(true); - $('#emoji_search').val('ali').trigger('input'); + awardsHandler.searchEmojis('ali'); expect($('[data-name=angel]').is(':visible')).toBe(false); expect($('[data-name=anger]').is(':visible')).toBe(false); expect($('[data-name=alien]').is(':visible')).toBe(true); + expect($('.js-emoji-menu-search').val()).toBe('ali'); + }) + .then(done) + .catch((err) => { + 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(() => { + awardsHandler.searchEmojis('ali'); + expect($('[data-name=angel]').is(':visible')).toBe(false); + expect($('[data-name=anger]').is(':visible')).toBe(false); + expect($('[data-name=alien]').is(':visible')).toBe(true); + awardsHandler.searchEmojis(''); + expect($('[data-name=angel]').is(':visible')).toBe(true); + expect($('[data-name=anger]').is(':visible')).toBe(true); + expect($('[data-name=alien]').is(':visible')).toBe(true); + expect($('.js-emoji-menu-search').val()).toBe(''); }) .then(done) .catch((err) => { @@ -234,6 +253,7 @@ require('~/lib/utils/common_utils'); }); }); }); + describe('emoji menu', function() { const emojiSelector = '[data-name="sunglasses"]'; const openEmojiMenuAndAddEmoji = function() { diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js index 806d728a874..03edbf9f947 100644 --- a/spec/javascripts/issue_show/issue_title_spec.js +++ b/spec/javascripts/issue_show/issue_title_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import issueTitle from '~/issue_show/issue_title'; +import issueTitle from '~/issue_show/issue_title.vue'; describe('Issue Title', () => { let IssueTitleComponent; diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index a3141894c74..d5746107ee1 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -114,8 +114,27 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do expect(hash).to eq({ link => user }) end - it 'returns an empty Hash when the list of nodes is empty' do - expect(subject.grouped_objects_for_nodes([], User, 'data-user')).to eq({}) + it 'returns an empty Hash when entry does not exist in the database' do + link = double(:link) + + expect(link).to receive(:has_attribute?). + with('data-user'). + and_return(true) + + expect(link).to receive(:attr). + with('data-user'). + and_return('1') + + nodes = [link] + bad_id = user.id + 100 + + expect(subject).to receive(:unique_attribute_values). + with(nodes, 'data-user'). + and_return([bad_id.to_s]) + + hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user') + + expect(hash).to eq({}) end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 9e3bd6d662f..03f040f4465 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -43,13 +43,29 @@ describe Gitlab::Ci::Trace::Stream do it 'forwards to the next linefeed, case 1' do stream.limit(7) - expect(stream.raw).to eq('') + result = stream.raw + + expect(result).to eq('') + expect(result.encoding).to eq(Encoding.default_external) end it 'forwards to the next linefeed, case 2' do stream.limit(29) - expect(stream.raw).to eq("\e[01;32m許功蓋\e[0m\n") + result = stream.raw + + expect(result).to eq("\e[01;32m許功蓋\e[0m\n") + expect(result.encoding).to eq(Encoding.default_external) + end + + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/30796 + it 'reads in binary, output as Encoding.default_external' do + stream.limit(52) + + result = stream.html + + expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ<br><span class=\"term-fg-green\">許功蓋</span><br>") + expect(result.encoding).to eq(Encoding.default_external) end end end diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 4e71597521d..ced93c8f762 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -29,7 +29,8 @@ RSpec.describe AbuseReport, type: :model do it 'lets a worker delete the user' do expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, - delete_solo_owned_groups: true) + delete_solo_owned_groups: true, + hard_delete: true) subject.remove_user(deleted_by: user) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 5e5c2b016b6..74d5ebc6db0 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -171,6 +171,27 @@ describe Repository, models: true do end end + describe '#commits' do + it 'sets follow when path is a single path' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice + + repository.commits('master', path: 'README.md') + repository.commits('master', path: ['README.md']) + end + + it 'does not set follow when path is multiple paths' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original + + repository.commits('master', path: ['README.md', 'CHANGELOG']) + end + + it 'does not set follow when there are no paths' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original + + repository.commits('master') + end + end + describe '#find_commits_by_message' do it 'returns commits with messages containing a given string' do commit_ids = repository.find_commits_by_message('submodule').map(&:id) @@ -1259,7 +1280,6 @@ describe Repository, models: true do :changelog, :license, :contributing, - :version, :gitignore, :koding, :gitlab_ci, diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 43c18992d1a..4bc30018ebd 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -152,6 +152,12 @@ describe Users::DestroyService, services: true do service.execute(user) end + + it 'does not run `MigrateToGhostUser` if hard_delete option is given' do + expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute) + + service.execute(user, hard_delete: true) + end end end end |