diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 766fb3a2ef7..f8f73e4d4d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -128,7 +128,7 @@ stages: - export CACHE_CLASSES=true - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - scripts/gitaly-test-spawn - - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' + - knapsack spinach "-r rerun" -b || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -b -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: expire_in: 31d when: always @@ -174,7 +174,8 @@ build-package: # Review docs base .review-docs: &review-docs image: ruby:2.4-alpine - before_script: [] + before_script: + - gem install gitlab --no-doc services: [] variables: SETUP_DB: "false" @@ -193,10 +194,9 @@ review-docs-deploy: name: review-docs/$CI_COMMIT_REF_NAME # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables # Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693 - url: http://$CI_COMMIT_REF_SLUG-built-from-ce-ee.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX + url: http://preview-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX on_stop: review-docs-cleanup script: - - gem install gitlab --no-doc - scripts/trigger-build-docs deploy # Cleanup remote environment of gitlab-docs @@ -207,7 +207,6 @@ review-docs-cleanup: name: review-docs/$CI_COMMIT_REF_NAME action: stop script: - - gem install gitlab --no-doc - scripts/trigger-build-docs cleanup # Retrieve knapsack and rspec_flaky reports @@ -413,12 +412,12 @@ downtime_check: ee_compat_check: <<: *rake-exec - only: - - branches@gitlab-org/gitlab-ce except: - master - tags - /^[\d-]+-stable(-ee)?/ + - branches@gitlab-org/gitlab-ee + - branches@gitlab/gitlab-ee allow_failure: yes cache: key: "ee_compat_check_repo" @@ -517,6 +516,12 @@ db:seed_fu-mysql: <<: *db-seed_fu <<: *use-mysql +db:check-schema-pg: + <<: *db-migrate-reset + <<: *use-pg + script: + - source scripts/schema_changed.sh + # Frontend-related jobs gitlab:assets:compile: <<: *dedicated-runner diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 4a36342fcab..fd2a01863fd 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -3.0.0 +3.1.0 diff --git a/Gemfile b/Gemfile index cc6618d3557..fa25d8ded33 100644 --- a/Gemfile +++ b/Gemfile @@ -362,6 +362,7 @@ group :test do gem 'sham_rack', '~> 1.3.6' gem 'timecop', '~> 0.8.0' gem 'concurrent-ruby', '~> 1.0.5' + gem 'test-prof', '~> 0.2.5' end gem 'octokit', '~> 4.6.2' diff --git a/Gemfile.lock b/Gemfile.lock index e10db81d0c9..90154d98c9c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -882,6 +882,7 @@ GEM ffi sysexits (1.2.0) temple (0.7.7) + test-prof (0.2.5) test_after_commit (1.1.0) activerecord (>= 3.2) text (1.3.1) @@ -1163,6 +1164,7 @@ DEPENDENCIES stackprof (~> 0.2.10) state_machines-activerecord (~> 0.4.0) sys-filesystem (~> 1.1.6) + test-prof (~> 0.2.5) test_after_commit (~> 1.1) thin (~> 1.7.0) timecop (~> 0.8.0) diff --git a/LICENSE b/LICENSE index ad4f2872db5..15c423e1416 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,7 @@ Copyright (c) 2011-2017 GitLab B.V. +With regard to the GitLab Software: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -17,3 +19,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For all third party components incorporated into the GitLab Software, those +components are licensed under the original license provided by the owner of the +applicable component. \ No newline at end of file diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index 904f7f64fa8..b41d464475f 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -73,7 +73,7 @@ Edit diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 298f737a2bc..497c23f014f 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -4,6 +4,8 @@ import Vue from 'vue'; +import '../mixins/discussion'; + const JumpToDiscussion = Vue.extend({ mixins: [DiscussionMixins], props: { diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js index 96e5a440357..fe7cf8f5fc1 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js @@ -4,6 +4,8 @@ import Vue from 'vue'; +import '../mixins/discussion'; + window.ResolveCount = Vue.extend({ mixins: [DiscussionMixins], props: { diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js deleted file mode 100644 index 800ca05cd11..00000000000 --- a/app/assets/javascripts/feature_highlight/feature_highlight.js +++ /dev/null @@ -1,61 +0,0 @@ -import Cookies from 'js-cookie'; -import _ from 'underscore'; -import { - getCookieName, - getSelector, - hidePopover, - setupDismissButton, - mouseenter, - mouseleave, -} from './feature_highlight_helper'; - -export const setupFeatureHighlightPopover = (id, debounceTimeout = 300) => { - const $selector = $(getSelector(id)); - const $parent = $selector.parent(); - const $popoverContent = $parent.siblings('.feature-highlight-popover-content'); - const hideOnScroll = hidePopover.bind($selector); - const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout); - - $selector - // Setup popover - .data('content', $popoverContent.prop('outerHTML')) - .popover({ - html: true, - // Override the existing template to add custom CSS classes - template: ` - - `, - }) - .on('mouseenter', mouseenter) - .on('mouseleave', debouncedMouseleave) - .on('inserted.bs.popover', setupDismissButton) - .on('show.bs.popover', () => { - window.addEventListener('scroll', hideOnScroll); - }) - .on('hide.bs.popover', () => { - window.removeEventListener('scroll', hideOnScroll); - }) - // Display feature highlight - .removeAttr('disabled'); -}; - -export const shouldHighlightFeature = (id) => { - const element = document.querySelector(getSelector(id)); - const previouslyDismissed = Cookies.get(getCookieName(id)) === 'true'; - - return element && !previouslyDismissed; -}; - -export const highlightFeatures = (highlightOrder) => { - const featureId = highlightOrder.find(shouldHighlightFeature); - - if (featureId) { - setupFeatureHighlightPopover(featureId); - return true; - } - - return false; -}; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js deleted file mode 100644 index 9f741355cd7..00000000000 --- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js +++ /dev/null @@ -1,57 +0,0 @@ -import Cookies from 'js-cookie'; - -export const getCookieName = cookieId => `feature-highlighted-${cookieId}`; -export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; - -export const showPopover = function showPopover() { - if (this.hasClass('js-popover-show')) { - return false; - } - this.popover('show'); - this.addClass('disable-animation js-popover-show'); - - return true; -}; - -export const hidePopover = function hidePopover() { - if (!this.hasClass('js-popover-show')) { - return false; - } - this.popover('hide'); - this.removeClass('disable-animation js-popover-show'); - - return true; -}; - -export const dismiss = function dismiss(cookieId) { - Cookies.set(getCookieName(cookieId), true); - hidePopover.call(this); - this.hide(); -}; - -export const mouseleave = function mouseleave() { - if (!$('.popover:hover').length > 0) { - const $featureHighlight = $(this); - hidePopover.call($featureHighlight); - } -}; - -export const mouseenter = function mouseenter() { - const $featureHighlight = $(this); - - const showedPopover = showPopover.call($featureHighlight); - if (showedPopover) { - $('.popover') - .on('mouseleave', mouseleave.bind($featureHighlight)); - } -}; - -export const setupDismissButton = function setupDismissButton() { - const popoverId = this.getAttribute('aria-describedby'); - const cookieId = this.dataset.highlight; - const $popover = $(this); - const dismissWrapper = dismiss.bind($popover, cookieId); - - $(`#${popoverId} .dismiss-feature-highlight`) - .on('click', dismissWrapper); -}; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_options.js b/app/assets/javascripts/feature_highlight/feature_highlight_options.js deleted file mode 100644 index fd48f2e87cc..00000000000 --- a/app/assets/javascripts/feature_highlight/feature_highlight_options.js +++ /dev/null @@ -1,12 +0,0 @@ -import { highlightFeatures } from './feature_highlight'; -import bp from '../breakpoints'; - -const highlightOrder = ['issue-boards']; - -export default function domContentLoaded(order) { - if (bp.getBreakpointSize() === 'lg') { - highlightFeatures(order); - } -} - -document.addEventListener('DOMContentLoaded', domContentLoaded.bind(this, highlightOrder)); diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js index 227bf65b560..b1ffd797f7e 100644 --- a/app/assets/javascripts/lib/utils/pretty_time.js +++ b/app/assets/javascripts/lib/utils/pretty_time.js @@ -1,68 +1,61 @@ import _ from 'underscore'; -(() => { - /* - * TODO: Make these methods more configurable (e.g. stringifyTime condensed or - * non-condensed, abbreviateTimelengths) - * */ +/* + * TODO: Make these methods more configurable (e.g. stringifyTime condensed or + * non-condensed, abbreviateTimelengths) + * */ - const utils = window.gl.utils = gl.utils || {}; - const prettyTime = utils.prettyTime = { - /* - * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } - * Seconds can be negative or positive, zero or non-zero. Can be configured for any day - * or week length. - */ - parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) { - const DAYS_PER_WEEK = daysPerWeek; - const HOURS_PER_DAY = hoursPerDay; - const MINUTES_PER_HOUR = 60; - const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; - const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; +/* + * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } + * Seconds can be negative or positive, zero or non-zero. Can be configured for any day + * or week length. +*/ - const timePeriodConstraints = { - weeks: MINUTES_PER_WEEK, - days: MINUTES_PER_DAY, - hours: MINUTES_PER_HOUR, - minutes: 1, - }; +export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) { + const DAYS_PER_WEEK = daysPerWeek; + const HOURS_PER_DAY = hoursPerDay; + const MINUTES_PER_HOUR = 60; + const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; + const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; - let unorderedMinutes = prettyTime.secondsToMinutes(seconds); - - return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { - const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); - - unorderedMinutes -= (periodCount * minutesPerPeriod); - - return periodCount; - }); - }, - - /* - * Accepts a timeObject and returns a condensed string representation of it - * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. - */ - - stringifyTime(timeObject) { - const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { - const isNonZero = !!unitValue; - return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; - }, '').trim(); - return reducedTime.length ? reducedTime : '0m'; - }, - - /* - * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns - * the first non-zero unit/value pair. - */ - - abbreviateTime(timeStr) { - return timeStr.split(' ') - .filter(unitStr => unitStr.charAt(0) !== '0')[0]; - }, - - secondsToMinutes(seconds) { - return Math.abs(seconds / 60); - }, + const timePeriodConstraints = { + weeks: MINUTES_PER_WEEK, + days: MINUTES_PER_DAY, + hours: MINUTES_PER_HOUR, + minutes: 1, }; -})(window.gl || (window.gl = {})); + + let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR); + + return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { + const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); + + unorderedMinutes -= (periodCount * minutesPerPeriod); + + return periodCount; + }); +} + +/* +* Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it +* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. +*/ + +export function stringifyTime(timeObject) { + const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { + const isNonZero = !!unitValue; + return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; + }, '').trim(); + return reducedTime.length ? reducedTime : '0m'; +} + +/* +* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns +* the first non-zero unit/value pair. +*/ + +export function abbreviateTime(timeStr) { + return timeStr.split(' ') + .filter(unitStr => unitStr.charAt(0) !== '0')[0]; +} + diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 0f84470828a..c2a104df749 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -101,7 +101,6 @@ import './label_manager'; import './labels'; import './labels_select'; import './layout_nav'; -import './feature_highlight/feature_highlight_options'; import LazyLoader from './lazy_loader'; import './line_highlighter'; import './logo'; diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index f2eb2338a1e..997550b37fb 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -11,6 +11,7 @@ export default class NewNavSidebar { initDomElements() { this.$page = $('.page-with-sidebar'); this.$sidebar = $('.nav-sidebar'); + this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar); this.$overlay = $('.mobile-overlay'); this.$openSidebar = $('.toggle-mobile-nav'); this.$closeSidebar = $('.close-nav-button'); @@ -55,6 +56,16 @@ export default class NewNavSidebar { this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } NewNavSidebar.setCollapsedCookie(collapsed); + + this.toggleSidebarOverflow(); + } + + toggleSidebarOverflow() { + if (this.$innerScroll.prop('scrollHeight') > this.$innerScroll.prop('offsetHeight')) { + this.$innerScroll.css('overflow-y', 'scroll'); + } else { + this.$innerScroll.css('overflow-y', ''); + } } render() { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index e3daa8cf949..e754f6c4460 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* global Mousetrap */ import Cookies from 'js-cookie'; +import Mousetrap from 'mousetrap'; import findAndFollowLink from './shortcuts_dashboard_navigation'; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js index 0da265053bd..a9fbc7f1a2f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js @@ -1,6 +1,5 @@ import stopwatchSvg from 'icons/_icon_stopwatch.svg'; - -import '../../../lib/utils/pretty_time'; +import { abbreviateTime } from '../../../lib/utils/pretty_time'; export default { name: 'time-tracking-collapsed-state', @@ -79,7 +78,7 @@ export default { }, methods: { abbreviateTime(timeStr) { - return gl.utils.prettyTime.abbreviateTime(timeStr); + return abbreviateTime(timeStr); }, }, template: ` diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js index 40f5c89c5bb..fd0d4570d68 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js @@ -1,6 +1,4 @@ -import '../../../lib/utils/pretty_time'; - -const prettyTime = gl.utils.prettyTime; +import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; export default { name: 'time-tracking-comparison-pane', @@ -23,12 +21,12 @@ export default { }, }, computed: { - parsedRemaining() { + parsedTimeRemaining() { const diffSeconds = this.timeEstimate - this.timeSpent; - return prettyTime.parseSeconds(diffSeconds); + return parseSeconds(diffSeconds); }, timeRemainingHumanReadable() { - return prettyTime.stringifyTime(this.parsedRemaining); + return stringifyTime(this.parsedTimeRemaining); }, timeRemainingTooltip() { const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:'; @@ -44,13 +42,6 @@ export default { timeRemainingStatusClass() { return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; }, - /* Parsed time values */ - parsedEstimate() { - return prettyTime.parseSeconds(this.timeEstimate); - }, - parsedSpent() { - return prettyTime.parseSeconds(this.timeSpent); - }, }, template: `
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js index aaca42e3ebc..219ff94924e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js @@ -72,12 +72,12 @@ export default { + class="btn btn-sm inline"> Check out branch diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js index 6c2e9ba1d30..c79b5c720eb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js @@ -12,6 +12,9 @@ export default { ciIcon, }, computed: { + hasPipeline() { + return this.mr.pipeline && Object.keys(this.mr.pipeline).length > 0; + }, hasCIError() { const { hasCI, ciStatus } = this.mr; @@ -28,7 +31,9 @@ export default { }, }, template: ` -
+
-