diff --git a/app/assets/javascripts/contributors/components/contributors.vue b/app/assets/javascripts/contributors/components/contributors.vue index 9b834793428..78d17a619fb 100644 --- a/app/assets/javascripts/contributors/components/contributors.vue +++ b/app/assets/javascripts/contributors/components/contributors.vue @@ -11,11 +11,14 @@ import { __ } from '~/locale'; import RefSelector from '~/ref/components/ref_selector.vue'; import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants'; import { xAxisLabelFormatter, dateFormatter } from '../utils'; +import { MASTER_CHART_HEIGHT } from '../constants'; import ContributorAreaChart from './contributor_area_chart.vue'; +import IndividualChart from './individual_chart.vue'; const GRAPHS_PATH_REGEX = /^(.*?)\/-\/graphs/g; export default { + MASTER_CHART_HEIGHT, i18n: { history: __('History'), refSelectorTranslations: { @@ -27,6 +30,7 @@ export default { GlButton, GlLoadingIcon, ContributorAreaChart, + IndividualChart, RefSelector, }, props: { @@ -52,9 +56,8 @@ export default { return { masterChart: null, individualCharts: [], + individualChartZoom: {}, svgs: {}, - masterChartHeight: 264, - individualChartHeight: 216, selectedBranch: this.branch, }; }, @@ -195,23 +198,13 @@ export default { }); }) .catch(() => {}); - this.masterChart.on('datazoom', debounce(this.setIndividualChartsZoom, 200)); - }, - onIndividualChartCreated(chart) { - this.individualCharts.push(chart); - }, - setIndividualChartsZoom(options) { - this.charts.forEach((chart) => - chart.setOption( - { - dataZoom: { - start: options.start, - end: options.end, - show: false, - }, - }, - { lazyUpdate: true }, - ), + + this.masterChart.on( + 'datazoom', + debounce(() => { + const [{ startValue, endValue }] = this.masterChart.getOption().dataZoom; + this.individualChartZoom = { startValue, endValue }; + }, 200), ); }, visitBranch(selected) { @@ -230,7 +223,7 @@ export default { diff --git a/app/assets/javascripts/contributors/components/individual_chart.vue b/app/assets/javascripts/contributors/components/individual_chart.vue new file mode 100644 index 00000000000..1d23273fd02 --- /dev/null +++ b/app/assets/javascripts/contributors/components/individual_chart.vue @@ -0,0 +1,86 @@ + + + diff --git a/app/assets/javascripts/contributors/constants.js b/app/assets/javascripts/contributors/constants.js new file mode 100644 index 00000000000..1c641c83e97 --- /dev/null +++ b/app/assets/javascripts/contributors/constants.js @@ -0,0 +1,2 @@ +export const MASTER_CHART_HEIGHT = 264; +export const INDIVIDUAL_CHART_HEIGHT = 216; diff --git a/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js b/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js index 9bb2884e065..73cb64519aa 100644 --- a/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js @@ -667,6 +667,17 @@ export const isInFuture = (date) => */ export const fallsBefore = (dateA, dateB) => differenceInMilliseconds(dateA, dateB) > 0; +/** + * Checks whether date falls in the `start -> end` time period. + * + * @param {Date} date + * @param {Date} start + * @param {Date} end + * @return {Boolean} Returns true if date falls in the time period, otherwise false + */ +export const isInTimePeriod = (date, start, end) => + differenceInMilliseconds(start, date) >= 0 && differenceInMilliseconds(date, end) >= 0; + /** * Removes the time component of the date. * diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index 13bba06d425..abc72018c82 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -72,7 +72,7 @@ export default class ActivityCalendar { this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; this.daySpace = 1; - this.daySize = 15; + this.daySize = 14; this.daySizeWithSpace = this.daySize + this.daySpace * 2; this.monthNames = [ __('Jan'), @@ -131,7 +131,6 @@ export default class ActivityCalendar { this.renderDays(); this.renderMonths(); this.renderDayTitles(); - this.renderKey(); } // Add extra padding for the last month label if it is also the last column @@ -153,7 +152,7 @@ export default class ActivityCalendar { .select(container) .append('svg') .attr('width', width) - .attr('height', 169) + .attr('height', 140) .attr('class', 'contrib-calendar') .attr('data-testid', 'contrib-calendar'); } @@ -257,25 +256,6 @@ export default class ActivityCalendar { .text((date) => this.monthNames[date.month]); } - renderKey() { - this.svg - .append('g') - .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) - .selectAll('rect') - .data(CONTRIB_LEGENDS) - .enter() - .append('rect') - .attr('width', this.daySize) - .attr('height', this.daySize) - .attr('x', (_, i) => this.daySizeWithSpace * i) - .attr('y', 0) - .attr('data-level', (_, i) => i) - .attr('class', 'user-contrib-cell has-tooltip contrib-legend') - .attr('title', (x) => x.title) - .attr('data-container', 'body') - .attr('data-html', true); - } - clickDay(stamp) { if (this.currentSelectedDate !== stamp.date) { this.currentSelectedDate = stamp.date; diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index f9e22808b0d..35d7edad96b 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -1,8 +1,8 @@ // TODO: Remove this with the removal of the old navigation. // See https://gitlab.com/gitlab-org/gitlab/-/issues/435899. -import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import $ from 'jquery'; +import initReadMore from '~/read_more'; import Activities from '~/activities'; import AjaxCache from '~/lib/utils/ajax_cache'; import axios from '~/lib/utils/axios_utils'; @@ -66,18 +66,35 @@ import UserOverviewBlock from './user_overview_block'; const CALENDAR_TEMPLATE = `
-
-
+
+
+
+ + + + + + + + + +
+
+
`; -const CALENDAR_PERIOD_6_MONTHS = 6; const CALENDAR_PERIOD_12_MONTHS = 12; -/* computation based on - * width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); - * (see activity_calendar.js) - */ -const OVERVIEW_CALENDAR_BREAKPOINT = 918; export default class UserTabs { constructor({ defaultAction, action, parentEl }) { @@ -105,12 +122,6 @@ export default class UserTabs { .off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event)) .on('click', '.gl-pagination a', (event) => this.changeProjectsPage(event)); - - window.addEventListener('resize', () => this.onResize()); - } - - onResize() { - this.loadActivityCalendar(); } changeProjectsPage(e) { @@ -194,19 +205,25 @@ export default class UserTabs { return; } + initReadMore(); + this.loadActivityCalendar(); UserTabs.renderMostRecentBlocks('#js-overview .activities-block', { requestParams: { limit: 15 }, }); + UserTabs.renderMostRecentBlocks('#js-overview .projects-block', { - requestParams: { limit: 10, skip_pagination: true, skip_namespace: true, compact_mode: true }, + requestParams: { limit: 3, skip_pagination: true, skip_namespace: true, card_mode: true }, }); this.loaded.overview = true; } static renderMostRecentBlocks(container, options) { + if ($(container).length === 0) { + return; + } // eslint-disable-next-line no-new new UserOverviewBlock({ container, @@ -218,8 +235,6 @@ export default class UserTabs { loadActivityCalendar() { const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar'); - if (!$calendarWrap.length || bp.getBreakpointSize() === 'xs') return; - const calendarPath = $calendarWrap.data('calendarPath'); AjaxCache.retrieve(calendarPath) @@ -240,7 +255,6 @@ export default class UserTabs { } static renderActivityCalendar(data, $calendarWrap) { - const monthsAgo = UserTabs.getVisibleCalendarPeriod($calendarWrap); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); const utcOffset = $calendarWrap.data('utcOffset'); const calendarHint = __('Issues, merge requests, pushes, and comments.'); @@ -257,8 +271,12 @@ export default class UserTabs { calendarActivitiesPath, utcOffset, firstDayOfWeek: gon.first_day_of_week, - monthsAgo, + CALENDAR_PERIOD_12_MONTHS, }); + + // Scroll to end + const calendarContainer = document.querySelector('.js-contrib-calendar'); + calendarContainer.scrollLeft = calendarContainer.scrollWidth; } toggleLoading(status) { @@ -282,11 +300,4 @@ export default class UserTabs { getCurrentAction() { return this.$parentEl.find('.nav-links a.active').data('action'); } - - static getVisibleCalendarPeriod($calendarWrap) { - const width = $calendarWrap.width(); - return width < OVERVIEW_CALENDAR_BREAKPOINT - ? CALENDAR_PERIOD_6_MONTHS - : CALENDAR_PERIOD_12_MONTHS; - } } diff --git a/app/assets/javascripts/profile/components/activity_calendar.vue b/app/assets/javascripts/profile/components/activity_calendar.vue index d359b478d35..b9668210f79 100644 --- a/app/assets/javascripts/profile/components/activity_calendar.vue +++ b/app/assets/javascripts/profile/components/activity_calendar.vue @@ -1,12 +1,8 @@ diff --git a/app/assets/javascripts/profile/components/overview_tab.vue b/app/assets/javascripts/profile/components/overview_tab.vue index 8cfa3fb3eea..ab8a2871a41 100644 --- a/app/assets/javascripts/profile/components/overview_tab.vue +++ b/app/assets/javascripts/profile/components/overview_tab.vue @@ -54,19 +54,19 @@ export default {