diff options
19 files changed, 569 insertions, 87 deletions
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index 2ffe07500e0..7b373a8ce22 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -70,9 +70,9 @@ export default ({ container }) => let durationString = ''; if (navigationEntries.length > 0) { - durationString = `BE ${this.formatMs(navigationEntries[0].responseEnd)} / `; - durationString += `FCP ${this.formatMs(paintEntries[1].startTime)} / `; - durationString += `DOM ${this.formatMs(navigationEntries[0].domContentLoadedEventEnd)}`; + durationString = `${Math.round(navigationEntries[0].responseEnd)} | `; + durationString += `${Math.round(paintEntries[1].startTime)} | `; + durationString += ` ${Math.round(navigationEntries[0].domContentLoadedEventEnd)}`; } let newEntries = resourceEntries.map(this.transformResourceEntry); @@ -105,10 +105,6 @@ export default ({ container }) => size: entry.transferSize ? `${nf.format(entry.transferSize)} bytes` : 'cached', }; }, - formatMs(msValue) { - const nf = new Intl.NumberFormat(); - return `${nf.format(Math.round(msValue))}ms`; - }, }, render(createElement) { return createElement('performance-bar-app', { diff --git a/app/assets/javascripts/releases/list/components/release_block.vue b/app/assets/javascripts/releases/list/components/release_block.vue index 2ae6b1a595f..09ef857ae4a 100644 --- a/app/assets/javascripts/releases/list/components/release_block.vue +++ b/app/assets/javascripts/releases/list/components/release_block.vue @@ -12,6 +12,7 @@ import { scrollToElement } from '~/lib/utils/common_utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import ReleaseBlockFooter from './release_block_footer.vue'; import EvidenceBlock from './evidence_block.vue'; +import ReleaseBlockMilestoneInfo from './release_block_milestone_info.vue'; export default { name: 'ReleaseBlock', @@ -23,6 +24,7 @@ export default { Icon, UserAvatarLink, ReleaseBlockFooter, + ReleaseBlockMilestoneInfo, }, directives: { GlTooltip: GlTooltipDirective, @@ -90,6 +92,12 @@ export default { shouldShowFooter() { return this.glFeatures.releaseIssueSummary; }, + shouldRenderReleaseMetaData() { + return !this.glFeatures.releaseIssueSummary; + }, + shouldRenderMilestoneInfo() { + return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones)); + }, }, mounted() { const hash = getLocationHash(); @@ -106,26 +114,30 @@ export default { </script> <template> <div :id="id" :class="{ 'bg-line-target-blue': isHighlighted }" class="card release-block"> + <div class="card-header d-flex align-items-center bg-white pr-0"> + <h2 class="card-title my-2 mr-auto gl-font-size-20"> + {{ release.name }} + <gl-badge v-if="release.upcoming_release" variant="warning" class="align-middle">{{ + __('Upcoming Release') + }}</gl-badge> + </h2> + <gl-link + v-if="shouldShowEditButton" + v-gl-tooltip + class="btn btn-default append-right-10 js-edit-button ml-2" + :title="__('Edit this release')" + :href="release._links.edit_url" + > + <icon name="pencil" /> + </gl-link> + </div> <div class="card-body"> - <div class="d-flex align-items-start"> - <h2 class="card-title mt-0 mr-auto"> - {{ release.name }} - <gl-badge v-if="release.upcoming_release" variant="warning" class="align-middle">{{ - __('Upcoming Release') - }}</gl-badge> - </h2> - <gl-link - v-if="shouldShowEditButton" - v-gl-tooltip - class="btn btn-default js-edit-button ml-2" - :title="__('Edit this release')" - :href="release._links.edit_url" - > - <icon name="pencil" /> - </gl-link> + <div v-if="shouldRenderMilestoneInfo"> + <release-block-milestone-info :milestones="release.milestones" /> + <hr class="mb-3 mt-0" /> </div> - <div class="card-subtitle d-flex flex-wrap text-secondary"> + <div v-if="shouldRenderReleaseMetaData" class="card-subtitle d-flex flex-wrap text-secondary"> <div class="append-right-8"> <icon name="commit" class="align-middle" /> <gl-link v-if="commitUrl" v-gl-tooltip.bottom :title="commit.title" :href="commitUrl"> diff --git a/app/assets/javascripts/releases/list/components/release_block_milestone_info.vue b/app/assets/javascripts/releases/list/components/release_block_milestone_info.vue new file mode 100644 index 00000000000..d3e354d6157 --- /dev/null +++ b/app/assets/javascripts/releases/list/components/release_block_milestone_info.vue @@ -0,0 +1,136 @@ +<script> +import { GlProgressBar, GlLink, GlBadge, GlButton, GlTooltipDirective } from '@gitlab/ui'; +import { __, n__, sprintf } from '~/locale'; +import { MAX_MILESTONES_TO_DISPLAY } from '../constants'; + +/** Sums the values of an array. For use with Array.reduce. */ +const sumReducer = (acc, curr) => acc + curr; + +export default { + name: 'ReleaseBlockMilestoneInfo', + components: { + GlProgressBar, + GlLink, + GlBadge, + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + milestones: { + type: Array, + required: true, + }, + }, + data() { + return { + showAllMilestones: false, + }; + }, + computed: { + percentCompleteText() { + return sprintf(__('%{percent}%{percentSymbol} complete'), { + percent: this.percentComplete, + percentSymbol: '%', + }); + }, + percentComplete() { + const percent = Math.round((this.closedIssuesCount / this.totalIssuesCount) * 100); + return Number.isNaN(percent) ? 0 : percent; + }, + allIssueStats() { + return this.milestones.map(m => m.issue_stats || {}); + }, + openIssuesCount() { + return this.allIssueStats.map(stats => stats.opened || 0).reduce(sumReducer); + }, + closedIssuesCount() { + return this.allIssueStats.map(stats => stats.closed || 0).reduce(sumReducer); + }, + totalIssuesCount() { + return this.openIssuesCount + this.closedIssuesCount; + }, + milestoneLabelText() { + return n__('Milestone', 'Milestones', this.milestones.length); + }, + issueCountsText() { + return sprintf(__('Open: %{open} • Closed: %{closed}'), { + open: this.openIssuesCount, + closed: this.closedIssuesCount, + }); + }, + milestonesToDisplay() { + return this.showAllMilestones + ? this.milestones + : this.milestones.slice(0, MAX_MILESTONES_TO_DISPLAY); + }, + showMoreLink() { + return this.milestones.length > MAX_MILESTONES_TO_DISPLAY; + }, + moreText() { + return this.showAllMilestones + ? __('show fewer') + : sprintf(__('show %{count} more'), { + count: this.milestones.length - MAX_MILESTONES_TO_DISPLAY, + }); + }, + }, + methods: { + toggleShowAll() { + this.showAllMilestones = !this.showAllMilestones; + }, + shouldRenderBullet(milestoneIndex) { + return Boolean(milestoneIndex !== this.milestonesToDisplay.length - 1 || this.showMoreLink); + }, + shouldRenderShowMoreLink(milestoneIndex) { + return Boolean(milestoneIndex === this.milestonesToDisplay.length - 1 && this.showMoreLink); + }, + }, +}; +</script> +<template> + <div class="release-block-milestone-info d-flex align-items-start flex-wrap"> + <div + v-gl-tooltip + class="milestone-progress-bar-container js-milestone-progress-bar-container d-flex flex-column align-items-start flex-shrink-1 mr-4 mb-3" + :title="__('Closed issues')" + > + <span class="mb-2">{{ percentCompleteText }}</span> + <span class="w-100"> + <gl-progress-bar :value="closedIssuesCount" :max="totalIssuesCount" variant="success" /> + </span> + </div> + <div class="d-flex flex-column align-items-start mr-4 mb-3 js-milestone-list-container"> + <span class="mb-1">{{ milestoneLabelText }}</span> + <div class="d-flex flex-wrap align-items-end"> + <template v-for="(milestone, index) in milestonesToDisplay"> + <gl-link + :key="milestone.id" + v-gl-tooltip + :title="milestone.description" + :href="milestone.web_url" + class="append-right-4" + > + {{ milestone.title }} + </gl-link> + <template v-if="shouldRenderBullet(index)"> + <span :key="'bullet-' + milestone.id" class="append-right-4">•</span> + </template> + <template v-if="shouldRenderShowMoreLink(index)"> + <gl-button :key="'more-button-' + milestone.id" variant="link" @click="toggleShowAll"> + {{ moreText }} + </gl-button> + </template> + </template> + </div> + </div> + <div class="d-flex flex-column align-items-start flex-shrink-0 mr-4 mb-3 js-issues-container"> + <span class="mb-1"> + {{ __('Issues') }} + <gl-badge pill variant="light" class="font-weight-bold">{{ totalIssuesCount }}</gl-badge> + </span> + {{ issueCountsText }} + </div> + </div> +</template> diff --git a/app/assets/javascripts/releases/list/constants.js b/app/assets/javascripts/releases/list/constants.js new file mode 100644 index 00000000000..defcd917465 --- /dev/null +++ b/app/assets/javascripts/releases/list/constants.js @@ -0,0 +1,7 @@ +/* eslint-disable import/prefer-default-export */ +// This eslint-disable ^^^ can be removed when at least +// one more constant is added to this file. Currently +// constants.js files with only a single constant +// are flagged by this rule. + +export const MAX_MILESTONES_TO_DISPLAY = 5; diff --git a/app/assets/stylesheets/components/release_block_milestone_info.scss b/app/assets/stylesheets/components/release_block_milestone_info.scss new file mode 100644 index 00000000000..b6a85ae965a --- /dev/null +++ b/app/assets/stylesheets/components/release_block_milestone_info.scss @@ -0,0 +1,6 @@ +.release-block-milestone-info { + .milestone-progress-bar-container { + width: 300px; + min-height: 46px; + } +} diff --git a/changelogs/unreleased/nfriend-add-release-issue-summary.yml b/changelogs/unreleased/nfriend-add-release-issue-summary.yml new file mode 100644 index 00000000000..0eaad63c107 --- /dev/null +++ b/changelogs/unreleased/nfriend-add-release-issue-summary.yml @@ -0,0 +1,5 @@ +--- +title: Add issue statistics to releases on the Releases page +merge_request: 19448 +author: +type: added diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index f3635613339..9c5a07919b3 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -645,6 +645,7 @@ Settings.rack_attack.git_basic_auth['ip_whitelist'] ||= %w{127.0.0.1} Settings.rack_attack.git_basic_auth['maxretry'] ||= 10 Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour +Settings.rack_attack['admin_area_protected_paths_enabled'] ||= false # # Gitaly diff --git a/config/initializers/rack_attack_new.rb b/config/initializers/rack_attack_new.rb index 92a8bf79432..6d29bb1cd8b 100644 --- a/config/initializers/rack_attack_new.rb +++ b/config/initializers/rack_attack_new.rb @@ -1,11 +1,22 @@ +# Specs for this file can be found on: +# * spec/lib/gitlab/throttle_spec.rb +# * spec/requests/rack_attack_global_spec.rb module Gitlab::Throttle def self.settings Gitlab::CurrentSettings.current_application_settings end + # Returns true if we should use the Admin Area protected paths throttle def self.protected_paths_enabled? - !self.omnibus_protected_paths_present? && - self.settings.throttle_protected_paths_enabled? + return false if should_use_omnibus_protected_paths? + + self.settings.throttle_protected_paths_enabled? + end + + # To be removed in 13.0: https://gitlab.com/gitlab-org/gitlab/issues/29952 + def self.should_use_omnibus_protected_paths? + !Settings.rack_attack.admin_area_protected_paths_enabled && + self.omnibus_protected_paths_present? end def self.omnibus_protected_paths_present? diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png Binary files differindex e876e2f373b..d206d5a4268 100644 --- a/doc/administration/monitoring/performance/img/performance_bar.png +++ b/doc/administration/monitoring/performance/img/performance_bar.png diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md index e65fdfd028d..98c611ea140 100644 --- a/doc/administration/monitoring/performance/performance_bar.md +++ b/doc/administration/monitoring/performance/performance_bar.md @@ -16,10 +16,10 @@ It allows you to see (from left to right): ![Rugged profiling using the Performance Bar](img/performance_bar_rugged_calls.png) - time taken and number of Redis calls; click through for details of these calls ![Redis profiling using the Performance Bar](img/performance_bar_redis_calls.png) -- total load timings of the page; click through for details of these calls - - BE = Backend - Time that the actual base page took to load - - FCP = [First Contentful Paint](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint) - Time until something was visible to the user - - DOM = [DomContentLoaded](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp) Event +- total load timings of the page; click through for details of these calls. Values in the following order: + - Backend - Time that the actual base page took to load + - [First Contentful Paint](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint) - Time until something was visible to the user + - [DomContentLoaded](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp) Event - Number of Requests that the page loaded ![Frontend requests using the Performance Bar](img/performance_bar_frontend.png) - a link to add a request's details to the performance bar; the request can be diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md index 5391f6e5ad6..d4d559c9483 100644 --- a/doc/topics/git/troubleshooting_git.md +++ b/doc/topics/git/troubleshooting_git.md @@ -130,5 +130,25 @@ remote: Calculating new repository size... (cancelled after 729ms) This could be used to further investigate what operation is performing poorly and provide GitLab with more information on how to improve the service. +## `git clone` over HTTP fails with `transfer closed with outstanding read data remaining` error + +If the buffer size is lower than what is allowed in the request, the action will fail with an error similar to the one below: + +```text +error: RPC failed; curl 18 transfer closed with outstanding read data remaining +fatal: The remote end hung up unexpectedly +fatal: early EOF +fatal: index-pack failed +``` + +This can be fixed by increasing the existing `http.postBuffer` value to one greater than the repository size. For example, if `git clone` fails when cloning a 500M repository, the solution will be to set `http.postBuffer` to `524288000` so that the request only starts buffering after the first 524288000 bytes. + +NOTE: **Note:** +The default value of `http.postBuffer`, 1 MiB, is applied if the setting is not configured. + +```sh +git config http.postBuffer 524288000 +``` + [SSH troubleshooting]: ../../ssh/README.md#troubleshooting "SSH Troubleshooting" [Broken-Pipe]: https://stackoverflow.com/questions/19120120/broken-pipe-when-pushing-to-git-repository/36971469#36971469 "StackOverflow: 'Broken pipe when pushing to Git repository'" diff --git a/doc/user/admin_area/settings/protected_paths.md b/doc/user/admin_area/settings/protected_paths.md index 548352f7bfe..5d2548890e3 100644 --- a/doc/user/admin_area/settings/protected_paths.md +++ b/doc/user/admin_area/settings/protected_paths.md @@ -60,18 +60,14 @@ NOTE: **Note:** If Omnibus settings are present, applications settings will be a To migrate from Omnibus GitLab 12.3 and earlier settings: -1. Disable the Protected Paths throttle from Omnibus, by changing `rack_attack_enabled` value to `false` on [`rack_attack.rb.erb`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/rack_attack.rb.erb#L18): - - ```ruby - rack_attack_enabled = false - ``` - 1. Customize and enable your protected paths settings by following [Configure using GitLab UI](#configure-using-gitlab-ui) section. -1. Restart GitLab: +1. SSH into your frontend nodes and add to `/etc/gitlab/gitlab.rb`: - ```bash - sudo gitlab-ctl restart + ```ruby + gitlab_rails['rack_attack_admin_area_protected_paths_enabled'] = true ``` +1. [Reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. + That's it. Protected paths throttle are now managed by GitLab admin settings. diff --git a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml index a6b09e38ce8..878f2acb276 100644 --- a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml @@ -1,6 +1,6 @@ apply: stage: deploy - image: "registry.gitlab.com/gitlab-org/cluster-integration/cluster-applications:v0.1.0" + image: "registry.gitlab.com/gitlab-org/cluster-integration/cluster-applications:v0.2.0" environment: name: production variables: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8d28cf86dd6..5f8c553bc80 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -324,6 +324,9 @@ msgstr "" msgid "%{percent}%% complete" msgstr "" +msgid "%{percent}%{percentSymbol} complete" +msgstr "" + msgid "%{primary} (%{secondary})" msgstr "" @@ -12015,6 +12018,9 @@ msgstr "" msgid "Open source software to collaborate on code" msgstr "" +msgid "Open: %{open} • Closed: %{closed}" +msgstr "" + msgid "Opened" msgstr "" @@ -21517,6 +21523,12 @@ msgstr "" msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}" msgstr "" +msgid "show %{count} more" +msgstr "" + +msgid "show fewer" +msgstr "" + msgid "show less" msgstr "" diff --git a/spec/frontend/releases/list/components/release_block_milestone_info_spec.js b/spec/frontend/releases/list/components/release_block_milestone_info_spec.js new file mode 100644 index 00000000000..c7d1bc3c372 --- /dev/null +++ b/spec/frontend/releases/list/components/release_block_milestone_info_spec.js @@ -0,0 +1,179 @@ +import { mount } from '@vue/test-utils'; +import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui'; +import ReleaseBlockMilestoneInfo from '~/releases/list/components/release_block_milestone_info.vue'; +import { milestones } from '../../mock_data'; +import { trimText } from 'helpers/text_helper'; +import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/list/constants'; + +describe('Release block milestone info', () => { + let wrapper; + let milestonesClone; + + const factory = milestonesProp => { + wrapper = mount(ReleaseBlockMilestoneInfo, { + propsData: { + milestones: milestonesProp, + }, + sync: false, + }); + + return wrapper.vm.$nextTick(); + }; + + beforeEach(() => { + milestonesClone = JSON.parse(JSON.stringify(milestones)); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container'); + const milestoneListContainer = () => wrapper.find('.js-milestone-list-container'); + const issuesContainer = () => wrapper.find('.js-issues-container'); + + describe('with default props', () => { + beforeEach(() => factory(milestonesClone)); + + it('renders the correct percentage', () => { + expect(milestoneProgressBarContainer().text()).toContain('41% complete'); + }); + + it('renders a progress bar that displays the correct percentage', () => { + const progressBar = milestoneProgressBarContainer().find(GlProgressBar); + + expect(progressBar.exists()).toBe(true); + expect(progressBar.attributes()).toEqual( + expect.objectContaining({ + value: '22', + max: '54', + }), + ); + }); + + it('renders a list of links to all associated milestones', () => { + expect(trimText(milestoneListContainer().text())).toContain('Milestones 13.6 • 13.5'); + + milestonesClone.forEach((m, i) => { + const milestoneLink = milestoneListContainer() + .findAll(GlLink) + .at(i); + + expect(milestoneLink.text()).toBe(m.title); + expect(milestoneLink.attributes('href')).toBe(m.web_url); + expect(milestoneLink.attributes('data-original-title')).toBe(m.description); + }); + }); + + it('renders the "Issues" section with a total count of issues associated to the milestone(s)', () => { + const totalIssueCount = 54; + const issuesContainerText = trimText(issuesContainer().text()); + + expect(issuesContainerText).toContain(`Issues ${totalIssueCount}`); + + const badge = issuesContainer().find(GlBadge); + expect(badge.text()).toBe(totalIssueCount.toString()); + + expect(issuesContainerText).toContain('Open: 32 • Closed: 22'); + }); + }); + + describe('with lots of milestones', () => { + let lotsOfMilestones; + let fullListString; + let abbreviatedListString; + + beforeEach(() => { + lotsOfMilestones = []; + const template = milestonesClone[0]; + + for (let i = 0; i < MAX_MILESTONES_TO_DISPLAY + 10; i += 1) { + lotsOfMilestones.push({ + ...template, + id: template.id + i, + iid: template.iid + i, + title: `m-${i}`, + }); + } + + fullListString = lotsOfMilestones.map(m => m.title).join(' • '); + abbreviatedListString = lotsOfMilestones + .slice(0, MAX_MILESTONES_TO_DISPLAY) + .map(m => m.title) + .join(' • '); + + return factory(lotsOfMilestones); + }); + + const clickShowMoreFewerButton = () => { + milestoneListContainer() + .find(GlButton) + .trigger('click'); + + return wrapper.vm.$nextTick(); + }; + + const milestoneListText = () => trimText(milestoneListContainer().text()); + + it('only renders a subset of the milestones', () => { + expect(milestoneListText()).toContain(`Milestones ${abbreviatedListString} • show 10 more`); + }); + + it('renders all milestones when "show more" is clicked', () => + clickShowMoreFewerButton().then(() => { + expect(milestoneListText()).toContain(`Milestones ${fullListString} • show fewer`); + })); + + it('returns to the original view when "show fewer" is clicked', () => + clickShowMoreFewerButton() + .then(clickShowMoreFewerButton) + .then(() => { + expect(milestoneListText()).toContain( + `Milestones ${abbreviatedListString} • show 10 more`, + ); + })); + }); + + const expectAllZeros = () => { + it('displays percentage as 0%', () => { + expect(milestoneProgressBarContainer().text()).toContain('0% complete'); + }); + + it('shows 0 for all issue counts', () => { + const issuesContainerText = trimText(issuesContainer().text()); + + expect(issuesContainerText).toContain('Issues 0 Open: 0 • Closed: 0'); + }); + }; + + /** Ensures we don't have any issues with dividing by zero when computing percentages */ + describe('when all issue counts are zero', () => { + beforeEach(() => { + milestonesClone = milestonesClone.map(m => ({ + ...m, + issue_stats: { + ...m.issue_stats, + opened: 0, + closed: 0, + }, + })); + + return factory(milestonesClone); + }); + + expectAllZeros(); + }); + + describe('if the API response is missing the "issue_stats" property', () => { + beforeEach(() => { + milestonesClone = milestonesClone.map(m => ({ + ...m, + issue_stats: undefined, + })); + + return factory(milestonesClone); + }); + + expectAllZeros(); + }); +}); diff --git a/spec/frontend/releases/list/components/release_block_spec.js b/spec/frontend/releases/list/components/release_block_spec.js index 056835bdaf2..c7a1cbdea17 100644 --- a/spec/frontend/releases/list/components/release_block_spec.js +++ b/spec/frontend/releases/list/components/release_block_spec.js @@ -117,35 +117,6 @@ describe('Release block', () => { }); }); - it('renders the milestone icon', () => { - expect( - milestoneListLabel() - .find(Icon) - .exists(), - ).toBe(true); - }); - - it('renders the label as "Milestones" if more than one milestone is passed in', () => { - expect( - milestoneListLabel() - .find('.js-label-text') - .text(), - ).toEqual('Milestones'); - }); - - it('renders a link to the milestone with a tooltip', () => { - const milestone = first(release.milestones); - const milestoneLink = wrapper.find('.js-milestone-link'); - - expect(milestoneLink.exists()).toBe(true); - - expect(milestoneLink.text()).toBe(milestone.title); - - expect(milestoneLink.attributes('href')).toBe(milestone.web_url); - - expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description); - }); - it('renders the footer', () => { expect(wrapper.find(ReleaseBlockFooter).exists()).toBe(true); }); @@ -187,18 +158,6 @@ describe('Release block', () => { }); }); - it('renders the label as "Milestone" if only a single milestone is passed in', () => { - releaseClone.milestones = releaseClone.milestones.slice(0, 1); - - return factory(releaseClone).then(() => { - expect( - milestoneListLabel() - .find('.js-label-text') - .text(), - ).toEqual('Milestone'); - }); - }); - it('renders upcoming release badge', () => { releaseClone.upcoming_release = true; @@ -281,4 +240,51 @@ describe('Release block', () => { }); }); }); + + describe('when the releaseIssueSummary feature flag is disabled', () => { + describe('with default props', () => { + beforeEach(() => factory(release, { releaseIssueSummary: false })); + + it('renders the milestone icon', () => { + expect( + milestoneListLabel() + .find(Icon) + .exists(), + ).toBe(true); + }); + + it('renders the label as "Milestones" if more than one milestone is passed in', () => { + expect( + milestoneListLabel() + .find('.js-label-text') + .text(), + ).toEqual('Milestones'); + }); + + it('renders a link to the milestone with a tooltip', () => { + const milestone = first(release.milestones); + const milestoneLink = wrapper.find('.js-milestone-link'); + + expect(milestoneLink.exists()).toBe(true); + + expect(milestoneLink.text()).toBe(milestone.title); + + expect(milestoneLink.attributes('href')).toBe(milestone.web_url); + + expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description); + }); + }); + + it('renders the label as "Milestone" if only a single milestone is passed in', () => { + releaseClone.milestones = releaseClone.milestones.slice(0, 1); + + return factory(releaseClone, { releaseIssueSummary: false }).then(() => { + expect( + milestoneListLabel() + .find('.js-label-text') + .text(), + ).toEqual('Milestone'); + }); + }); + }); }); diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js index e1830b494bc..630f14d3a73 100644 --- a/spec/frontend/releases/mock_data.js +++ b/spec/frontend/releases/mock_data.js @@ -11,6 +11,10 @@ export const milestones = [ due_date: '2019-09-19', start_date: '2019-08-31', web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2', + issue_stats: { + opened: 14, + closed: 19, + }, }, { id: 49, @@ -24,6 +28,10 @@ export const milestones = [ due_date: '2019-10-11', start_date: '2019-08-19', web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1', + issue_stats: { + opened: 18, + closed: 3, + }, }, ]; diff --git a/spec/lib/gitlab/throttle_spec.rb b/spec/lib/gitlab/throttle_spec.rb new file mode 100644 index 00000000000..674646a5f06 --- /dev/null +++ b/spec/lib/gitlab/throttle_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Throttle do + describe '.protected_paths_enabled?' do + subject { described_class.protected_paths_enabled? } + + context 'when omnibus protected paths throttle should be used' do + before do + expect(described_class).to receive(:should_use_omnibus_protected_paths?).and_return(true) + end + + it { is_expected.to be_falsey } + end + + context 'when omnibus protected paths throttle should not be used' do + before do + expect(described_class).to receive(:should_use_omnibus_protected_paths?).and_return(false) + end + + it 'returns Application Settings throttle_protected_paths_enabled?' do + expect(Gitlab::CurrentSettings.current_application_settings).to receive(:throttle_protected_paths_enabled?) + + subject + end + end + end + + describe '.should_use_omnibus_protected_paths?' do + subject { described_class.should_use_omnibus_protected_paths? } + + context 'when rack_attack.admin_area_protected_paths_enabled config is unspecified' do + context 'when the omnibus protected paths throttle has been recently used (it has data)' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(true) + end + + it { is_expected.to be_truthy } + end + + context 'when the omnibus protected paths throttle has not been recently used' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(false) + end + + it { is_expected.to be_falsey } + end + end + + context 'when rack_attack.admin_area_protected_paths_enabled config is false' do + before do + stub_config(rack_attack: { + admin_area_protected_paths_enabled: false + }) + end + + context 'when the omnibus protected paths throttle has been recently used (it has data)' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(true) + end + + it { is_expected.to be_truthy } + end + + context 'when the omnibus protected paths throttle has not been recently used' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(false) + end + + it { is_expected.to be_falsey } + end + end + + context 'when rack_attack.admin_area_protected_paths_enabled config is true' do + before do + stub_config(rack_attack: { + admin_area_protected_paths_enabled: true + }) + + expect(described_class).not_to receive(:omnibus_protected_paths_present?) + end + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 4d5055a7e27..59113687d1c 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -249,10 +249,10 @@ describe 'Rack Attack global throttles' do expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params } end - context 'when Omnibus throttle is present' do + context 'when Omnibus throttle should be used' do before do allow(Gitlab::Throttle) - .to receive(:omnibus_protected_paths_present?).and_return(true) + .to receive(:should_use_omnibus_protected_paths?).and_return(true) end it 'allows requests over the rate limit' do @@ -298,7 +298,7 @@ describe 'Rack Attack global throttles' do it_behaves_like 'rate-limited token-authenticated requests' end - context 'when Omnibus throttle is present' do + context 'when Omnibus throttle should be used' do let(:request_args) { [api(api_partial_url, personal_access_token: token)] } let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token)] } @@ -309,7 +309,7 @@ describe 'Rack Attack global throttles' do stub_application_setting(settings_to_set) allow(Gitlab::Throttle) - .to receive(:omnibus_protected_paths_present?).and_return(true) + .to receive(:should_use_omnibus_protected_paths?).and_return(true) end it 'allows requests over the rate limit' do @@ -339,7 +339,7 @@ describe 'Rack Attack global throttles' do it_behaves_like 'rate-limited web authenticated requests' - context 'when Omnibus throttle is present' do + context 'when Omnibus throttle should be used' do before do settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds @@ -347,7 +347,7 @@ describe 'Rack Attack global throttles' do stub_application_setting(settings_to_set) allow(Gitlab::Throttle) - .to receive(:omnibus_protected_paths_present?).and_return(true) + .to receive(:should_use_omnibus_protected_paths?).and_return(true) login_as(user) end |