Commit daff9d36 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 911d55e6 ca790a62
<script> <script>
import { GlTable, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlTable, GlIcon, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { trackAlertIntegrationsViewsOptions } from '../constants'; import { trackAlertIntegrationsViewsOptions } from '../constants';
...@@ -27,6 +27,7 @@ export default { ...@@ -27,6 +27,7 @@ export default {
components: { components: {
GlTable, GlTable,
GlIcon, GlIcon,
GlLoadingIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -37,10 +38,15 @@ export default { ...@@ -37,10 +38,15 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
loading: {
type: Boolean,
required: false,
default: false,
},
}, },
fields: [ fields: [
{ {
key: 'activated', key: 'active',
label: __('Status'), label: __('Status'),
}, },
{ {
...@@ -78,12 +84,13 @@ export default { ...@@ -78,12 +84,13 @@ export default {
:empty-text="$options.i18n.emptyState" :empty-text="$options.i18n.emptyState"
:items="integrations" :items="integrations"
:fields="$options.fields" :fields="$options.fields"
:busy="loading"
stacked="md" stacked="md"
:tbody-tr-class="tbodyTrClass" :tbody-tr-class="tbodyTrClass"
show-empty show-empty
> >
<template #cell(activated)="{ item }"> <template #cell(active)="{ item }">
<span v-if="item.activated" data-testid="integration-activated-status"> <span v-if="item.active" data-testid="integration-activated-status">
<gl-icon <gl-icon
v-gl-tooltip v-gl-tooltip
name="check-circle-filled" name="check-circle-filled"
...@@ -104,6 +111,10 @@ export default { ...@@ -104,6 +111,10 @@ export default {
{{ $options.i18n.status.disabled.name }} {{ $options.i18n.status.disabled.name }}
</span> </span>
</template> </template>
<template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>
</gl-table> </gl-table>
</div> </div>
</template> </template>
<script> <script>
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import IntegrationsList from './alerts_integrations_list.vue'; import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue'; import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue'; import SettingsFormNew from './alerts_settings_form_new.vue';
...@@ -19,19 +21,52 @@ export default { ...@@ -19,19 +21,52 @@ export default {
prometheus: { prometheus: {
default: {}, default: {},
}, },
projectPath: {
default: '',
},
},
apollo: {
integrations: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getIntegrationsQuery,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
const { alertManagementIntegrations: { nodes: list = [] } = {} } = data.project || {};
return {
list,
};
},
error() {
this.errored = true;
},
},
},
data() {
return {
errored: false,
integrations: {},
};
}, },
computed: { computed: {
integrations() { loading() {
return this.$apollo.queries.integrations.loading;
},
intergrationsOptionsOld() {
return [ return [
{ {
name: s__('AlertSettings|HTTP endpoint'), name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'), type: s__('AlertsIntegrations|HTTP endpoint'),
activated: this.generic.activated, active: this.generic.activated,
}, },
{ {
name: s__('AlertSettings|External Prometheus'), name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'), type: s__('AlertsIntegrations|Prometheus'),
activated: this.prometheus.activated, active: this.prometheus.activated,
}, },
]; ];
}, },
...@@ -41,7 +76,10 @@ export default { ...@@ -41,7 +76,10 @@ export default {
<template> <template>
<div> <div>
<integrations-list :integrations="integrations" /> <integrations-list
:integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld"
:loading="loading"
/>
<settings-form-new v-if="glFeatures.httpIntegrationsList" /> <settings-form-new v-if="glFeatures.httpIntegrationsList" />
<settings-form-old v-else /> <settings-form-old v-else />
</div> </div>
......
fragment IntegrationItem on AlertManagementIntegration {
id
type
active
name
url
token
apiUrl
}
#import "../fragments/integration_item.fragment.graphql"
query getIntegrations($projectPath: ID!) {
project(fullPath: $projectPath) {
alertManagementIntegrations {
nodes {
...IntegrationItem
}
}
}
}
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue'; import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
Vue.use(VueApollo);
export default el => { export default el => {
if (!el) { if (!el) {
return null; return null;
...@@ -24,8 +28,22 @@ export default el => { ...@@ -24,8 +28,22 @@ export default el => {
opsgenieMvcFormPath, opsgenieMvcFormPath,
opsgenieMvcEnabled, opsgenieMvcEnabled,
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
projectPath,
} = el.dataset; } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {},
},
),
});
apolloProvider.clients.defaultClient.cache.writeData({
data: {},
});
return new Vue({ return new Vue({
el, el,
provide: { provide: {
...@@ -51,7 +69,9 @@ export default el => { ...@@ -51,7 +69,9 @@ export default el => {
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable), opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
}, },
projectPath,
}, },
apolloProvider,
components: { components: {
AlertSettingsWrapper, AlertSettingsWrapper,
}, },
......
...@@ -103,7 +103,7 @@ export default { ...@@ -103,7 +103,7 @@ export default {
> >
{{ $options.translations.newFlagAlert }} {{ $options.translations.newFlagAlert }}
</gl-alert> </gl-alert>
<gl-loading-icon v-if="isLoading" /> <gl-loading-icon v-if="isLoading" size="xl" class="gl-mt-7" />
<template v-else-if="!isLoading && !hasError"> <template v-else-if="!isLoading && !hasError">
<gl-alert v-if="deprecatedAndEditable" variant="warning" :dismissible="false" class="gl-my-5"> <gl-alert v-if="deprecatedAndEditable" variant="warning" :dismissible="false" class="gl-my-5">
......
...@@ -8,13 +8,12 @@ ...@@ -8,13 +8,12 @@
@include gl-text-gray-500; @include gl-text-gray-500;
tbody { tbody {
tr:not(.b-table-busy-slot) { tr:not(.b-table-busy-slot):not(.b-table-empty-row) {
// TODO replace with gitlab/ui utilities: https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1791
&:hover { &:hover {
border-top-style: double; @include gl-border-t-double;
td { td {
border-bottom-style: initial; @include gl-border-b-initial;
} }
} }
} }
...@@ -22,7 +21,7 @@ ...@@ -22,7 +21,7 @@
tr { tr {
&:focus { &:focus {
outline: none; @include gl-outline-none;
} }
td, td,
...@@ -118,22 +117,22 @@ ...@@ -118,22 +117,22 @@
} }
.gl-tabs-nav { .gl-tabs-nav {
border-bottom-width: 0; @include gl-border-b-0;
.gl-tab-nav-item { .gl-tab-nav-item {
color: $gray-500; @include gl-text-gray-500;
> .gl-tab-counter-badge { > .gl-tab-counter-badge {
color: inherit; @include gl-reset-color;
@include gl-font-sm; @include gl-font-sm;
background-color: $gray-50; @include gl-bg-gray-50;
} }
} }
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
.list-header { .list-header {
flex-direction: column-reverse; @include gl-flex-direction-column-reverse;
} }
.create-incident-button { .create-incident-button {
......
...@@ -29,7 +29,8 @@ module OperationsHelper ...@@ -29,7 +29,8 @@ module OperationsHelper
'url' => alerts_service.url, 'url' => alerts_service.url,
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'), 'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_usage_url' => project_alert_management_index_path(@project), 'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s 'disabled' => disabled.to_s,
'project_path' => project_path(@project)
} }
end end
......
...@@ -22,7 +22,7 @@ module Projects ...@@ -22,7 +22,7 @@ module Projects
def execute def execute
return unless project&.lfs_enabled? && lfs_download_object return unless project&.lfs_enabled? && lfs_download_object
return error("LFS file with oid #{lfs_oid} has invalid attributes") unless lfs_download_object.valid? return error("LFS file with oid #{lfs_oid} has invalid attributes") unless lfs_download_object.valid?
return link_existing_lfs_object! if Feature.enabled?(:lfs_link_existing_object, project, default_enabled: true) && lfs_size > LARGE_FILE_SIZE && lfs_object return link_existing_lfs_object! if lfs_size > LARGE_FILE_SIZE && lfs_object
wrap_download_errors do wrap_download_errors do
download_lfs_file! download_lfs_file!
......
#!/usr/bin/env bash #!/usr/bin/env bash
cd $(dirname $0)/.. cd $(dirname $0)/.. || exit 1
if [ -n "$SIDEKIQ_WORKERS" ] ; then if [ -n "$SIDEKIQ_WORKERS" ] ; then
exec bin/background_jobs_sk_cluster "$@" exec bin/background_jobs_sk_cluster "$@"
......
#!/bin/sh #!/bin/sh
cd $(dirname $0)/.. cd $(dirname $0)/.. || exit 1
app_root=$(pwd) app_root=$(pwd)
mail_room_pidfile="$app_root/tmp/pids/mail_room.pid" mail_room_pidfile="$app_root/tmp/pids/mail_room.pid"
......
#!/bin/sh #!/bin/sh
cd $(dirname $0)/.. cd $(dirname $0)/.. || exit 1
app_root=$(pwd) app_root=$(pwd)
unicorn_pidfile="$app_root/tmp/pids/unicorn.pid" unicorn_pidfile="$app_root/tmp/pids/unicorn.pid"
......
---
title: Execute `exit 1` when shell script `cd` fails
merge_request: 46122
author: Peter Dave Hello
type: other
---
title: Make loading icon on feature flag edit page larger
merge_request: 46268
author:
type: fixed
---
name: lfs_link_existing_object
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41770
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249246
group: group::source code
type: development
default_enabled: true
...@@ -208,8 +208,8 @@ ask an administrator to execute the following commands: ...@@ -208,8 +208,8 @@ ask an administrator to execute the following commands:
```shell ```shell
> sudo gitlab-rails console # Login to Rails console of GitLab instance. > sudo gitlab-rails console # Login to Rails console of GitLab instance.
> Feature.enabled?(:disable_merge_trains) # Check if it's disabled or not. > Feature.enabled?(:disable_merge_trains) # Check if it's disabled or not.
> Feature.enable(:disable_merge_trains) # Disable Merge Trains. > Feature.enable(:disable_merge_trains) # Enable Merge Trains.
> Feature.disable(:disable_merge_trains) # Enable Merge Trains. > Feature.disable(:disable_merge_trains) # Disable Merge Trains.
``` ```
When you disable this feature, all existing merge trains are cancelled and When you disable this feature, all existing merge trains are cancelled and
......
...@@ -6,7 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -6,7 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Incidents # Incidents
Incidents are critical entities in incident management workflows. They represent a service disruption or outage that needs to be restored urgently. GitLab provides tools for the triage, response, and remediation of incidents. Incidents are critical entities in incident management workflows. They represent
a service disruption or outage that needs to be restored urgently. GitLab provides
tools for the triage, response, and remediation of incidents.
## Incident Creation ## Incident Creation
...@@ -14,7 +16,8 @@ You can create an incident manually or automatically. ...@@ -14,7 +16,8 @@ You can create an incident manually or automatically.
### Create incidents manually ### Create incidents manually
If you have at least Guest [permissions](../../user/permissions.md), to create an Incident, you have two options to do this manually. If you have at least Guest [permissions](../../user/permissions.md), to create an
Incident, you have two options to do this manually.
**From the Incidents List:** **From the Incidents List:**
...@@ -43,17 +46,17 @@ If you have at least Guest [permissions](../../user/permissions.md), to create a ...@@ -43,17 +46,17 @@ If you have at least Guest [permissions](../../user/permissions.md), to create a
With Maintainer or higher [permissions](../../user/permissions.md), you can enable With Maintainer or higher [permissions](../../user/permissions.md), you can enable
GitLab to create incident automatically whenever an alert is triggered: GitLab to create incident automatically whenever an alert is triggered:
1. Navigate to **Settings > Operations > Incidents** and expand 1. Navigate to **Settings > Operations > Incidents** and expand **Incidents**:
**Incidents**:
![Incident Management Settings](./img/incident_management_settings_v13_3.png) ![Incident Management Settings](./img/incident_management_settings_v13_3.png)
1. Check the **Create an incident** 1. Check the **Create an incident** checkbox.
checkbox. 1. To customize the incident, select an
1. To customize the incident, select an [issue templates](../../user/project/description_templates.md#creating-issue-templates). [issue template](../../user/project/description_templates.md#creating-issue-templates).
1. To send [an email notification](alert_notifications.md#email-notifications) to users 1. To send [an email notification](alert_notifications.md#email-notifications) to users
with [Developer permissions](../../user/permissions.md), select with [Developer permissions](../../user/permissions.md), select
**Send a separate email notification to Developers**. Email notifications will also be sent to users with **Maintainer** and **Owner** permissions. **Send a separate email notification to Developers**. Email notifications are
also sent to users with **Maintainer** and **Owner** permissions.
1. Click **Save changes**. 1. Click **Save changes**.
### Create incidents via the PagerDuty webhook ### Create incidents via the PagerDuty webhook
...@@ -183,10 +186,22 @@ un-threaded and ordered chronologically, newest to oldest: ...@@ -183,10 +186,22 @@ un-threaded and ordered chronologically, newest to oldest:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241663) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241663) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5.
After enabling **Incident SLA** in the Incident Management configuration, newly-created You can enable the Service Level Agreement Countdown timer on incidents to track
incidents display a SLA (Service Level Agreement) timer showing the time remaining before the Service Level Agreements (SLAs) you hold with your customers. The timer is
the SLA period expires. If the incident is not closed before the SLA period ends, GitLab automatically started when the incident is created, and shows the time
adds a `missed::SLA` label to the incident. remaining before the SLA period expires. To configure the timer:
1. Navigate to **Settings > Operations**.
1. Scroll to **Incidents** and click **Expand**, then select the
**Incident settings** tab.
1. Select **Activate "time to SLA" countdown timer**.
1. Set a time limit in increments of 15 minutes.
1. Click **Save changes**.
After you enable the SLA countdown timer, the **Time to SLA** attribute is displayed
as a column in the Incidents List, and as a field on newly created Incidents. If
the incident isn't closed before the SLA period ends, GitLab adds a `missed::SLA`
label to the incident.
## Incident Actions ## Incident Actions
...@@ -194,15 +209,18 @@ There are different actions available to help triage and respond to incidents. ...@@ -194,15 +209,18 @@ There are different actions available to help triage and respond to incidents.
### Assign incidents ### Assign incidents
Assign incidents to users that are actively responding. Select **Edit** in the right-hand side bar to select or deselect assignees. Assign incidents to users that are actively responding. Select **Edit** in the
right-hand side bar to select or deselect assignees.
### Change severity ### Change severity
See [Incident List](#incident-list) for a full description of the severities available. Select **Edit** in the right-hand side bar to change the severity of an incident. See [Incident List](#incident-list) for a full description of the severity levels available.
Select **Edit** in the right-hand side bar to change the severity of an incident.
### Add a to-do item ### Add a to-do item
Add a to-do for incidents that you want to track in your to-do list. Click the **Add a to do** button at the top of the right-hand side bar to add a to-do item. Add a to-do for incidents that you want to track in your to-do list. Click the
**Add a to do** button at the top of the right-hand side bar to add a to-do item.
### Manage incidents from Slack ### Manage incidents from Slack
......
...@@ -263,9 +263,9 @@ with the older Rails version - which could cause non-GET requests to ...@@ -263,9 +263,9 @@ with the older Rails version - which could cause non-GET requests to
fail for [multi-node GitLab installations](https://docs.gitlab.com/omnibus/update/#multi-node--ha-deployment). fail for [multi-node GitLab installations](https://docs.gitlab.com/omnibus/update/#multi-node--ha-deployment).
So, if you are using multiple Rails servers and specifically upgrading from 13.0, So, if you are using multiple Rails servers and specifically upgrading from 13.0,
all servers must first be upgraded to 13.1.0 before upgrading to later versions: all servers must first be upgraded to 13.1.X before upgrading to 13.2.0 or later:
1. Ensure all GitLab web nodes are on GitLab 13.1.0. 1. Ensure all GitLab web nodes are on GitLab 13.1.X.
1. Optionally, enable the `global_csrf_token` feature flag to enable new 1. Optionally, enable the `global_csrf_token` feature flag to enable new
method of CSRF token generation: method of CSRF token generation:
......
...@@ -34,11 +34,6 @@ export default { ...@@ -34,11 +34,6 @@ export default {
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
projectFullPath: {
type: String,
required: false,
default: '',
},
hasVulnerabilities: { hasVulnerabilities: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -86,14 +81,13 @@ export default { ...@@ -86,14 +81,13 @@ export default {
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" /> <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</div> </div>
<project-pipeline-status :pipeline="pipeline" /> <project-pipeline-status :pipeline="pipeline" />
<vulnerabilities-count-list :project-full-path="projectFullPath" :filters="filters" /> <vulnerabilities-count-list :filters="filters" />
</template> </template>
<template #sticky> <template #sticky>
<filters @filterChange="handleFilterChange" /> <filters @filterChange="handleFilterChange" />
</template> </template>
<project-vulnerabilities-app <project-vulnerabilities-app
:dashboard-documentation="dashboardDocumentation" :dashboard-documentation="dashboardDocumentation"
:project-full-path="projectFullPath"
:filters="filters" :filters="filters"
/> />
</security-dashboard-layout> </security-dashboard-layout>
......
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import PipelineStatusBadge from './pipeline_status_badge.vue'; import PipelineStatusBadge from './pipeline_status_badge.vue';
import projectAutoFixMrsCountQuery from '../graphql/project_auto_fix_mrs_count.query.graphql';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
components: { components: {
...@@ -10,6 +12,24 @@ export default { ...@@ -10,6 +12,24 @@ export default {
TimeAgoTooltip, TimeAgoTooltip,
PipelineStatusBadge, PipelineStatusBadge,
}, },
mixins: [glFeatureFlagsMixin()],
inject: ['projectFullPath', 'autoFixMrsPath'],
apollo: {
autoFixMrsCount: {
query: projectAutoFixMrsCountQuery,
variables() {
return {
fullPath: this.projectFullPath,
};
},
update(data) {
return data?.project?.mergeRequests?.count || 0;
},
skip() {
return !this.glFeatures.securityAutoFix;
},
},
},
props: { props: {
pipeline: { type: Object, required: true }, pipeline: { type: Object, required: true },
}, },
...@@ -22,7 +42,9 @@ export default { ...@@ -22,7 +42,9 @@ export default {
title: __( title: __(
'The Security Dashboard shows the results of the last successful pipeline run on the default branch.', 'The Security Dashboard shows the results of the last successful pipeline run on the default branch.',
), ),
label: __('Last updated'), lastUpdated: __('Last updated'),
autoFixSolutions: s__('AutoRemediation|Auto-fix solutions'),
autoFixMrsLink: s__('AutoRemediation|%{mrsCount} ready for review'),
}, },
}; };
</script> </script>
...@@ -33,10 +55,20 @@ export default { ...@@ -33,10 +55,20 @@ export default {
<div <div
class="gl-display-flex gl-align-items-center gl-border-solid gl-border-1 gl-border-gray-100 gl-p-6" class="gl-display-flex gl-align-items-center gl-border-solid gl-border-1 gl-border-gray-100 gl-p-6"
> >
<span class="gl-font-weight-bold">{{ $options.i18n.label }}</span> <div class="gl-mr-6">
<time-ago-tooltip class="gl-px-3" :time="pipeline.createdAt" /> <span class="gl-font-weight-bold gl-mr-3">{{ $options.i18n.lastUpdated }}</span>
<gl-link :href="pipeline.path" target="_blank">#{{ pipeline.id }}</gl-link> <span class="gl-white-space-nowrap">
<pipeline-status-badge :pipeline="pipeline" class="gl-ml-3" /> <time-ago-tooltip class="gl-pr-3" :time="pipeline.createdAt" />
<gl-link :href="pipeline.path" target="_blank">#{{ pipeline.id }}</gl-link>
<pipeline-status-badge :pipeline="pipeline" class="gl-ml-3" />
</span>
</div>
<div v-if="autoFixMrsCount" data-testid="auto-fix-mrs-link">
<span class="gl-font-weight-bold gl-mr-3">{{ $options.i18n.autoFixSolutions }}</span>
<gl-link :href="autoFixMrsPath" target="_blank" class="gl-white-space-nowrap">{{
sprintf($options.i18n.autoFixMrsLink, { mrsCount: autoFixMrsCount })
}}</gl-link>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -16,11 +16,8 @@ export default { ...@@ -16,11 +16,8 @@ export default {
GlIntersectionObserver, GlIntersectionObserver,
VulnerabilityList, VulnerabilityList,
}, },
inject: ['projectFullPath'],
props: { props: {
projectFullPath: {
type: String,
required: true,
},
filters: { filters: {
type: Object, type: Object,
required: false, required: false,
......
...@@ -6,11 +6,8 @@ export default { ...@@ -6,11 +6,8 @@ export default {
components: { components: {
VulnerabilityCountListLayout, VulnerabilityCountListLayout,
}, },
inject: ['projectFullPath'],
props: { props: {
projectFullPath: {
type: String,
required: true,
},
filters: { filters: {
type: Object, type: Object,
required: false, required: false,
......
...@@ -55,8 +55,9 @@ export default (el, dashboardType) => { ...@@ -55,8 +55,9 @@ export default (el, dashboardType) => {
securityBuildsFailedCount: Number(securityBuildsFailedCount), securityBuildsFailedCount: Number(securityBuildsFailedCount),
securityBuildsFailedPath, securityBuildsFailedPath,
}; };
props.projectFullPath = el.dataset.projectFullPath; provide.projectFullPath = el.dataset.projectFullPath;
provide.autoFixDocumentation = el.dataset.autoFixDocumentation; provide.autoFixDocumentation = el.dataset.autoFixDocumentation;
provide.autoFixMrsPath = el.dataset.autoFixMrsPath;
} else if (dashboardType === DASHBOARD_TYPES.GROUP) { } else if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = FirstClassGroupSecurityDashboard; component = FirstClassGroupSecurityDashboard;
props.groupFullPath = el.dataset.groupFullPath; props.groupFullPath = el.dataset.groupFullPath;
......
query autoFixMrsCount($fullPath: ID!) {
project(fullPath: $fullPath) {
mergeRequests(labels: "GitLab-auto-fix", state: opened) {
count
}
}
}
...@@ -208,7 +208,8 @@ module EE ...@@ -208,7 +208,8 @@ module EE
not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'), not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'),
no_pipeline_run_scanners_help_path: new_project_pipeline_path(project), no_pipeline_run_scanners_help_path: new_project_pipeline_path(project),
security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'), security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'),
auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests') auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests'),
auto_fix_mrs_path: project_merge_requests_path(@project, label_name: 'GitLab-auto-fix')
}.merge!(security_dashboard_pipeline_data(project)) }.merge!(security_dashboard_pipeline_data(project))
end end
end end
......
...@@ -17,9 +17,10 @@ module EE ...@@ -17,9 +17,10 @@ module EE
override :exceeded_size override :exceeded_size
# @param change_size [int] in bytes # @param change_size [int] in bytes
def exceeded_size(change_size = 0) def exceeded_size(change_size = 0)
exceeded_size = super size = super
exceeded_size -= remaining_additional_purchased_storage if additional_repo_storage_available? size -= remaining_additional_purchased_storage if additional_repo_storage_available?
exceeded_size
[size, 0].max
end end
private private
...@@ -38,8 +39,16 @@ module EE ...@@ -38,8 +39,16 @@ module EE
namespace&.additional_purchased_storage_size&.megabytes.to_i namespace&.additional_purchased_storage_size&.megabytes.to_i
end end
def current_project_excess
[current_size - limit, 0].max
end
def total_excess_without_current_project
total_repository_size_excess - current_project_excess
end
def remaining_additional_purchased_storage def remaining_additional_purchased_storage
additional_purchased_storage - total_repository_size_excess additional_purchased_storage - total_excess_without_current_project
end end
end end
end end
......
...@@ -19,12 +19,12 @@ const props = { ...@@ -19,12 +19,12 @@ const props = {
id: '214', id: '214',
path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214', path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214',
}, },
projectFullPath: '/group/project',
securityDashboardHelpPath: '/security/dashboard/help-path', securityDashboardHelpPath: '/security/dashboard/help-path',
vulnerabilitiesExportEndpoint: '/vulnerabilities/exports', vulnerabilitiesExportEndpoint: '/vulnerabilities/exports',
}; };
const provide = { const provide = {
projectFullPath: '/group/project',
dashboardDocumentation: '/help/docs', dashboardDocumentation: '/help/docs',
autoFixDocumentation: '/auto/fix/documentation', autoFixDocumentation: '/auto/fix/documentation',
emptyStateSvgPath: '/svgs/empty/svg', emptyStateSvgPath: '/svgs/empty/svg',
......
import { merge } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue'; import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue';
...@@ -18,12 +20,29 @@ describe('Project Pipeline Status Component', () => { ...@@ -18,12 +20,29 @@ describe('Project Pipeline Status Component', () => {
const findPipelineStatusBadge = () => wrapper.find(PipelineStatusBadge); const findPipelineStatusBadge = () => wrapper.find(PipelineStatusBadge);
const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip); const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip);
const findLink = () => wrapper.find(GlLink); const findLink = () => wrapper.find(GlLink);
const findAutoFixMrsLink = () => wrapper.findByTestId('auto-fix-mrs-link');
const createWrapper = ({ props = {}, options = {} } = {}) => { const createWrapper = (options = {}) => {
return shallowMount(ProjectPipelineStatus, { return extendedWrapper(
propsData: { ...DEFAULT_PROPS, ...props }, shallowMount(
...options, ProjectPipelineStatus,
}); merge(
{},
{
propsData: DEFAULT_PROPS,
provide: {
projectFullPath: '/group/project',
glFeatures: { securityAutoFix: true },
autoFixMrsPath: '/merge_requests?label_name=GitLab-auto-fix',
},
data() {
return { autoFixMrsCount: 0 };
},
},
options,
),
),
);
}; };
afterEach(() => { afterEach(() => {
...@@ -56,7 +75,7 @@ describe('Project Pipeline Status Component', () => { ...@@ -56,7 +75,7 @@ describe('Project Pipeline Status Component', () => {
describe('when no pipeline has run', () => { describe('when no pipeline has run', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper({ props: { pipeline: { path: '' } } }); wrapper = createWrapper({ propsData: { pipeline: { path: '' } } });
}); });
it('should not show the project_pipeline_status component', () => { it('should not show the project_pipeline_status component', () => {
...@@ -65,4 +84,32 @@ describe('Project Pipeline Status Component', () => { ...@@ -65,4 +84,32 @@ describe('Project Pipeline Status Component', () => {
expect(findPipelineStatusBadge().exists()).toBe(false); expect(findPipelineStatusBadge().exists()).toBe(false);
}); });
}); });
describe('auto-fix MRs', () => {
describe('when there are auto-fix MRs', () => {
beforeEach(() => {
wrapper = createWrapper({
data() {
return { autoFixMrsCount: 12 };
},
});
});
it('renders the auto-fix container', () => {
expect(findAutoFixMrsLink().exists()).toBe(true);
});
it('renders a link to open auto-fix MRs if any', () => {
const link = findAutoFixMrsLink().find(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe('/merge_requests?label_name=GitLab-auto-fix');
});
});
it('does not render the link if there are no open auto-fix MRs', () => {
wrapper = createWrapper();
expect(findAutoFixMrsLink().exists()).toBe(false);
});
});
}); });
...@@ -12,10 +12,12 @@ describe('Vulnerabilities app component', () => { ...@@ -12,10 +12,12 @@ describe('Vulnerabilities app component', () => {
const createWrapper = ({ props = {}, $apollo = apolloMock } = {}, options = {}) => { const createWrapper = ({ props = {}, $apollo = apolloMock } = {}, options = {}) => {
wrapper = shallowMount(ProjectVulnerabilitiesApp, { wrapper = shallowMount(ProjectVulnerabilitiesApp, {
provide: {
projectFullPath: '#',
},
propsData: { propsData: {
dashboardDocumentation: '#', dashboardDocumentation: '#',
emptyStateSvgPath: '#', emptyStateSvgPath: '#',
projectFullPath: '#',
...props, ...props,
}, },
mocks: { mocks: {
......
...@@ -9,7 +9,7 @@ describe('Vulnerabilities count list component', () => { ...@@ -9,7 +9,7 @@ describe('Vulnerabilities count list component', () => {
const createWrapper = ({ query } = {}) => { const createWrapper = ({ query } = {}) => {
return shallowMount(VulnerabilityCountList, { return shallowMount(VulnerabilityCountList, {
propsData: { provide: {
projectFullPath: '/root/security-project', projectFullPath: '/root/security-project',
}, },
mocks: { mocks: {
......
...@@ -155,7 +155,8 @@ RSpec.describe ProjectsHelper do ...@@ -155,7 +155,8 @@ RSpec.describe ProjectsHelper do
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index', security_dashboard_help_path: '/help/user/application_security/security_dashboard/index',
not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'), not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'),
no_pipeline_run_scanners_help_path: "/#{project.full_path}/-/pipelines/new", no_pipeline_run_scanners_help_path: "/#{project.full_path}/-/pipelines/new",
auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests') auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests'),
auto_fix_mrs_path: end_with('/merge_requests?label_name=GitLab-auto-fix')
} }
end end
......
...@@ -136,41 +136,69 @@ RSpec.describe Gitlab::RepositorySizeChecker do ...@@ -136,41 +136,69 @@ RSpec.describe Gitlab::RepositorySizeChecker do
stub_feature_flags(namespace_storage_limit: false) stub_feature_flags(namespace_storage_limit: false)
end end
context 'when current size + total repository size excess are below or equal to the limit + additional purchased storage' do context 'with additional purchased storage' do
let(:current_size) { 50 }
let(:total_repository_size_excess) { 10 } let(:total_repository_size_excess) { 10 }
let(:additional_purchased_storage) { 10 } let(:additional_purchased_storage) { 10 }
it 'returns zero' do context 'when no change size provided' do
expect(subject.exceeded_size).to eq(0) context 'when current size + total repository size excess is below the limit (additional purchase storage not used)' do
end let(:current_size) { limit - 1 }
end
context 'when current size + total repository size excess are over the limit + additional purchased storage' do it 'returns zero' do
let(:current_size) { 51 } expect(subject.exceeded_size).to eq(0)
let(:total_repository_size_excess) { 10 } end
let(:additional_purchased_storage) { 10 } end
it 'returns 1' do context 'when current size + total repository size excess is equal to the limit (additional purchase storage not used)' do
expect(subject.exceeded_size).to eq(1.megabytes) let(:current_size) { limit }
end
end it 'returns zero' do
expect(subject.exceeded_size).to eq(0)
end
end
context 'when there is remaining additional purchased storage (current size + other project excess use some additional purchased storage)' do
let(:current_size) { limit + 1 }
context 'when change size will be over the limit' do it 'returns zero' do
let(:current_size) { 50 } expect(subject.exceeded_size).to eq(0)
end
end
it 'returns 1' do context 'when additional purchased storage is depleted (current size + other project excess exceed additional purchased storage)' do
expect(subject.exceeded_size(1.megabytes)).to eq(1.megabytes) let(:total_repository_size_excess) { 15 }
let(:current_size) { 61 }
it 'returns a positive number' do
expect(subject.exceeded_size).to eq(5.megabytes)
end
end
end end
end
context 'when change size will not be over the limit' do context 'when a change size is provided' do
let(:current_size) { 49 } let(:change_size) { 1.megabyte }
context 'when current size + total repository size excess is below the limit (additional purchase storage not used)' do
let(:current_size) { limit - 1 }
it 'returns zero' do
expect(subject.exceeded_size(change_size)).to eq(0)
end
end
it 'returns zero' do context 'when current size + total repository size excess is equal to the limit (additional purchase storage depleted)' do
expect(subject.exceeded_size(1.megabytes)).to eq(0) let(:current_size) { limit }
it 'returns a positive number' do
expect(subject.exceeded_size(change_size)).to eq(1.megabyte)
end
end
end end
end end
context 'without additional purchased storage' do
include_examples 'checker size exceeded'
end
end end
context 'with feature flag :additional_repo_storage_by_namespace disabled' do context 'with feature flag :additional_repo_storage_by_namespace disabled' do
......
...@@ -37,7 +37,9 @@ module Gitlab ...@@ -37,7 +37,9 @@ module Gitlab
# @param change_size [int] in bytes # @param change_size [int] in bytes
def exceeded_size(change_size = 0) def exceeded_size(change_size = 0)
current_size + change_size - limit size = current_size + change_size - limit
[size, 0].max
end end
def error_message def error_message
......
...@@ -3952,6 +3952,12 @@ msgstr "" ...@@ -3952,6 +3952,12 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found." msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found."
msgstr "" msgstr ""
msgid "AutoRemediation|%{mrsCount} ready for review"
msgstr ""
msgid "AutoRemediation|Auto-fix solutions"
msgstr ""
msgid "AutoRemediation|If you're using dependency and/or container scanning, and auto-fix is enabled, auto-fix automatically creates merge requests with fixes to vulnerabilities." msgid "AutoRemediation|If you're using dependency and/or container scanning, and auto-fix is enabled, auto-fix automatically creates merge requests with fixes to vulnerabilities."
msgstr "" msgstr ""
......
#!/usr/bin/env bash #!/usr/bin/env bash
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.." || exit 1
echo "=> Linting documents at path $(pwd) as $(whoami)..." echo "=> Linting documents at path $(pwd) as $(whoami)..."
echo echo
ERRORCODE=0 ERRORCODE=0
......
...@@ -8,12 +8,12 @@ import { trackAlertIntegrationsViewsOptions } from '~/alerts_settings/constants' ...@@ -8,12 +8,12 @@ import { trackAlertIntegrationsViewsOptions } from '~/alerts_settings/constants'
const mockIntegrations = [ const mockIntegrations = [
{ {
activated: true, active: true,
name: 'Integration 1', name: 'Integration 1',
type: 'HTTP endpoint', type: 'HTTP endpoint',
}, },
{ {
activated: false, active: false,
name: 'Integration 2', name: 'Integration 2',
type: 'HTTP endpoint', type: 'HTTP endpoint',
}, },
......
...@@ -3,8 +3,6 @@ import { GlForm, GlFormSelect, GlCollapse, GlFormInput } from '@gitlab/ui'; ...@@ -3,8 +3,6 @@ import { GlForm, GlFormSelect, GlCollapse, GlFormInput } from '@gitlab/ui';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue';
import { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
jest.mock('~/alerts_settings/services');
describe('AlertsSettingsFormNew', () => { describe('AlertsSettingsFormNew', () => {
let wrapper; let wrapper;
......
import { shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue'; import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue'; import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue';
import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue'; import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue'; import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
import { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
import mockIntegrations from './mocks/integrations.json';
jest.mock('~/alerts_settings/services'); describe('AlertsSettingsWrapper', () => {
describe('AlertsSettingsFormWrapper', () => {
let wrapper; let wrapper;
const createComponent = (data = {}, provide = {}) => { const findLoader = () => wrapper.find(IntegrationsList).find(GlLoadingIcon);
wrapper = shallowMount(AlertsSettingsWrapper, { const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
wrapper = mount(AlertsSettingsWrapper, {
data() { data() {
return { ...data }; return { ...data };
}, },
...@@ -20,6 +23,16 @@ describe('AlertsSettingsFormWrapper', () => { ...@@ -20,6 +23,16 @@ describe('AlertsSettingsFormWrapper', () => {
glFeatures: { httpIntegrationsList: false }, glFeatures: { httpIntegrationsList: false },
...provide, ...provide,
}, },
mocks: {
$apollo: {
query: jest.fn(),
queries: {
integrations: {
loading,
},
},
},
},
}); });
}; };
...@@ -30,19 +43,41 @@ describe('AlertsSettingsFormWrapper', () => { ...@@ -30,19 +43,41 @@ describe('AlertsSettingsFormWrapper', () => {
} }
}); });
describe('with default values', () => { describe('with httpIntegrationsList feature flag disabled', () => {
it('renders alerts integrations list and old form by default', () => { it('renders data driven alerts integrations list and old form by default', () => {
createComponent(); createComponent();
expect(wrapper.find(IntegrationsList).exists()).toBe(true); expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(true); expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(true);
expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(false); expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(false);
}); });
});
it('renders alerts integrations list and new form when httpIntegrationsList feature flag is enabled', () => { describe('with httpIntegrationsList feature flag enabled', () => {
createComponent({}, { glFeatures: { httpIntegrationsList: true } }); it('renders the GraphQL alerts integrations list and new form', () => {
createComponent({ provide: { glFeatures: { httpIntegrationsList: true } } });
expect(wrapper.find(IntegrationsList).exists()).toBe(true); expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(false); expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(false);
expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(true); expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(true);
}); });
it('uses a loading state inside the IntegrationsList table', () => {
createComponent({
data: { integrations: {} },
provide: { glFeatures: { httpIntegrationsList: true } },
loading: true,
});
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
it('renders the IntegrationsList table using the API data', () => {
createComponent({
data: { integrations: { list: mockIntegrations } },
provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
expect(findLoader().exists()).toBe(false);
expect(findIntegrations()).toHaveLength(mockIntegrations.length);
});
}); });
}); });
[
{
"id": "gid://gitlab/AlertManagement::HttpIntegration/7",
"type": "HTTP",
"active": true,
"name": "test",
"url": "http://192.168.1.152:3000/root/autodevops/alerts/notify/test/eddd36969b2d3d6a.json",
"token": "7eb24af194116411ec8d66b58c6b0d2e",
"apiUrl": null
},
{
"id": "gid://gitlab/AlertManagement::HttpIntegration/6",
"type": "HTTP",
"active": false,
"name": "test",
"url": "http://192.168.1.152:3000/root/autodevops/alerts/notify/test/abce123.json",
"token": "8639e0ce06c731b00ee3e8dcdfd14fe0",
"apiUrl": null
},
{
"id": "gid://gitlab/AlertManagement::HttpIntegration/5",
"type": "HTTP",
"active": false,
"name": "test",
"url": "http://192.168.1.152:3000/root/autodevops/alerts/notify/test/bcd64c85f918a2e2.json",
"token": "5c8101533d970a55d5c105f8abff2192",
"apiUrl": null
},
{
"id": "gid://gitlab/PrometheusService/12",
"type": "PROMETHEUS",
"active": true,
"name": "Prometheus",
"url": "http://192.168.1.152:3000/root/autodevops/prometheus/alerts/notify.json",
"token": "0b18c37caa8fe980799b349916fe5ddf",
"apiUrl": "https://another-url-2.com"
}
]
...@@ -43,7 +43,8 @@ RSpec.describe OperationsHelper do ...@@ -43,7 +43,8 @@ RSpec.describe OperationsHelper do
'prometheus_api_url' => nil, 'prometheus_api_url' => nil,
'prometheus_activated' => 'false', 'prometheus_activated' => 'false',
'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json), 'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json),
'disabled' => 'false' 'disabled' => 'false',
'project_path' => project_path(project)
) )
end end
end end
......
...@@ -241,18 +241,6 @@ RSpec.describe Projects::LfsPointers::LfsDownloadService do ...@@ -241,18 +241,6 @@ RSpec.describe Projects::LfsPointers::LfsDownloadService do
context 'and first fragments are the same' do context 'and first fragments are the same' do
let(:lfs_content) { existing_lfs_object.file.read } let(:lfs_content) { existing_lfs_object.file.read }
context 'when lfs_link_existing_object feature flag disabled' do
before do
stub_feature_flags(lfs_link_existing_object: false)
end
it 'does not call link_existing_lfs_object!' do
expect(subject).not_to receive(:link_existing_lfs_object!)
subject.execute
end
end
it 'returns success' do it 'returns success' do
expect(subject.execute).to eq({ status: :success }) expect(subject.execute).to eq({ status: :success })
end end
......
...@@ -17,35 +17,58 @@ RSpec.shared_examples 'checker size not over limit' do ...@@ -17,35 +17,58 @@ RSpec.shared_examples 'checker size not over limit' do
end end
RSpec.shared_examples 'checker size exceeded' do RSpec.shared_examples 'checker size exceeded' do
context 'when current size is below or equal to the limit' do context 'when no change size provided' do
let(:current_size) { 50 } context 'when current size is below the limit' do
let(:current_size) { limit - 1 }
it 'returns zero' do it 'returns zero' do
expect(subject.exceeded_size).to eq(0) expect(subject.exceeded_size).to eq(0)
end
end end
end
context 'when current size is over the limit' do context 'when current size is equal to the limit' do
let(:current_size) { 51 } let(:current_size) { limit }
it 'returns zero' do it 'returns zero' do
expect(subject.exceeded_size).to eq(1.megabytes) expect(subject.exceeded_size).to eq(0)
end
end end
end
context 'when change size will be over the limit' do context 'when current size is over the limit' do
let(:current_size) { 50 } let(:current_size) { limit + 1 }
let(:total_repository_size_excess) { 1 }
it 'returns zero' do it 'returns a positive number' do
expect(subject.exceeded_size(1.megabytes)).to eq(1.megabytes) expect(subject.exceeded_size).to eq(1.megabyte)
end
end end
end end
context 'when change size will not be over the limit' do context 'when a change size is provided' do
let(:current_size) { 49 } let(:change_size) { 1.megabyte }
context 'when change size will be over the limit' do
let(:current_size) { limit }
it 'returns a positive number' do
expect(subject.exceeded_size(change_size)).to eq(1.megabyte)
end
end
context 'when change size will be at the limit' do
let(:current_size) { limit - 1 }
it 'returns zero' do
expect(subject.exceeded_size(change_size)).to eq(0)
end
end
context 'when change size will be under the limit' do
let(:current_size) { limit - 2 }
it 'returns zero' do it 'returns zero' do
expect(subject.exceeded_size(1.megabytes)).to eq(0) expect(subject.exceeded_size(change_size)).to eq(0)
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment