diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-04-19 09:57:43 +0100 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2017-04-19 09:57:43 +0100 |
commit | 836fddd0da9fb1a40e25d96b1c6ff1a36dde8e55 (patch) | |
tree | 8f5c6a345a7555f793dbf5f699d21c6f66e34e96 | |
parent | bd6cbf9145567298fb64442ffc3abf5746ba9a9c (diff) | |
parent | bbd83376d625b8d9cb73cbc83c3c0eb71b1abf32 (diff) | |
download | gitlab-ce-vue-pipelines.tar.gz |
Merge branch 'master' into vue-pipelinesvue-pipelines
* master: (23 commits)
Fix container registry navigation menu highlights
Resolve "Mini pipeline graph + status badge, when updating in real time don't change color and svg icon"
Refactor group search out of global search
disables test settings on chat notification services when repository is empty
Disable initialization table pipeline for new merge request form
Improves support for long build traces:
Review changes, used eq instead of match
Remove lighten blue and add blue-25 for background
Fixed tests
29595 Customize experience callout design
Remove unneeded format block
Fixed tests
29595 Customize experience callout design
Remove issue boards from recent searches
Updated specs
Remove helper
[ci skip] Use favicon full path
Update phrasing via @jschatz1 comments
Change heading
Update index.md
...
50 files changed, 529 insertions, 142 deletions
diff --git a/app/assets/images/ci_favicons/icon_status_canceled.ico b/app/assets/images/ci_favicons/favicon_status_canceled.ico Binary files differindex 5a19458f2a2..5a19458f2a2 100755 --- a/app/assets/images/ci_favicons/icon_status_canceled.ico +++ b/app/assets/images/ci_favicons/favicon_status_canceled.ico diff --git a/app/assets/images/ci_favicons/icon_status_created.ico b/app/assets/images/ci_favicons/favicon_status_created.ico Binary files differindex 4dca9640cb3..4dca9640cb3 100755 --- a/app/assets/images/ci_favicons/icon_status_created.ico +++ b/app/assets/images/ci_favicons/favicon_status_created.ico diff --git a/app/assets/images/ci_favicons/icon_status_failed.ico b/app/assets/images/ci_favicons/favicon_status_failed.ico Binary files differindex c961ff9a69b..c961ff9a69b 100755 --- a/app/assets/images/ci_favicons/icon_status_failed.ico +++ b/app/assets/images/ci_favicons/favicon_status_failed.ico diff --git a/app/assets/images/ci_favicons/icon_status_manual.ico b/app/assets/images/ci_favicons/favicon_status_manual.ico Binary files differindex 5fbbc99ea7c..5fbbc99ea7c 100755 --- a/app/assets/images/ci_favicons/icon_status_manual.ico +++ b/app/assets/images/ci_favicons/favicon_status_manual.ico diff --git a/app/assets/images/ci_favicons/icon_status_not_found.ico b/app/assets/images/ci_favicons/favicon_status_not_found.ico Binary files differindex 21afa9c72e6..21afa9c72e6 100755 --- a/app/assets/images/ci_favicons/icon_status_not_found.ico +++ b/app/assets/images/ci_favicons/favicon_status_not_found.ico diff --git a/app/assets/images/ci_favicons/icon_status_pending.ico b/app/assets/images/ci_favicons/favicon_status_pending.ico Binary files differindex 8be32dab85a..8be32dab85a 100755 --- a/app/assets/images/ci_favicons/icon_status_pending.ico +++ b/app/assets/images/ci_favicons/favicon_status_pending.ico diff --git a/app/assets/images/ci_favicons/icon_status_running.ico b/app/assets/images/ci_favicons/favicon_status_running.ico Binary files differindex f328ff1a5ed..f328ff1a5ed 100755 --- a/app/assets/images/ci_favicons/icon_status_running.ico +++ b/app/assets/images/ci_favicons/favicon_status_running.ico diff --git a/app/assets/images/ci_favicons/icon_status_skipped.ico b/app/assets/images/ci_favicons/favicon_status_skipped.ico Binary files differindex b4394e1b4af..b4394e1b4af 100755 --- a/app/assets/images/ci_favicons/icon_status_skipped.ico +++ b/app/assets/images/ci_favicons/favicon_status_skipped.ico diff --git a/app/assets/images/ci_favicons/icon_status_success.ico b/app/assets/images/ci_favicons/favicon_status_success.ico Binary files differindex 4f436c95242..4f436c95242 100755 --- a/app/assets/images/ci_favicons/icon_status_success.ico +++ b/app/assets/images/ci_favicons/favicon_status_success.ico diff --git a/app/assets/images/ci_favicons/icon_status_warning.ico b/app/assets/images/ci_favicons/favicon_status_warning.ico Binary files differindex 805cc20cdec..805cc20cdec 100755 --- a/app/assets/images/ci_favicons/icon_status_warning.ico +++ b/app/assets/images/ci_favicons/favicon_status_warning.ico diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 0aad95c2fe3..97f279e4be4 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -2,6 +2,8 @@ consistent-return, prefer-rest-params */ /* global Breakpoints */ +import { bytesToKiB } from './lib/utils/number_utils'; + const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; }; const AUTO_SCROLL_OFFSET = 75; const DOWN_BUILD_TRACE = '#down-build-trace'; @@ -20,6 +22,7 @@ window.Build = (function () { this.state = this.options.logState; this.buildStage = this.options.buildStage; this.$document = $(document); + this.logBytes = 0; this.updateDropdown = bind(this.updateDropdown, this); @@ -98,15 +101,22 @@ window.Build = (function () { if (log.append) { $buildContainer.append(log.html); + this.logBytes += log.size; } else { $buildContainer.html(log.html); - if (log.truncated) { - $('.js-truncated-info-size').html(` ${log.size} `); - this.$truncatedInfo.removeClass('hidden'); - this.initAffixTruncatedInfo(); - } else { - this.$truncatedInfo.addClass('hidden'); - } + this.logBytes = log.size; + } + + // if the incremental sum of logBytes we received is less than the total + // we need to show a message warning the user about that. + if (this.logBytes < log.total) { + // size is in bytes, we need to calculate KiB + const size = bytesToKiB(this.logBytes); + $('.js-truncated-info-size').html(`${size}`); + this.$truncatedInfo.removeClass('hidden'); + this.initAffixTruncatedInfo(); + } else { + this.$truncatedInfo.addClass('hidden'); } this.checkAutoscroll(); diff --git a/app/assets/javascripts/ci_status_icons.js b/app/assets/javascripts/ci_status_icons.js new file mode 100644 index 00000000000..f16616873b2 --- /dev/null +++ b/app/assets/javascripts/ci_status_icons.js @@ -0,0 +1,34 @@ +import CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg'; +import CREATED_SVG from 'icons/_icon_status_created_borderless.svg'; +import FAILED_SVG from 'icons/_icon_status_failed_borderless.svg'; +import MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg'; +import PENDING_SVG from 'icons/_icon_status_pending_borderless.svg'; +import RUNNING_SVG from 'icons/_icon_status_running_borderless.svg'; +import SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg'; +import SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg'; +import WARNING_SVG from 'icons/_icon_status_warning_borderless.svg'; + +const StatusIconEntityMap = { + icon_status_canceled: CANCELED_SVG, + icon_status_created: CREATED_SVG, + icon_status_failed: FAILED_SVG, + icon_status_manual: MANUAL_SVG, + icon_status_pending: PENDING_SVG, + icon_status_running: RUNNING_SVG, + icon_status_skipped: SKIPPED_SVG, + icon_status_success: SUCCESS_SVG, + icon_status_warning: WARNING_SVG, +}; + +export { + CANCELED_SVG, + CREATED_SVG, + FAILED_SVG, + MANUAL_SVG, + PENDING_SVG, + RUNNING_SVG, + SKIPPED_SVG, + SUCCESS_SVG, + WARNING_SVG, + StatusIconEntityMap as default, +}; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 33c900e264b..01c4b9821d3 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -368,9 +368,9 @@ }); }; - w.gl.utils.setFavicon = (iconName) => { - if (faviconEl && iconName) { - faviconEl.setAttribute('href', `/assets/${iconName}.ico`); + w.gl.utils.setFavicon = (faviconPath) => { + if (faviconEl && faviconPath) { + faviconEl.setAttribute('href', faviconPath); } }; @@ -385,8 +385,8 @@ url: pageUrl, dataType: 'json', success: function(data) { - if (data && data.icon) { - gl.utils.setFavicon(`ci_favicons/${data.icon}`); + if (data && data.favicon) { + gl.utils.setFavicon(data.favicon); } else { gl.utils.resetFavicon(); } diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js new file mode 100644 index 00000000000..1e96c7ab5cd --- /dev/null +++ b/app/assets/javascripts/lib/utils/constants.js @@ -0,0 +1,2 @@ +/* eslint-disable import/prefer-default-export */ +export const BYTES_IN_KIB = 1024; diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index e2bf69ee52e..f1b07408671 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -1,4 +1,4 @@ -/* eslint-disable import/prefer-default-export */ +import { BYTES_IN_KIB } from './constants'; /** * Function that allows a number with an X amount of decimals @@ -32,3 +32,13 @@ export function formatRelevantDigits(number) { } return formattedNumber; } + +/** + * Utility function that calculates KiB of the given bytes. + * + * @param {Number} number bytes + * @return {Number} KiB + */ +export function bytesToKiB(number) { + return number / BYTES_IN_KIB; +} diff --git a/app/assets/javascripts/pipelines/components/stage.js b/app/assets/javascripts/pipelines/components/stage.js index a2c29002707..b8cc3630611 100644 --- a/app/assets/javascripts/pipelines/components/stage.js +++ b/app/assets/javascripts/pipelines/components/stage.js @@ -1,32 +1,11 @@ /* global Flash */ -import canceledSvg from 'icons/_icon_status_canceled_borderless.svg'; -import createdSvg from 'icons/_icon_status_created_borderless.svg'; -import failedSvg from 'icons/_icon_status_failed_borderless.svg'; -import manualSvg from 'icons/_icon_status_manual_borderless.svg'; -import pendingSvg from 'icons/_icon_status_pending_borderless.svg'; -import runningSvg from 'icons/_icon_status_running_borderless.svg'; -import skippedSvg from 'icons/_icon_status_skipped_borderless.svg'; -import successSvg from 'icons/_icon_status_success_borderless.svg'; -import warningSvg from 'icons/_icon_status_warning_borderless.svg'; +import StatusIconEntityMap from '../../ci_status_icons'; export default { data() { - const svgsDictionary = { - icon_status_canceled: canceledSvg, - icon_status_created: createdSvg, - icon_status_failed: failedSvg, - icon_status_manual: manualSvg, - icon_status_pending: pendingSvg, - icon_status_running: runningSvg, - icon_status_skipped: skippedSvg, - icon_status_success: successSvg, - icon_status_warning: warningSvg, - }; - return { builds: '', spinner: '<span class="fa fa-spinner fa-spin"></span>', - svg: svgsDictionary[this.stage.status.icon], }; }, @@ -89,6 +68,9 @@ export default { triggerButtonClass() { return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; }, + svgHTML() { + return StatusIconEntityMap[this.stage.status.icon]; + }, }, template: ` <div> @@ -100,7 +82,7 @@ export default { data-toggle="dropdown" type="button" :aria-label="stage.title"> - <span v-html="svg" aria-hidden="true"></span> + <span v-html="svgHTML" aria-hidden="true"></span> <i class="fa fa-caret-down" aria-hidden="true"></i> </button> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 144adbcdaef..411f1c4442b 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -61,8 +61,9 @@ .truncated-info { text-align: center; border-bottom: 1px solid; - background-color: $black-transparent; + background-color: $black; height: 45px; + padding: 15px; &.affix { top: 0; @@ -87,6 +88,16 @@ right: 5px; left: 5px; } + + .truncated-info-size { + margin: 0 5px; + } + + .raw-link { + color: inherit; + margin-left: 5px; + text-decoration: underline; + } } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 8c6dd392865..fe084eb9397 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -289,8 +289,12 @@ table.u2f-registrations { margin: 0 auto; .bordered-box { - border: 1px solid $border-color; + border: 1px solid $blue-300; border-radius: $border-radius-default; + background-color: $blue-25; + position: relative; + display: flex; + justify-content: center; } .landing { @@ -298,28 +302,59 @@ table.u2f-registrations { margin-bottom: $gl-padding; .close { - margin-right: 20px; - } + position: absolute; + right: 20px; + opacity: 1; + + .dismiss-icon { + float: right; + cursor: pointer; + color: $blue-300; + } - .dismiss-icon { - float: right; - cursor: pointer; - color: $cycle-analytics-dismiss-icon-color; + &:hover { + background-color: transparent; + border: 0; + + .dismiss-icon { + color: $blue-400; + } + } } .svg-container { - text-align: center; + margin-right: 30px; + display: inline-block; svg { - width: 136px; - height: 136px; + height: 110px; + vertical-align: top; } } + + .user-callout-copy { + display: inline-block; + vertical-align: top; + } } @media(max-width: $screen-xs-max) { - .inner-content { - padding-left: 30px; + text-align: center; + + .bordered-box { + display: block; + } + + .landing { + .svg-container, + .user-callout-copy { + margin: 0; + display: block; + + svg { + height: 75px; + } + } } } } diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index fa782c6fbb7..f2dfb87dbda 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -22,7 +22,7 @@ class ChatNotificationService < Service end def can_test? - valid? + super && valid? end def self.supported_events diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index dfd9d1584a1..944472f3e51 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,8 +1,12 @@ class StatusEntity < Grape::Entity include RequestAwareEntity - expose :icon, :favicon, :text, :label, :group + expose :icon, :text, :label, :group expose :has_details?, as: :has_details expose :details_path + + expose :favicon do |status| + ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico")) + end end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index 8409b592b72..ff188102b62 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -7,16 +7,13 @@ module Search end def execute - group = Group.find_by(id: params[:group_id]) if params[:group_id].present? - projects = ProjectsFinder.new(current_user: current_user).execute - - if group - projects = projects.inside_path(group.full_path) - end - Gitlab::SearchResults.new(current_user, projects, params[:search]) end + def projects + @projects ||= ProjectsFinder.new(current_user: current_user).execute + end + def scope @scope ||= begin allowed_scopes = %w[issues merge_requests milestones] diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb new file mode 100644 index 00000000000..29478e3251f --- /dev/null +++ b/app/services/search/group_service.rb @@ -0,0 +1,18 @@ +module Search + class GroupService < Search::GlobalService + attr_accessor :group + + def initialize(user, group, params) + super(user, params) + + @group = group + end + + def projects + return Project.none unless group + return @projects if defined? @projects + + @projects = super.inside_path(group.full_path) + end + end +end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 8d46a8dab3e..22736c71725 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -54,6 +54,8 @@ class SearchService Search::ProjectService.new(project, current_user, params) elsif show_snippets? Search::SnippetService.new(current_user, params) + elsif group + Search::GroupService.new(current_user, group, params) else Search::GlobalService.new(current_user, params) end diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e34cddeb3e2..37429c7cfc0 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -11,13 +11,13 @@ Project - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases graphs network)) do + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do %span Repository - if project_nav_tab? :container_registry - = nav_link(controller: %w(container_registry)) do + = nav_link(controller: %w[projects/registry/repositories]) do = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do %span Registry diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 0faad57a312..7cb2ec83cc7 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -71,11 +71,11 @@ = custom_icon('scroll_down_hover_active') #up-build-trace %pre.build-trace#build-trace - .js-truncated-info.truncated-info.hidden - %span< - Showing last - %span.js-truncated-info-size>< - KiB of log + .js-truncated-info.truncated-info.hidden< + Showing last + %span.js-truncated-info-size.truncated-info-size>< + KiB of log - + %a.js-raw-link.raw-link{ :href => raw_namespace_project_build_path(@project.namespace, @project, @build) }>< Complete Raw %code.bash.js-build-output .build-loader-animation.js-build-refresh diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 03069804c86..da79ca2ee75 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -46,7 +46,7 @@ -# This tab is always loaded via AJAX - if @pipelines.any? #pipelines.pipelines.tab-pane - = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)) + = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index de4aa255bbd..2f1dbe87619 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1,3 +1,4 @@ - endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json) +- disable_initialization = local_assigns.fetch(:disable_initialization, false) -= render 'projects/commit/pipelines_list', endpoint: endpoint_path += render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml index 8f1293adcb1..8308baa7829 100644 --- a/app/views/shared/_user_callout.html.haml +++ b/app/views/shared/_user_callout.html.haml @@ -3,12 +3,11 @@ %button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss customize experience box' } = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') - .row - .col-sm-3.col-xs-12.svg-container - = custom_icon('icon_customization') - .col-sm-8.col-xs-12.inner-content - %h4 - Customize your experience - %p - Change syntax themes, default project pages, and more in preferences. - = link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout' + .svg-container + = custom_icon('icon_customization') + .user-callout-copy + %h4 + Customize your experience + %p + Change syntax themes, default project pages, and more in preferences. + = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout' diff --git a/changelogs/unreleased/29595-customize-experience-callout.yml b/changelogs/unreleased/29595-customize-experience-callout.yml new file mode 100644 index 00000000000..ec8393142c6 --- /dev/null +++ b/changelogs/unreleased/29595-customize-experience-callout.yml @@ -0,0 +1,4 @@ +--- +title: 29595 Update callout design +merge_request: +author: diff --git a/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml b/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml new file mode 100644 index 00000000000..6e43a032f20 --- /dev/null +++ b/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml @@ -0,0 +1,4 @@ +--- +title: Disable test settings on chat notification services when repository is empty +merge_request: 10759 +author: diff --git a/doc/README.md b/doc/README.md index b6790b4d008..7703c64e152 100644 --- a/doc/README.md +++ b/doc/README.md @@ -6,10 +6,6 @@ All technical content published by GitLab lives in the documentation, including: - [User docs](#user-documentation): general documentation dedicated to regular users of GitLab - [Admin docs](#administrator-documentation): general documentation dedicated to administrators of GitLab instances - [Contributor docs](#contributor-documentation): general documentation on how to develop and contribute to GitLab -- [Topics](topics/index.md): pages organized per topic, gathering all the - resources already published by GitLab related to a specific subject, including - general docs, [technical articles](development/writing_documentation.md#technical-articles), - blog posts and video tutorials. - [GitLab University](university/README.md): guides to learn Git and GitLab through courses and videos. diff --git a/doc/user/search/img/search_history.gif b/doc/user/search/img/search_history.gif Binary files differnew file mode 100644 index 00000000000..4cfa48ee0ab --- /dev/null +++ b/doc/user/search/img/search_history.gif diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 9d1ca1adcb2..45f443819ec 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -40,6 +40,12 @@ The same process is valid for merge requests. Navigate to your project's **Merge and click **Search or filter results...**. Merge requests can be filtered by author, assignee, milestone, and label. +## Search History + +You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser. + +![search history](img/search_history.gif) + ### Shortcut You'll also find a shortcut on the search field on the top-right of the project's dashboard to diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 68b14c7c04c..fa462cbe095 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -4,7 +4,7 @@ module Gitlab # This was inspired from: http://stackoverflow.com/a/10219411/1520132 class Stream BUFFER_SIZE = 4096 - LIMIT_SIZE = 50.kilobytes + LIMIT_SIZE = 500.kilobytes attr_reader :stream diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index bcdf1b1faa8..c69676a1dac 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -15,7 +15,7 @@ module Gitlab end unless URI(address).scheme.in?(%w(tcp unix)) - raise "Unsupported Gitaly address: #{address.inspect}" + raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'" end @addresses[name] = address diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb index 13208d21918..faf3770f5e9 100644 --- a/spec/controllers/projects/builds_controller_spec.rb +++ b/spec/controllers/projects/builds_controller_spec.rb @@ -60,7 +60,7 @@ describe Projects::BuildsController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq status.favicon + expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 1739d40ab88..cc393bd24f2 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq status.favicon + expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index d8f9bfd0d37..d9192177a06 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -86,7 +86,7 @@ describe Projects::PipelinesController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq status.favicon + expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" end end end diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index 7174bf1e041..8ec96bdb583 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -1,11 +1,11 @@ /* eslint-disable no-new */ /* global Build */ - -require('~/lib/utils/datetime_utility'); -require('~/lib/utils/url_utility'); -require('~/build'); -require('~/breakpoints'); -require('vendor/jquery.nicescroll'); +import { bytesToKiB } from '~/lib/utils/number_utils'; +import '~/lib/utils/datetime_utility'; +import '~/lib/utils/url_utility'; +import '~/build'; +import '~/breakpoints'; +import 'vendor/jquery.nicescroll'; describe('Build', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`; @@ -144,24 +144,6 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).toMatch(/Different/); }); - it('shows information about truncated log', () => { - jasmine.clock().tick(4001); - const [{ success }] = $.ajax.calls.argsFor(0); - - success.call($, { - html: '<span>Update</span>', - status: 'success', - append: false, - truncated: true, - size: '50', - }); - - expect( - $('#build-trace .js-truncated-info').text().trim(), - ).toContain('Showing last 50 KiB of log'); - expect($('#build-trace .js-truncated-info-size').text()).toMatch('50'); - }); - it('reloads the page when the build is done', () => { spyOn(gl.utils, 'visitUrl'); @@ -176,6 +158,107 @@ describe('Build', () => { expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL); }); + + describe('truncated information', () => { + describe('when size is less than total', () => { + it('shows information about truncated log', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden'); + }); + + it('shows the size in KiB', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + const size = 50; + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(size)}`); + }); + + it('shows incremented size', () => { + jasmine.clock().tick(4001); + let args = $.ajax.calls.argsFor(0)[0]; + args.success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(50)}`); + + jasmine.clock().tick(4001); + args = $.ajax.calls.argsFor(2)[0]; + args.success.call($, { + html: '<span>Update</span>', + status: 'success', + append: true, + size: 10, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(60)}`); + }); + + it('renders the raw link', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect( + document.querySelector('.js-raw-link').textContent.trim(), + ).toContain('Complete Raw'); + }); + }); + + describe('when size is equal than total', () => { + it('does not show the trunctated information', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 100, + total: 100, + }); + + expect(document.querySelector('.js-truncated-info').classList).toContain('hidden'); + }); + }); + }); }); }); }); diff --git a/spec/javascripts/ci_status_icon_spec.js b/spec/javascripts/ci_status_icon_spec.js new file mode 100644 index 00000000000..c83416c15ef --- /dev/null +++ b/spec/javascripts/ci_status_icon_spec.js @@ -0,0 +1,44 @@ +import * as icons from '~/ci_status_icons'; + +describe('CI status icons', () => { + const statuses = [ + 'canceled', + 'created', + 'failed', + 'manual', + 'pending', + 'running', + 'skipped', + 'success', + 'warning', + ]; + + statuses.forEach((status) => { + it(`should export a ${status} svg`, () => { + const key = `${status.toUpperCase()}_SVG`; + + expect(Object.hasOwnProperty.call(icons, key)).toBe(true); + expect(icons[key]).toMatch(/^<svg/); + }); + }); + + describe('default export map', () => { + const entityIconNames = [ + 'icon_status_canceled', + 'icon_status_created', + 'icon_status_failed', + 'icon_status_manual', + 'icon_status_pending', + 'icon_status_running', + 'icon_status_skipped', + 'icon_status_success', + 'icon_status_warning', + ]; + + entityIconNames.forEach((iconName) => { + it(`should have a '${iconName}' key`, () => { + expect(Object.hasOwnProperty.call(icons.default, iconName)).toBe(true); + }); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 03f3c206f44..56aabc16382 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -313,7 +313,7 @@ require('~/lib/utils/common_utils'); describe('gl.utils.setFavicon', () => { it('should set page favicon to provided favicon', () => { - const faviconName = 'custom_favicon'; + const faviconPath = '//custom_favicon'; const fakeLink = { setAttribute() {}, }; @@ -321,9 +321,9 @@ require('~/lib/utils/common_utils'); spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { expect(attr).toEqual('href'); - expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true); + expect(val.indexOf(faviconPath) > -1).toBe(true); }); - gl.utils.setFavicon(faviconName); + gl.utils.setFavicon(faviconPath); }); }); @@ -345,13 +345,12 @@ require('~/lib/utils/common_utils'); describe('gl.utils.setCiStatusFavicon', () => { it('should set page favicon to CI status favicon based on provided status', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`; - const FAVICON_PATH = 'ci_favicons/'; - const FAVICON = 'icon_status_success'; + const FAVICON_PATH = '//icon_status_success'; const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub(); const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub(); spyOn($, 'ajax').and.callFake(function (options) { - options.success({ icon: FAVICON }); - expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON); + options.success({ favicon: FAVICON_PATH }); + expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH); options.success(); expect(spyResetFavicon).toHaveBeenCalled(); options.error(); diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js index 5fde8be9123..90b12c9f115 100644 --- a/spec/javascripts/lib/utils/number_utility_spec.js +++ b/spec/javascripts/lib/utils/number_utility_spec.js @@ -1,4 +1,4 @@ -import { formatRelevantDigits } from '~/lib/utils/number_utils'; +import { formatRelevantDigits, bytesToKiB } from '~/lib/utils/number_utils'; describe('Number Utils', () => { describe('formatRelevantDigits', () => { @@ -38,4 +38,11 @@ describe('Number Utils', () => { expect(leftFromDecimal.length).toBe(3); }); }); + + describe('bytesToKiB', () => { + it('calculates KiB for the given bytes', () => { + expect(bytesToKiB(1024)).toEqual(1); + expect(bytesToKiB(1000)).toEqual(0.9765625); + }); + }); }); diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js index c0375ebc61c..28d0c7dcd99 100644 --- a/spec/javascripts/user_callout_spec.js +++ b/spec/javascripts/user_callout_spec.js @@ -14,7 +14,6 @@ describe('UserCallout', function () { this.userCallout = new UserCallout(); this.closeButton = $('.js-close-callout.close'); this.userCalloutBtn = $('.js-close-callout:not(.close)'); - this.userCalloutContainer = $('.user-callout'); }); it('hides when user clicks on the dismiss-icon', (done) => { diff --git a/spec/javascripts/vue_pipelines_index/stage_spec.js b/spec/javascripts/vue_pipelines_index/stage_spec.js new file mode 100644 index 00000000000..542661df2b0 --- /dev/null +++ b/spec/javascripts/vue_pipelines_index/stage_spec.js @@ -0,0 +1,66 @@ +import Vue from 'vue'; +import { SUCCESS_SVG } from '~/ci_status_icons'; +import Stage from '~/vue_pipelines_index/components/stage'; + +function minify(string) { + return string.replace(/\s/g, ''); +} + +describe('Pipelines Stage', () => { + describe('data', () => { + let stageReturnValue; + + beforeEach(() => { + stageReturnValue = Stage.data(); + }); + + it('should return object with .builds and .spinner', () => { + expect(stageReturnValue).toEqual({ + builds: '', + spinner: '<span class="fa fa-spinner fa-spin"></span>', + }); + }); + }); + + describe('computed', () => { + describe('svgHTML', function () { + let stage; + let svgHTML; + + beforeEach(() => { + stage = { stage: { status: { icon: 'icon_status_success' } } }; + + svgHTML = Stage.computed.svgHTML.call(stage); + }); + + it("should return the correct icon for the stage's status", () => { + expect(svgHTML).toBe(SUCCESS_SVG); + }); + }); + }); + + describe('when mounted', () => { + let StageComponent; + let renderedComponent; + let stage; + + beforeEach(() => { + stage = { status: { icon: 'icon_status_success' } }; + + StageComponent = Vue.extend(Stage); + + renderedComponent = new StageComponent({ + propsData: { + stage, + }, + }).$mount(); + }); + + it('should render the correct status svg', () => { + const minifiedComponent = minify(renderedComponent.$el.outerHTML); + const expectedSVG = minify(SUCCESS_SVG); + + expect(minifiedComponent).toContain(expectedSVG); + }); + }); +}); diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb index c98e7ee14fd..592c90cda36 100644 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ b/spec/models/project_services/chat_notification_service_spec.rb @@ -1,11 +1,29 @@ require 'spec_helper' describe ChatNotificationService, models: true do - describe "Associations" do + describe 'Associations' do before do allow(subject).to receive(:activated?).and_return(true) end it { is_expected.to validate_presence_of :webhook } end + + describe '#can_test?' do + context 'with empty repository' do + it 'returns false' do + subject.project = create(:empty_project, :empty_repo) + + expect(subject.can_test?).to be false + end + end + + context 'with repository' do + it 'returns true' do + subject.project = create(:project) + + expect(subject.can_test?).to be true + end + end + end end diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb index 3cc791bca50..7f1abecfafe 100644 --- a/spec/serializers/build_serializer_spec.rb +++ b/spec/serializers/build_serializer_spec.rb @@ -38,7 +38,7 @@ describe BuildSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to eq(status.favicon) + expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico") end end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index f6249ab4664..ecde45a6d44 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -144,7 +144,7 @@ describe PipelineSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to eq(status.favicon) + expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico") end end end diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb index 2531607acad..cbf4f56213d 100644 --- a/spec/services/search/global_service_spec.rb +++ b/spec/services/search/global_service_spec.rb @@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do expect(results.objects('projects')).to match_array [found_project] end - - context 'nested group' do - let!(:nested_group) { create(:group, :nested) } - let!(:project) { create(:empty_project, namespace: nested_group) } - - before do - project.add_master(user) - end - - it 'returns result from nested group' do - results = Search::GlobalService.new(user, search: project.path).execute - - expect(results.objects('projects')).to match_array [project] - end - - it 'returns result from descendants when search inside group' do - results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute - - expect(results.objects('projects')).to match_array [project] - end - end end end end diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb new file mode 100644 index 00000000000..38f264f6e7b --- /dev/null +++ b/spec/services/search/group_service_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Search::GroupService, services: true do + shared_examples_for 'group search' do + context 'finding projects by name' do + let(:user) { create(:user) } + let(:term) { "Project Name" } + let(:nested_group) { create(:group, :nested) } + + # These projects shouldn't be found + let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") } + let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )} + let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) } + + # These projects should be found + let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") } + let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") } + let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") } + + let(:results) { Search::GroupService.new(user, search_group, search: term).execute } + subject { results.objects('projects') } + + context 'in parent group' do + let(:search_group) { nested_group.parent } + + it { is_expected.to match_array([project1, project2, project3]) } + end + + context 'in subgroup' do + let(:search_group) { nested_group } + + it { is_expected.to match_array([project1, project2]) } + end + end + end + + describe 'basic search' do + include_examples 'group search' + end +end diff --git a/spec/views/layouts/nav/_project.html.haml_spec.rb b/spec/views/layouts/nav/_project.html.haml_spec.rb new file mode 100644 index 00000000000..fd1637ca91b --- /dev/null +++ b/spec/views/layouts/nav/_project.html.haml_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'layouts/nav/_project' do + describe 'container registry tab' do + before do + stub_container_registry_config(enabled: true) + + assign(:project, create(:project)) + allow(view).to receive(:current_ref).and_return('master') + + allow(view).to receive(:can?).and_return(true) + allow(controller).to receive(:controller_name) + .and_return('repositories') + allow(controller).to receive(:controller_path) + .and_return('projects/registry/repositories') + end + + it 'has both Registry and Repository tabs' do + render + + expect(rendered).to have_text 'Repository' + expect(rendered).to have_text 'Registry' + end + + it 'highlights only one tab' do + render + + expect(rendered).to have_css('.active', count: 1) + end + + it 'highlights container registry tab only' do + render + + expect(rendered).to have_css('.active', text: 'Registry') + end + end +end |