diff options
67 files changed, 539 insertions, 322 deletions
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index cf4a70e321e..64f258aed64 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -300,7 +300,7 @@ GitLabDropdown = (function() { return function(data) { _this.fullData = data; _this.parseData(_this.fullData); - _this.focusTextInput(true); + _this.focusTextInput(); if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') { return _this.filter.input.trigger('input'); } @@ -790,24 +790,16 @@ GitLabDropdown = (function() { return [selectedObject, isMarking]; }; - GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) { + GitLabDropdown.prototype.focusTextInput = function() { if (this.options.filterable) { - this.dropdown.one('transitionend', () => { - const initialScrollTop = $(window).scrollTop(); + const initialScrollTop = $(window).scrollTop(); - if (this.dropdown.is('.open')) { - this.filterInput.focus(); - } - - if ($(window).scrollTop() < initialScrollTop) { - $(window).scrollTop(initialScrollTop); - } - }); + if (this.dropdown.is('.open')) { + this.filterInput.focus(); + } - if (triggerFocus) { - // This triggers after a ajax request - // in case of slow requests, the dropdown transition could already be finished - this.dropdown.trigger('transitionend'); + if ($(window).scrollTop() < initialScrollTop) { + $(window).scrollTop(initialScrollTop); } } }; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index 743c049e9fb..151a4ce012c 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -84,9 +84,12 @@ export default (function() { return _.each(author_commits, (function(_this) { return function(d) { _this.redraw_author_commit_info(d); - $(_this.authors[d.author_name].list_item).appendTo("ol"); - _this.authors[d.author_name].set_data(d.dates); - return _this.authors[d.author_name].redraw(); + if (_this.authors[d.author_name] != null) { + $(_this.authors[d.author_name].list_item).appendTo("ol"); + _this.authors[d.author_name].set_data(d.dates); + return _this.authors[d.author_name].redraw(); + } + return ''; }; })(this)); }; @@ -108,10 +111,14 @@ export default (function() { }; ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) { - var author_commit_info, author_list_item; - author_list_item = $(this.authors[author.author_name].list_item); - author_commit_info = this.format_author_commit_info(author); - return author_list_item.find("span").html(author_commit_info); + var author_commit_info, author_list_item, $author; + $author = this.authors[author.author_name]; + if ($author != null) { + author_list_item = $(this.authors[author.author_name].list_item); + author_commit_info = this.format_author_commit_info(author); + return author_list_item.find("span").html(author_commit_info); + } + return ''; }; return ContributorsStatGraph; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 187f3c008e8..9a4012232a0 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -1,8 +1,16 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ import _ from 'underscore'; -import d3 from 'd3'; +import { extent, max } from 'd3-array'; +import { select, event as d3Event } from 'd3-selection'; +import { scaleTime, scaleLinear } from 'd3-scale'; +import { axisLeft, axisBottom } from 'd3-axis'; +import { area } from 'd3-shape'; +import { brushX } from 'd3-brush'; +import { timeParse } from 'd3-time-format'; import { dateTickFormat } from '../lib/utils/tick_formats'; +const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse }; + const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; const hasProp = {}.hasOwnProperty; @@ -71,8 +79,8 @@ export const ContributorsGraph = (function() { }; ContributorsGraph.prototype.create_scale = function(width, height) { - this.x = d3.time.scale().range([0, width]).clamp(true); - return this.y = d3.scale.linear().range([height, 0]).nice(); + this.x = d3.scaleTime().range([0, width]).clamp(true); + return this.y = d3.scaleLinear().range([height, 0]).nice(); }; ContributorsGraph.prototype.draw_x_axis = function() { @@ -124,7 +132,7 @@ export const ContributorsMasterGraph = (function(superClass) { ContributorsMasterGraph.prototype.parse_dates = function(data) { var parseDate; - parseDate = d3.time.format("%Y-%m-%d").parse; + parseDate = d3.timeParse("%Y-%m-%d"); return data.forEach(function(d) { return d.date = parseDate(d.date); }); @@ -135,11 +143,10 @@ export const ContributorsMasterGraph = (function(superClass) { }; ContributorsMasterGraph.prototype.create_axes = function() { - this.x_axis = d3.svg.axis() + this.x_axis = d3.axisBottom() .scale(this.x) - .orient('bottom') .tickFormat(dateTickFormat); - return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5); + return this.y_axis = d3.axisLeft().scale(this.y).ticks(5); }; ContributorsMasterGraph.prototype.create_svg = function() { @@ -147,16 +154,16 @@ export const ContributorsMasterGraph = (function(superClass) { }; ContributorsMasterGraph.prototype.create_area = function(x, y) { - return this.area = d3.svg.area().x(function(d) { + return this.area = d3.area().x(function(d) { return x(d.date); }).y0(this.height).y1(function(d) { d.commits = d.commits || d.additions || d.deletions; return y(d.commits); - }).interpolate("basis"); + }); }; ContributorsMasterGraph.prototype.create_brush = function() { - return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content); + return this.brush = d3.brushX(this.x).extent([[this.x.range()[0], 0], [this.x.range()[1], this.height]]).on("end", this.update_content); }; ContributorsMasterGraph.prototype.draw_path = function(data) { @@ -168,7 +175,12 @@ export const ContributorsMasterGraph = (function(superClass) { }; ContributorsMasterGraph.prototype.update_content = function() { - ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent()); + // d3Event.selection replaces the function brush.empty() calls + if (d3Event.selection != null) { + ContributorsGraph.set_x_domain(d3Event.selection.map(this.x.invert)); + } else { + ContributorsGraph.set_x_domain(this.x_max_domain); + } return $("#brush_change").trigger('change'); }; @@ -226,18 +238,17 @@ export const ContributorsAuthorGraph = (function(superClass) { }; ContributorsAuthorGraph.prototype.create_axes = function() { - this.x_axis = d3.svg.axis() + this.x_axis = d3.axisBottom() .scale(this.x) - .orient('bottom') .ticks(8) .tickFormat(dateTickFormat); - return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5); + return this.y_axis = d3.axisLeft().scale(this.y).ticks(5); }; ContributorsAuthorGraph.prototype.create_area = function(x, y) { - return this.area = d3.svg.area().x(function(d) { + return this.area = d3.area().x(function(d) { var parseDate; - parseDate = d3.time.format("%Y-%m-%d").parse; + parseDate = d3.timeParse("%Y-%m-%d"); return x(parseDate(d)); }).y0(this.height).y1((function(_this) { return function(d) { @@ -247,11 +258,12 @@ export const ContributorsAuthorGraph = (function(superClass) { return y(0); } }; - })(this)).interpolate("basis"); + })(this)); }; ContributorsAuthorGraph.prototype.create_svg = function() { - this.list_item = d3.selectAll(".person")[0].pop(); + var persons = document.querySelectorAll('.person'); + this.list_item = persons[persons.length - 1]; return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")"); }; diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index a6f82b247e2..ab3cc29146a 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,59 +1,51 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */ -import _ from 'underscore'; -import Cookies from 'js-cookie'; import ContextualSidebar from './contextual_sidebar'; import initFlyOutNav from './fly_out_nav'; -(function() { - var hideEndFade; +function hideEndFade($scrollingTabs) { + $scrollingTabs.each(function scrollTabsLoop() { + const $this = $(this); + $this.siblings('.fade-right').toggleClass('scrolling', $this.width() < $this.prop('scrollWidth')); + }); +} - hideEndFade = function($scrollingTabs) { - return $scrollingTabs.each(function() { - var $this; - $this = $(this); - return $this.siblings('.fade-right').toggleClass('scrolling', $this.width() < $this.prop('scrollWidth')); - }); - }; +export default function initLayoutNav() { + const contextualSidebar = new ContextualSidebar(); + contextualSidebar.bindEvents(); + + initFlyOutNav(); $(document).on('init.scrolling-tabs', () => { const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized'); $scrollingTabs.addClass('is-initialized'); - hideEndFade($scrollingTabs); - $(window).off('resize.nav').on('resize.nav', function() { - return hideEndFade($scrollingTabs); - }); - $scrollingTabs.off('scroll').on('scroll', function(event) { - var $this, currentPosition, maxPosition; - $this = $(this); - currentPosition = $this.scrollLeft(); - maxPosition = $this.prop('scrollWidth') - $this.outerWidth(); + $(window).on('resize.nav', () => { + hideEndFade($scrollingTabs); + }).trigger('resize.nav'); + + $scrollingTabs.on('scroll', function tabsScrollEvent() { + const $this = $(this); + const currentPosition = $this.scrollLeft(); + const maxPosition = $this.prop('scrollWidth') - $this.outerWidth(); + $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); - return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); + $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); }); - $scrollingTabs.each(function () { - var $this = $(this); - var scrollingTabWidth = $this.width(); - var $active = $this.find('.active'); - var activeWidth = $active.width(); + $scrollingTabs.each(function scrollTabsEachLoop() { + const $this = $(this); + const scrollingTabWidth = $this.width(); + const $active = $this.find('.active'); + const activeWidth = $active.width(); if ($active.length) { - var offset = $active.offset().left + activeWidth; + const offset = $active.offset().left + activeWidth; if (offset > scrollingTabWidth - 30) { - var scrollLeft = scrollingTabWidth / 2; - scrollLeft = (offset - scrollLeft) - (activeWidth / 2); + const scrollLeft = (offset - (scrollingTabWidth / 2)) - (activeWidth / 2); + $this.scrollLeft(scrollLeft); } } }); - }); - - $(() => { - const contextualSidebar = new ContextualSidebar(); - contextualSidebar.bindEvents(); - - initFlyOutNav(); - }); -}).call(window); + }).trigger('init.scrolling-tabs'); +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 15a3a91c5f5..59bfa482bb0 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -41,7 +41,7 @@ import Flash, { removeFlashClickListener } from './flash'; import './gl_dropdown'; import initTodoToggle from './header'; import initImporterStatus from './importer_status'; -import './layout_nav'; +import initLayoutNav from './layout_nav'; import LazyLoader from './lazy_loader'; import './line_highlighter'; import initLogoAnimation from './logo'; @@ -89,6 +89,7 @@ $(function () { var fitSidebarForSize; initBreadcrumbs(); + initLayoutNav(); initImporterStatus(); initTodoToggle(); initLogoAnimation(); @@ -261,8 +262,6 @@ $(function () { renderTimeago(); - $(document).trigger('init.scrolling-tabs'); - $('form.filter-form').on('submit', function (event) { const link = document.createElement('a'); link.href = this.action; diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index cdae287658b..eede04a06cd 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -1,5 +1,8 @@ <script> - import d3 from 'd3'; + import { scaleLinear, scaleTime } from 'd3-scale'; + import { axisLeft, axisBottom } from 'd3-axis'; + import { max, extent } from 'd3-array'; + import { select } from 'd3-selection'; import GraphLegend from './graph/legend.vue'; import GraphFlag from './graph/flag.vue'; import GraphDeployment from './graph/deployment.vue'; @@ -7,10 +10,12 @@ import MonitoringMixin from '../mixins/monitoring_mixins'; import eventHub from '../event_hub'; import measurements from '../utils/measurements'; - import { timeScaleFormat, bisectDate } from '../utils/date_time_formatters'; + import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters'; import createTimeSeries from '../utils/multiple_time_series'; import bp from '../../breakpoints'; + const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }; + export default { props: { graphData: { @@ -156,25 +161,22 @@ this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; } - const axisXScale = d3.time.scale() + const axisXScale = d3.scaleTime() .range([0, this.graphWidth - 70]); - const axisYScale = d3.scale.linear() + const axisYScale = d3.scaleLinear() .range([this.graphHeight - this.graphHeightOffset, 0]); const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []); axisXScale.domain(d3.extent(allValues, d => d.time)); axisYScale.domain([0, d3.max(allValues.map(d => d.value))]); - const xAxis = d3.svg.axis() + const xAxis = d3.axisBottom() .scale(axisXScale) - .ticks(d3.time.minute, 60) - .tickFormat(timeScaleFormat) - .orient('bottom'); + .tickFormat(timeScaleFormat); - const yAxis = d3.svg.axis() + const yAxis = d3.axisLeft() .scale(axisYScale) - .ticks(measurements.yTicks) - .orient('left'); + .ticks(measurements.yTicks); d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis); diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index ad07a8465e2..48bdec1e030 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -1,17 +1,32 @@ -import d3 from 'd3'; +import { timeFormat as time } from 'd3-time-format'; +import { timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3-time'; +import { bisector } from 'd3-array'; -export const dateFormat = d3.time.format('%b %-d, %Y'); -export const dateFormatWithName = d3.time.format('%a, %b %-d'); -export const timeFormat = d3.time.format('%-I:%M%p'); +const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear }; + +export const dateFormat = d3.time('%b %-d, %Y'); +export const timeFormat = d3.time('%-I:%M%p'); +export const dateFormatWithName = d3.time('%a, %b %-d'); export const bisectDate = d3.bisector(d => d.time).left; -export const timeScaleFormat = d3.time.format.multi([ - ['.%L', d => d.getMilliseconds()], - [':%S', d => d.getSeconds()], - ['%-I:%M', d => d.getMinutes()], - ['%-I %p', d => d.getHours()], - ['%a %-d', d => d.getDay() && d.getDate() !== 1], - ['%b %-d', d => d.getDate() !== 1], - ['%B', d => d.getMonth()], - ['%Y', () => true], -]); +export function timeScaleFormat(date) { + let formatFunction; + if (d3.timeSecond(date) < date) { + formatFunction = d3.time('.%L'); + } else if (d3.timeMinute(date) < date) { + formatFunction = d3.time(':%S'); + } else if (d3.timeHour(date) < date) { + formatFunction = d3.time('%-I:%M'); + } else if (d3.timeDay(date) < date) { + formatFunction = d3.time('%-I %p'); + } else if (d3.timeWeek(date) < date) { + formatFunction = d3.time('%a %d'); + } else if (d3.timeMonth(date) < date) { + formatFunction = d3.time('%b %d'); + } else if (d3.timeYear(date) < date) { + formatFunction = d3.time('%B'); + } else { + formatFunction = d3.time('%Y'); + } + return formatFunction(date); +} diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index d21a265bd43..4ce3dad440c 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -1,5 +1,10 @@ -import d3 from 'd3'; import _ from 'underscore'; +import { scaleLinear, scaleTime } from 'd3-scale'; +import { line, area, curveLinear } from 'd3-shape'; +import { extent, max } from 'd3-array'; +import { timeMinute } from 'd3-time'; + +const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute }; const defaultColorPalette = { blue: ['#1f78d1', '#8fbce8'], @@ -38,27 +43,27 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom let lineColor = ''; let areaColor = ''; - const timeSeriesScaleX = d3.time.scale() + const timeSeriesScaleX = d3.scaleTime() .range([0, graphWidth - 70]); - const timeSeriesScaleY = d3.scale.linear() + const timeSeriesScaleY = d3.scaleLinear() .range([graphHeight - graphHeightOffset, 0]); timeSeriesScaleX.domain(xDom); - timeSeriesScaleX.ticks(d3.time.minute, 60); + timeSeriesScaleX.ticks(d3.timeMinute, 60); timeSeriesScaleY.domain(yDom); const defined = d => !isNaN(d.value) && d.value != null; - const lineFunction = d3.svg.line() + const lineFunction = d3.line() .defined(defined) - .interpolate('linear') + .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate .x(d => timeSeriesScaleX(d.time)) .y(d => timeSeriesScaleY(d.value)); - const areaFunction = d3.svg.area() + const areaFunction = d3.area() .defined(defined) - .interpolate('linear') + .curve(d3.curveLinear) .x(d => timeSeriesScaleX(d.time)) .y0(graphHeight - graphHeightOffset) .y1(d => timeSeriesScaleY(d.value)); diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js index 4fa8c680580..0581239d5a5 100644 --- a/app/assets/javascripts/users/activity_calendar.js +++ b/app/assets/javascripts/users/activity_calendar.js @@ -1,7 +1,10 @@ import _ from 'underscore'; -import d3 from 'd3'; +import { scaleLinear, scaleThreshold } from 'd3-scale'; +import { select } from 'd3-selection'; import { getDayName, getDayDifference } from '../lib/utils/datetime_utility'; +const d3 = { select, scaleLinear, scaleThreshold }; + const LOADING_HTML = ` <div class="text-center"> <i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i> @@ -28,7 +31,7 @@ function formatTooltipText({ date, count }) { return `${contribText}<br />${dateDayName} ${dateText}`; } -const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); +const initColorKey = () => d3.scaleLinear().range(['#acd5f2', '#254e77']).domain([0, 3]); export default class ActivityCalendar { constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) { @@ -205,7 +208,7 @@ export default class ActivityCalendar { initColor() { const colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; - return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange); + return d3.scaleThreshold().domain([0, 10, 20, 30]).range(colorRange); } clickDay(stamp) { diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss index 8baf7ca23a4..2e417315ed7 100644 --- a/app/assets/stylesheets/framework/contextual-sidebar.scss +++ b/app/assets/stylesheets/framework/contextual-sidebar.scss @@ -9,12 +9,6 @@ padding-left: $contextual-sidebar-width; } - // Override position: absolute - .right-sidebar { - position: fixed; - height: calc(100% - #{$header-height}); - } - .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { padding: 10px 0 15px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 478269f3fcf..bc907a390d8 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -16,27 +16,18 @@ @mixin set-visible { transform: translateY(0); - visibility: visible; - opacity: 1; - transition-duration: 100ms, 150ms, 25ms; - transition-delay: 35ms, 50ms, 25ms; + display: block; } @mixin set-invisible { transform: translateY(-10px); - visibility: hidden; - opacity: 0; - transition-property: opacity, transform, visibility; - transition-duration: 70ms, 250ms, 250ms; - transition-timing-function: linear, $dropdown-animation-timing; - transition-delay: 25ms, 50ms, 0ms; + display: none; } .open { .dropdown-menu, .dropdown-menu-nav { @include set-visible; - display: block; min-height: 40px; @media (max-width: $screen-xs-max) { @@ -55,6 +46,11 @@ } } +// Get search dropdown to line up with other nav dropdowns +.search-input-container .dropdown-menu { + margin-top: 11px; +} + .dropdown-toggle { padding: 6px 8px 6px 10px; background-color: $white-light; @@ -214,7 +210,6 @@ .dropdown-menu, .dropdown-menu-nav { @include set-invisible; - display: block; position: absolute; width: auto; top: 100%; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 0742c0a2a09..d61809cb0a4 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -90,11 +90,6 @@ .right-sidebar { border-left: 1px solid $border-color; height: calc(100% - #{$header-height}); - - &.affix { - position: fixed; - top: $header-height; - } } .with-performance-bar .right-sidebar.affix { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index e19196e0c41..e1637618ab2 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -122,7 +122,7 @@ } .right-sidebar { - position: absolute; + position: fixed; top: $header-height; bottom: 0; right: 0; @@ -502,7 +502,7 @@ top: $header-height + $performance-bar-height; .issuable-sidebar { - height: calc(100% - #{$header-height} - #{$performance-bar-height}); + height: calc(100% - #{$performance-bar-height}); } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 49c8e546bf2..c9363188505 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -108,13 +108,6 @@ input[type="checkbox"]:hover { // Custom dropdown positioning .dropdown-menu { - transition-property: opacity, transform; - transition-duration: 250ms, 250ms; - transition-delay: 0ms, 25ms; - transition-timing-function: $dropdown-animation-timing; - transform: translateY(0); - opacity: 0; - display: block; left: -5px; } @@ -152,13 +145,6 @@ input[type="checkbox"]:hover { background-color: $nav-badge-bg; border-color: $border-color; } - - .dropdown-menu { - transition-duration: 100ms, 75ms; - transition-delay: 75ms, 100ms; - transform: translateY(7px); - opacity: 1; - } } &.has-value { diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index cede147d559..8e2c42c1bd3 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -10,7 +10,6 @@ } .axis { - fill: $stat-graph-axis-fill; font-size: 10px; } @@ -54,9 +53,7 @@ } .selection rect { - fill: $stat-graph-selection-fill; fill-opacity: 0.1; - stroke: $stat-graph-selection-stroke; stroke-width: 1px; stroke-opacity: 0.4; shape-rendering: crispedges; diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index cde1e284d2d..86bade49ec9 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -8,12 +8,12 @@ class AutocompleteController < ApplicationController def users @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute - render json: @users, only: [:name, :username, :id], methods: [:avatar_url] + render json: UserSerializer.new.represent(@users) end def user @user = User.find(params[:id]) - render json: @user, only: [:name, :username, :id], methods: [:avatar_url] + render json: UserSerializer.new.represent(@user) end def projects diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb index b5dece38de1..e26ce6da030 100644 --- a/app/helpers/form_helper.rb +++ b/app/helpers/form_helper.rb @@ -35,7 +35,7 @@ module FormHelper multi_select: true, 'input-meta': 'name', 'always-show-selectbox': true, - current_user_info: current_user.to_json(only: [:id, :name]) + current_user_info: UserSerializer.new.represent(current_user) } } end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index b4ca0e68e0b..2668cf78afe 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -362,7 +362,7 @@ module IssuablesHelper moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable), projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id), editable: can_edit_issuable, - currentUser: current_user.as_json(only: [:username, :id, :name], methods: :avatar_url), + currentUser: UserSerializer.new.represent(current_user), rootPath: root_path, fullPath: @project.full_path } diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 2f57660516d..0f9ac958f95 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -139,7 +139,7 @@ module SearchHelper id: "filtered-search-#{type}", placeholder: 'Search or filter results...', data: { - 'username-params' => @users.to_json(only: [:id, :username]) + 'username-params' => UserSerializer.new.represent(@users) }, autocomplete: 'off' } diff --git a/app/models/concerns/blocks_json_serialization.rb b/app/models/concerns/blocks_json_serialization.rb new file mode 100644 index 00000000000..8019e6adc1c --- /dev/null +++ b/app/models/concerns/blocks_json_serialization.rb @@ -0,0 +1,16 @@ +# Overrides `as_json` and `to_json` to raise an exception when called in order +# to prevent accidentally exposing attributes +# +# Not that that would ever happen... but just in case. +module BlocksJsonSerialization + extend ActiveSupport::Concern + + JsonSerializationError = Class.new(StandardError) + + def to_json(*) + raise JsonSerializationError, + "JSON serialization has been disabled on #{self.class.name}" + end + + alias_method :as_json, :to_json +end diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 89fe6527647..5911b56c34c 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -24,7 +24,7 @@ module TimeTrackable # rubocop:disable Gitlab/ModuleWithInstanceVariables def spend_time(options) @time_spent = options[:duration] - @time_spent_user = options[:user] + @time_spent_user = User.find(options[:user_id]) @spent_at = options[:spent_at] @original_total_time_spent = nil diff --git a/app/models/project.rb b/app/models/project.rb index 5183a216c53..3440c01b356 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1148,7 +1148,7 @@ class Project < ActiveRecord::Base def change_head(branch) if repository.branch_exists?(branch) repository.before_change_head - repository.write_ref('HEAD', "refs/heads/#{branch}", force: true) + repository.write_ref('HEAD', "refs/heads/#{branch}") repository.copy_gitattributes(branch) repository.after_change_head reload_default_branch diff --git a/app/models/repository.rb b/app/models/repository.rb index 387428d90a6..a34f5e5439b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -19,7 +19,6 @@ class Repository attr_accessor :full_path, :disk_path, :project, :is_wiki delegate :ref_name_for_sha, to: :raw_repository - delegate :write_ref, to: :raw_repository CreateTreeError = Class.new(StandardError) @@ -256,10 +255,11 @@ class Repository # This will still fail if the file is corrupted (e.g. 0 bytes) begin - write_ref(keep_around_ref_name(sha), sha, force: true) - rescue Gitlab::Git::Repository::GitError => ex - # Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156 - return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/ + write_ref(keep_around_ref_name(sha), sha) + rescue Rugged::ReferenceError => ex + Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" + rescue Rugged::OSError => ex + raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/ Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" end @@ -269,6 +269,10 @@ class Repository ref_exists?(keep_around_ref_name(sha)) end + def write_ref(ref_path, sha) + rugged.references.create(ref_path, sha, force: true) + end + def diverging_commit_counts(branch) root_ref_hash = raw_repository.commit(root_ref).id cache.fetch(:"diverging_commit_counts_#{branch.name}") do @@ -1015,7 +1019,7 @@ class Repository end def create_ref(ref, ref_path) - write_ref(ref_path, ref) + raw_repository.write_ref(ref_path, ref) end def ls_files(ref) diff --git a/app/models/user.rb b/app/models/user.rb index 51941f43919..b52f17cd6a8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,6 +18,7 @@ class User < ActiveRecord::Base include CreatedAtFilterable include IgnorableColumn include BulkMemberAccessLoad + include BlocksJsonSerialization DEFAULT_NOTIFICATION_LEVEL = :participating diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index c499f384426..842fe4e09c4 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -5,7 +5,7 @@ module Projects if fork_source = @project.fork_source fork_source.lfs_objects.find_each do |lfs_object| - lfs_object.projects << @project + lfs_object.projects << @project unless lfs_object.projects.include?(@project) end refresh_forks_count(fork_source) diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 06ac86cd5a9..669c1ba0a22 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -405,7 +405,7 @@ module QuickActions if time_spent @updates[:spend_time] = { duration: time_spent, - user: current_user, + user_id: current_user.id, spent_at: time_spent_date } end @@ -428,7 +428,7 @@ module QuickActions current_user.can?(:"admin_#{issuable.to_ability_name}", project) end command :remove_time_spent do - @updates[:spend_time] = { duration: :reset, user: current_user } + @updates[:spend_time] = { duration: :reset, user_id: current_user.id } end desc "Append the comment with #{SHRUG}" diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml index b3f73e96b81..8e5e32e9f16 100644 --- a/app/views/shared/boards/components/_sidebar.html.haml +++ b/app/views/shared/boards/components/_sidebar.html.haml @@ -1,5 +1,4 @@ -%board-sidebar{ "inline-template" => true, - ":current-user" => "#{current_user ? current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) : {}}" } +%board-sidebar{ "inline-template" => true, ":current-user" => (UserSerializer.new.represent(current_user) || {}).to_json } %transition{ name: "boards-sidebar-slide" } %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" } .issuable-sidebar diff --git a/app/workers/concerns/project_import_options.rb b/app/workers/concerns/project_import_options.rb new file mode 100644 index 00000000000..10b971344f7 --- /dev/null +++ b/app/workers/concerns/project_import_options.rb @@ -0,0 +1,23 @@ +module ProjectImportOptions + extend ActiveSupport::Concern + + included do + IMPORT_RETRY_COUNT = 5 + + sidekiq_options retry: IMPORT_RETRY_COUNT, status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION + + # We only want to mark the project as failed once we exhausted all retries + sidekiq_retries_exhausted do |job| + project = Project.find(job['args'].first) + + action = if project.forked? + "fork" + else + "import" + end + + project.mark_import_as_failed("Every #{action} attempt has failed: #{job['error_message']}. Please try again.") + Sidekiq.logger.warn "Failed #{job['class']} with #{job['args']}: #{job['error_message']}" + end + end +end diff --git a/app/workers/concerns/project_start_import.rb b/app/workers/concerns/project_start_import.rb index 0704ebbb0fd..4e55a1ee3d6 100644 --- a/app/workers/concerns/project_start_import.rb +++ b/app/workers/concerns/project_start_import.rb @@ -1,3 +1,4 @@ +# Used in EE by mirroring module ProjectStartImport def start(project) if project.import_started? && project.import_jid == self.jid diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index a07ef1705a1..d1c57b82681 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -1,11 +1,8 @@ class RepositoryForkWorker - ForkError = Class.new(StandardError) - include ApplicationWorker include Gitlab::ShellAdapter include ProjectStartImport - - sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION + include ProjectImportOptions def perform(project_id, forked_from_repository_storage_path, source_disk_path) project = Project.find(project_id) @@ -18,20 +15,12 @@ class RepositoryForkWorker result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_disk_path, project.repository_storage_path, project.disk_path) - raise ForkError, "Unable to fork project #{project_id} for repository #{source_disk_path} -> #{project.disk_path}" unless result + raise "Unable to fork project #{project_id} for repository #{source_disk_path} -> #{project.disk_path}" unless result project.repository.after_import - raise ForkError, "Project #{project_id} had an invalid repository after fork" unless project.valid_repo? + raise "Project #{project_id} had an invalid repository after fork" unless project.valid_repo? project.import_finish - rescue ForkError => ex - fail_fork(project, ex.message) - raise - rescue => ex - return unless project - - fail_fork(project, ex.message) - raise ForkError, "#{ex.class} #{ex.message}" end private @@ -42,9 +31,4 @@ class RepositoryForkWorker Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.") false end - - def fail_fork(project, message) - Rails.logger.error(message) - project.mark_import_as_failed(message) - end end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 55715c83cb1..31e2798c36b 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -1,11 +1,8 @@ class RepositoryImportWorker - ImportError = Class.new(StandardError) - include ApplicationWorker include ExceptionBacktrace include ProjectStartImport - - sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION + include ProjectImportOptions def perform(project_id) project = Project.find(project_id) @@ -23,17 +20,9 @@ class RepositoryImportWorker # to those importers to mark the import process as complete. return if service.async? - raise ImportError, result[:message] if result[:status] == :error + raise result[:message] if result[:status] == :error project.after_import - rescue ImportError => ex - fail_import(project, ex.message) - raise - rescue => ex - return unless project - - fail_import(project, ex.message) - raise ImportError, "#{ex.class} #{ex.message}" end private @@ -44,8 +33,4 @@ class RepositoryImportWorker Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") false end - - def fail_import(project, message) - project.mark_import_as_failed(message) - end end diff --git a/changelogs/unreleased/38318-search-merge-requests-with-api.yml b/changelogs/unreleased/38318-search-merge-requests-with-api.yml new file mode 100644 index 00000000000..d8b2f1f25c8 --- /dev/null +++ b/changelogs/unreleased/38318-search-merge-requests-with-api.yml @@ -0,0 +1,5 @@ +--- +title: Add optional search param for Merge Requests API +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml b/changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml new file mode 100644 index 00000000000..ce238a2c79f --- /dev/null +++ b/changelogs/unreleased/39246-fork-and-import-jobs-should-only-be-marked-as-failed-when-the-number-of-retries-was-exhausted.yml @@ -0,0 +1,5 @@ +--- +title: Only mark import and fork jobs as failed once all Sidekiq retries get exhausted +merge_request: 15844 +author: +type: changed diff --git a/changelogs/unreleased/bvl-fix-unlinking-with-lfs-objects.yml b/changelogs/unreleased/bvl-fix-unlinking-with-lfs-objects.yml new file mode 100644 index 00000000000..058d686e74c --- /dev/null +++ b/changelogs/unreleased/bvl-fix-unlinking-with-lfs-objects.yml @@ -0,0 +1,6 @@ +--- +title: Don't link LFS objects to a project when unlinking forks when they were already + linked +merge_request: 16006 +author: +type: fixed diff --git a/changelogs/unreleased/zj-empty-repo-importer.yml b/changelogs/unreleased/zj-empty-repo-importer.yml new file mode 100644 index 00000000000..71d50af9a04 --- /dev/null +++ b/changelogs/unreleased/zj-empty-repo-importer.yml @@ -0,0 +1,5 @@ +--- +title: Fix GitHub importer using removed interface +merge_request: +author: +type: fixed diff --git a/config/webpack.config.js b/config/webpack.config.js index f02fcda827a..d8797bbf4d3 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -32,7 +32,6 @@ var config = { boards: './boards/boards_bundle.js', common: './commons/index.js', common_vue: './vue_shared/vue_resource_interceptor.js', - common_d3: ['d3'], cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js', commit_pipelines: './commit/pipelines/pipelines_bundle.js', deploy_keys: './deploy_keys/index.js', @@ -225,6 +224,9 @@ var config = { 'monitoring', 'users', ], + minChunks: function (module, count) { + return module.resource && /d3-/.test(module.resource); + }, }), // create cacheable common library bundles diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 880b0ed2c65..4d3592e8f71 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -47,6 +47,7 @@ Parameters: | `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | +| `search` | string | no | Search merge requests against their `title` and `description` | ```json [ @@ -161,6 +162,7 @@ Parameters: | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | +| `search` | string | no | Search merge requests against their `title` and `description` | ```json [ diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 5f943ba27d1..b29c5848aef 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -8,7 +8,7 @@ module API helpers do def find_issues(args = {}) - args = params.merge(args) + args = declared_params.merge(args) args.delete(:id) args[:milestone_title] = args.delete(:milestone) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d34886fca2e..02f2b75ab9d 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -8,7 +8,7 @@ module API helpers do def find_merge_requests(args = {}) - args = params.merge(args) + args = declared_params.merge(args) args[:milestone_title] = args.delete(:milestone) args[:label_name] = args.delete(:labels) @@ -41,6 +41,7 @@ module API optional :scope, type: String, values: %w[created-by-me assigned-to-me all], desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' + optional :search, type: String, desc: 'Search merge requests for text present in the title or description' use :pagination end end diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb index df4632346dd..2bb451dea89 100644 --- a/lib/api/time_tracking_endpoints.rb +++ b/lib/api/time_tracking_endpoints.rb @@ -85,7 +85,7 @@ module API update_issuable(spend_time: { duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)), - user: current_user + user_id: current_user.id }) end @@ -97,7 +97,7 @@ module API authorize! update_issuable_key, load_issuable status :ok - update_issuable(spend_time: { duration: :reset, user: current_user }) + update_issuable(spend_time: { duration: :reset, user_id: current_user.id }) end desc "Show time stats for a project #{issuable_name}" diff --git a/lib/api/v3/time_tracking_endpoints.rb b/lib/api/v3/time_tracking_endpoints.rb index d5b90e435ba..1aad39815f9 100644 --- a/lib/api/v3/time_tracking_endpoints.rb +++ b/lib/api/v3/time_tracking_endpoints.rb @@ -86,7 +86,7 @@ module API update_issuable(spend_time: { duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)), - user: current_user + user_id: current_user.id }) end @@ -98,7 +98,7 @@ module API authorize! update_issuable_key, load_issuable status :ok - update_issuable(spend_time: { duration: :reset, user: current_user }) + update_issuable(spend_time: { duration: :reset, user_id: current_user.id }) end desc "Show time stats for a project #{issuable_name}" diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 848a782446a..044c60caa05 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1101,17 +1101,12 @@ module Gitlab end end - def write_ref(ref_path, ref, force: false) + def write_ref(ref_path, ref) raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ') raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00") - ref = "refs/heads/#{ref}" unless ref.start_with?("refs") || ref =~ /\A[a-f0-9]+\z/i - - rugged.references.create(ref_path, ref, force: force) - rescue Rugged::ReferenceError => ex - raise GitError, "could not create ref #{ref_path}: #{ex}" - rescue Rugged::OSError => ex - raise GitError, "could not create ref #{ref_path}: #{ex}" + input = "update #{ref_path}\x00#{ref}\x00\x00" + run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) } end def fetch_ref(source_repository, source_ref:, target_ref:) diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb index 9cf2e7fd871..7dd68a0d1cd 100644 --- a/lib/gitlab/github_import/importer/repository_importer.rb +++ b/lib/gitlab/github_import/importer/repository_importer.rb @@ -29,7 +29,7 @@ module Gitlab # this code, e.g. because we had to retry this job after # `import_wiki?` raised a rate limit error. In this case we'll skip # re-importing the main repository. - if project.repository.empty_repo? + if project.empty_repo? import_repository else true diff --git a/package.json b/package.json index 9e816e007ee..a5bf2309a0f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,14 @@ "core-js": "^2.4.1", "cropper": "^2.3.0", "css-loader": "^0.28.0", - "d3": "^3.5.11", + "d3-array": "^1.2.1", + "d3-axis": "^1.0.8", + "d3-brush": "^1.0.4", + "d3-scale": "^1.0.7", + "d3-selection": "^1.2.0", + "d3-shape": "^1.2.0", + "d3-time": "^1.0.8", + "d3-time-format": "^2.1.1", "deckar01-task_list": "^2.0.0", "diff": "^3.4.0", "document-register-element": "1.3.0", diff --git a/qa/README.md b/qa/README.md index 1cfbbdd9d42..7f2dd39ff63 100644 --- a/qa/README.md +++ b/qa/README.md @@ -33,7 +33,14 @@ You can also supply specific tests to run as another parameter. For example, to test the EE license specs, you can run: ``` -EE_LICENSE="<YOUR LICENSE KEY>" bin/qa Test::Instance http://localhost qa/ee +EE_LICENSE="<YOUR LICENSE KEY>" bin/qa Test::Instance http://localhost qa/specs/features/ee +``` + +Since the arguments would be passed to `rspec`, you could use all `rspec` +options there. For example, passing `--backtrace` and also line number: + +``` +bin/qa Test::Instance http://localhost qa/specs/features/login/standard_spec.rb:3 --backtrace ``` ### Overriding the authenticated user diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index a9530becb65..70faf28e09d 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -12,7 +12,7 @@ feature 'Contributions Calendar', :js do issue_params = { title: issue_title } def get_cell_color_selector(contributions) - activity_colors = %w[#ededed #acd5f2 #7fa8c9 #527ba0 #254e77] + activity_colors = ["#ededed", "rgb(172, 213, 242)", "rgb(127, 168, 201)", "rgb(82, 123, 160)", "rgb(37, 78, 119)"] # We currently don't actually test the cases with contributions >= 20 activity_colors_index = if contributions > 0 && contributions < 10 diff --git a/spec/features/merge_requests/image_diff_notes_spec.rb b/spec/features/merge_requests/image_diff_notes_spec.rb index ddc73437917..b53570835cb 100644 --- a/spec/features/merge_requests/image_diff_notes_spec.rb +++ b/spec/features/merge_requests/image_diff_notes_spec.rb @@ -12,7 +12,8 @@ feature 'image diff notes', :js do # Stub helper to return any blob file as image from public app folder. # This is necessary to run this specs since we don't display repo images in capybara. - allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png') + allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png') + allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.ico') end context 'create commit diff notes' do diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 27efc32c95b..9f24193a2ac 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -82,9 +82,9 @@ feature 'Milestone' do milestone = create(:milestone, project: project, title: 8.7) issue1 = create(:issue, project: project, milestone: milestone) issue2 = create(:issue, project: project, milestone: milestone) - issue1.spend_time(duration: 3600, user: user) + issue1.spend_time(duration: 3600, user_id: user.id) issue1.save! - issue2.spend_time(duration: 7200, user: user) + issue2.spend_time(duration: 7200, user_id: user.id) issue2.save! visit project_milestone_path(project, milestone) diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index 861f26e162f..6599839a526 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,8 +1,10 @@ /* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */ - -import d3 from 'd3'; +import { scaleLinear, scaleTime } from 'd3-scale'; +import { timeParse } from 'd3-time-format'; import { ContributorsGraph, ContributorsMasterGraph } from '~/graphs/stat_graph_contributors_graph'; +const d3 = { scaleLinear, scaleTime, timeParse }; + describe("ContributorsGraph", function () { describe("#set_x_domain", function () { it("set the x_domain", function () { @@ -53,7 +55,7 @@ describe("ContributorsGraph", function () { it("sets the instance's x domain using the prototype's x_domain", function () { ContributorsGraph.prototype.x_domain = 20; var instance = new ContributorsGraph(); - instance.x = d3.time.scale().range([0, 100]).clamp(true); + instance.x = d3.scaleTime().range([0, 100]).clamp(true); spyOn(instance.x, 'domain'); instance.set_x_domain(); expect(instance.x.domain).toHaveBeenCalledWith(20); @@ -64,7 +66,7 @@ describe("ContributorsGraph", function () { it("sets the instance's y domain using the prototype's y_domain", function () { ContributorsGraph.prototype.y_domain = 30; var instance = new ContributorsGraph(); - instance.y = d3.scale.linear().range([100, 0]).nice(); + instance.y = d3.scaleLinear().range([100, 0]).nice(); spyOn(instance.y, 'domain'); instance.set_y_domain(); expect(instance.y.domain).toHaveBeenCalledWith(30); @@ -118,7 +120,7 @@ describe("ContributorsMasterGraph", function () { describe("#parse_dates", function () { it("parses the dates", function () { var graph = new ContributorsMasterGraph(); - var parseDate = d3.time.format("%Y-%m-%d").parse; + var parseDate = d3.timeParse("%Y-%m-%d"); var data = [{ date: "2013-01-01" }, { date: "2012-12-15" }]; var correct = [{ date: parseDate(data[0].date) }, { date: parseDate(data[1].date) }]; graph.parse_dates(data); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 050f0ea9ebd..a6be474805b 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -290,15 +290,18 @@ import 'vendor/jquery.scrollTo'; $('body').removeAttr('data-page'); }); - it('requires an absolute pathname', function () { - spyOn($, 'ajax').and.callFake(function (options) { - expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json'); + it('triggers Ajax request to JSON endpoint', function (done) { + const url = '/foo/bar/merge_requests/1/diffs'; + spyOn(this.class, 'ajaxGet').and.callFake((options) => { + expect(options.url).toEqual(`${url}.json`); + done(); }); - this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); + this.class.loadDiff(url); }); - it('triggers scroll event when diff already loaded', function () { + it('triggers scroll event when diff already loaded', function (done) { + spyOn(this.class, 'ajaxGet').and.callFake(() => done.fail()); spyOn(document, 'dispatchEvent'); this.class.diffsLoaded = true; @@ -307,6 +310,7 @@ import 'vendor/jquery.scrollTo'; expect( document.dispatchEvent, ).toHaveBeenCalledWith(new CustomEvent('scroll')); + done(); }); describe('with inline diff', () => { diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 168e5d07504..46a57e08963 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -70,7 +70,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do describe '#execute' do it 'imports the repository and wiki' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(true) @@ -93,7 +93,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do end it 'does not import the repository if it already exists' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(false) @@ -115,7 +115,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do end it 'does not import the wiki if it is disabled' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(true) @@ -137,7 +137,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do end it 'does not import the wiki if the repository could not be imported' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(true) diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb new file mode 100644 index 00000000000..5906b588d0e --- /dev/null +++ b/spec/models/concerns/blocks_json_serialization_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe BlocksJsonSerialization do + DummyModel = Class.new do + include BlocksJsonSerialization + end + + it 'blocks as_json' do + expect { DummyModel.new.as_json } + .to raise_error(described_class::JsonSerializationError, /DummyModel/) + end + + it 'blocks to_json' do + expect { DummyModel.new.to_json } + .to raise_error(described_class::JsonSerializationError, /DummyModel/) + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 9df26f06a11..4b217df2e8f 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -291,7 +291,7 @@ describe Issuable do context 'total_time_spent is updated' do before do - issue.spend_time(duration: 2, user: user, spent_at: Time.now) + issue.spend_time(duration: 2, user_id: user.id, spent_at: Time.now) issue.save expect(Gitlab::HookData::IssuableBuilder) .to receive(:new).with(issue).and_return(builder) @@ -485,7 +485,7 @@ describe Issuable do let(:issue) { create(:issue) } def spend_time(seconds) - issue.spend_time(duration: seconds, user: user) + issue.spend_time(duration: seconds, user_id: user.id) issue.save! end diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 9048da0c73d..673c609f534 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -189,9 +189,9 @@ describe Milestone, 'Milestoneish' do describe '#total_issue_time_spent' do it 'calculates total issue time spent' do - closed_issue_1.spend_time(duration: 300, user: author) + closed_issue_1.spend_time(duration: 300, user_id: author.id) closed_issue_1.save! - closed_issue_2.spend_time(duration: 600, user: assignee) + closed_issue_2.spend_time(duration: 600, user_id: assignee.id) closed_issue_2.save! expect(milestone.total_issue_time_spent).to eq(900) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f805f2dcddb..cbeac2f05d3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1863,11 +1863,10 @@ describe Project do project.change_head(project.default_branch) end - it 'creates the new reference' do - expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', + it 'creates the new reference with rugged' do + expect(project.repository.rugged.references).to receive(:create).with('HEAD', "refs/heads/#{project.default_branch}", force: true) - project.change_head(project.default_branch) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 1d7069feebd..9a68ae086ea 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1979,23 +1979,6 @@ describe Repository do File.delete(path) end - - it "attempting to call keep_around when exists a lock does not fail" do - ref = repository.send(:keep_around_ref_name, sample_commit.id) - path = File.join(repository.path, ref) - lock_path = "#{path}.lock" - - FileUtils.mkdir_p(File.dirname(path)) - File.open(lock_path, 'w') { |f| f.write('') } - - begin - expect { repository.keep_around(sample_commit.id) }.not_to raise_error(Gitlab::Git::Repository::GitError) - - expect(File.exist?(lock_path)).to be_falsey - ensure - File.delete(path) - end - end end describe '#update_ref' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4687d9dfa00..e58e7588df0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -12,6 +12,7 @@ describe User do it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(TokenAuthenticatable) } + it { is_expected.to include_module(BlocksJsonSerialization) } end describe 'delegations' do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 91616da6d9a..60dbd74d59d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -150,6 +150,26 @@ describe API::MergeRequests do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(merge_request3.id) end + + context 'search params' do + before do + merge_request.update(title: 'Search title', description: 'Search description') + end + + it 'returns merge requests matching given search string for title' do + get api("/merge_requests", user), search: merge_request.title + + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request.id) + end + + it 'returns merge requests for project matching given search string for description' do + get api("/merge_requests", user), project_id: project.id, search: merge_request.description + + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request.id) + end + end end end diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 2bba71fef4f..3ec6139bfa6 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -62,6 +62,26 @@ describe Projects::UnlinkForkService do expect(source.forks_count).to be_zero end + context 'when the source has LFS objects' do + let(:lfs_object) { create(:lfs_object) } + + before do + lfs_object.projects << project + end + + it 'links the fork to the lfs object before unlinking' do + subject.execute + + expect(lfs_object.projects).to include(forked_project) + end + + it 'does not fail if the lfs objects were already linked' do + lfs_object.projects << forked_project + + expect { subject.execute }.not_to raise_error + end + end + context 'when the original project was deleted' do it 'does not fail when the original project is deleted' do source = forked_project.forked_from_project diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index c35177f6ebc..eb46480fa54 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -209,7 +209,7 @@ describe QuickActions::InterpretService do expect(updates).to eq(spend_time: { duration: 3600, - user: developer, + user_id: developer.id, spent_at: DateTime.now.to_date }) end @@ -221,7 +221,7 @@ describe QuickActions::InterpretService do expect(updates).to eq(spend_time: { duration: -1800, - user: developer, + user_id: developer.id, spent_at: DateTime.now.to_date }) end @@ -233,7 +233,7 @@ describe QuickActions::InterpretService do expect(updates).to eq(spend_time: { duration: 1800, - user: developer, + user_id: developer.id, spent_at: Date.parse(date) }) end @@ -267,7 +267,7 @@ describe QuickActions::InterpretService do it 'populates spend_time: :reset if content contains /remove_time_spent' do _, updates = service.execute(content, issuable) - expect(updates).to eq(spend_time: { duration: :reset, user: developer }) + expect(updates).to eq(spend_time: { duration: :reset, user_id: developer.id }) end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 47412110b4b..9025589ae0b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -927,7 +927,7 @@ describe SystemNoteService do # We need a custom noteable in order to the shared examples to be green. let(:noteable) do mr = create(:merge_request, source_project: project) - mr.spend_time(duration: 360000, user: author) + mr.spend_time(duration: 360000, user_id: author.id) mr.save! mr end @@ -965,7 +965,7 @@ describe SystemNoteService do end def spend_time!(seconds) - noteable.spend_time(duration: seconds, user: author) + noteable.spend_time(duration: seconds, user_id: author.id) noteable.save! end end diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb index af1083f4bfd..dd3089d22e5 100644 --- a/spec/support/api/time_tracking_shared_examples.rb +++ b/spec/support/api/time_tracking_shared_examples.rb @@ -79,7 +79,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| context 'when subtracting time' do it 'subtracts time of the total spent time' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1h' @@ -91,7 +91,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| context 'when time to subtract is greater than the total spent time' do it 'does not modify the total time spent' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1w' @@ -119,7 +119,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do it "returns the time stats for #{issuable_name}" do - issuable.update_attributes!(spend_time: { duration: 1800, user: user }, + issuable.update_attributes!(spend_time: { duration: 1800, user_id: user.id }, time_estimate: 3600) get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user) diff --git a/spec/support/api/v3/time_tracking_shared_examples.rb b/spec/support/api/v3/time_tracking_shared_examples.rb index afe0f4cecda..f27a2d06c83 100644 --- a/spec/support/api/v3/time_tracking_shared_examples.rb +++ b/spec/support/api/v3/time_tracking_shared_examples.rb @@ -75,7 +75,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| context 'when subtracting time' do it 'subtracts time of the total spent time' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1h' @@ -87,7 +87,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| context 'when time to subtract is greater than the total spent time' do it 'does not modify the total time spent' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1w' @@ -115,7 +115,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do it "returns the time stats for #{issuable_name}" do - issuable.update_attributes!(spend_time: { duration: 1800, user: user }, + issuable.update_attributes!(spend_time: { duration: 1800, user_id: user.id }, time_estimate: 3600) get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user) diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb new file mode 100644 index 00000000000..b6c111df8b9 --- /dev/null +++ b/spec/workers/concerns/project_import_options_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe ProjectImportOptions do + let(:project) { create(:project, :import_started) } + let(:job) { { 'args' => [project.id, nil, nil], 'jid' => '123' } } + let(:worker_class) do + Class.new do + include Sidekiq::Worker + include ProjectImportOptions + end + end + + it 'sets default retry limit' do + expect(worker_class.sidekiq_options['retry']).to eq(ProjectImportOptions::IMPORT_RETRY_COUNT) + end + + it 'sets default status expiration' do + expect(worker_class.sidekiq_options['status_expiration']).to eq(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + end + + describe '.sidekiq_retries_exhausted' do + it 'marks fork as failed' do + expect { worker_class.sidekiq_retries_exhausted_block.call(job) }.to change { project.reload.import_status }.from("started").to("failed") + end + + it 'logs the appropriate error message for forked projects' do + allow_any_instance_of(Project).to receive(:forked?).and_return(true) + + worker_class.sidekiq_retries_exhausted_block.call(job) + + expect(project.reload.import_error).to include("fork") + end + + it 'logs the appropriate error message for forked projects' do + worker_class.sidekiq_retries_exhausted_block.call(job) + + expect(project.reload.import_error).to include("import") + end + end +end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 74c85848b7e..31598586f59 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -1,17 +1,21 @@ require 'spec_helper' describe RepositoryForkWorker do - let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) } - let(:shell) { Gitlab::Shell.new } - - subject { described_class.new } - - before do - allow(subject).to receive(:gitlab_shell).and_return(shell) + describe 'modules' do + it 'includes ProjectImportOptions' do + expect(described_class).to include_module(ProjectImportOptions) + end end describe "#perform" do + let(:project) { create(:project, :repository) } + let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) } + let(:shell) { Gitlab::Shell.new } + + before do + allow(subject).to receive(:gitlab_shell).and_return(shell) + end + def perform! subject.perform(fork_project.id, '/test/path', project.disk_path) end @@ -60,14 +64,7 @@ describe RepositoryForkWorker do expect_fork_repository.and_return(false) - expect { perform! }.to raise_error(RepositoryForkWorker::ForkError, error_message) - end - - it 'handles unexpected error' do - expect_fork_repository.and_raise(RuntimeError) - - expect { perform! }.to raise_error(RepositoryForkWorker::ForkError) - expect(fork_project.reload.import_status).to eq('failed') + expect { perform! }.to raise_error(StandardError, error_message) end end end diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 0af537647ad..85ac14eb347 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -1,11 +1,15 @@ require 'spec_helper' describe RepositoryImportWorker do - let(:project) { create(:project, :import_scheduled) } - - subject { described_class.new } + describe 'modules' do + it 'includes ProjectImportOptions' do + expect(described_class).to include_module(ProjectImportOptions) + end + end describe '#perform' do + let(:project) { create(:project, :import_scheduled) } + context 'when worker was reset without cleanup' do let(:jid) { '12345678' } let(:started_project) { create(:project, :import_started, import_jid: jid) } @@ -44,22 +48,11 @@ describe RepositoryImportWorker do expect do subject.perform(project.id) - end.to raise_error(RepositoryImportWorker::ImportError, error) + end.to raise_error(StandardError, error) expect(project.reload.import_jid).not_to be_nil end end - context 'with unexpected error' do - it 'marks import as failed' do - allow_any_instance_of(Projects::ImportService).to receive(:execute).and_raise(RuntimeError) - - expect do - subject.perform(project.id) - end.to raise_error(RepositoryImportWorker::ImportError) - expect(project.reload.import_status).to eq('failed') - end - end - context 'when using an asynchronous importer' do it 'does not mark the import process as finished' do service = double(:service) diff --git a/yarn.lock b/yarn.lock index c4d1bd3c682..55d0d33c9f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1704,14 +1704,112 @@ custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" +d3-array@^1.2.0, d3-array@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" + +d3-axis@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa" + +d3-brush@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + +d3-collection@1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" + +d3-color@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" + +d3-dispatch@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" + +d3-drag@1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d" + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-ease@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e" + +d3-format@1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f" + +d3-interpolate@1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" + +d3-scale@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-color "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + +d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88" + +d3-shape@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" + dependencies: + d3-path "1" + +d3-time-format@2, d3-time-format@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" + dependencies: + d3-time "1" + +d3-time@1, d3-time@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" + +d3-timer@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" + +d3-transition@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + d3@3.5.17: version "3.5.17" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" -d3@^3.5.11: - version "3.5.11" - resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.11.tgz#d130750eed0554db70e8432102f920a12407b69c" - d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" |