Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-15 21:12:52 +00:00
parent 0f50c47cd7
commit 1760f824bb
26 changed files with 476 additions and 176 deletions

View file

@ -1 +1 @@
a67a6fdd96ba690d57c919f9a042dceebab2832e
ac32a7f07a26a15fb59ea897ee7150f98910903d

View file

@ -1,9 +1,9 @@
<script>
import { GlPopover, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { GlPopover, GlButton, GlTooltipDirective, GlTabs, GlTab } from '@gitlab/ui';
import $ from 'jquery';
import { keysFor, BOLD_TEXT, ITALIC_TEXT, LINK_TEXT } from '~/behaviors/shortcuts/keybindings';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { s__, __ } from '~/locale';
import { CopyAsGFM } from '../../../behaviors/markdown/copy_as_gfm';
import ToolbarButton from './toolbar_button.vue';
@ -12,6 +12,8 @@ export default {
ToolbarButton,
GlPopover,
GlButton,
GlTabs,
GlTab,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -144,136 +146,143 @@ export default {
italic: keysFor(ITALIC_TEXT),
link: keysFor(LINK_TEXT),
},
i18n: {
writeTabTitle: __('Write'),
previewTabTitle: __('Preview'),
},
};
</script>
<template>
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
<button class="js-write-link" type="button" @click="writeMarkdownTab($event)">
{{ __('Write') }}
</button>
</li>
<li :class="{ active: previewMarkdown }" class="md-header-tab">
<button
class="js-preview-link js-md-preview-button"
type="button"
@click="previewMarkdownTab($event)"
>
{{ __('Preview') }}
</button>
</li>
<li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
<toolbar-button
tag="**"
:button-title="
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
"
:shortcuts="$options.shortcuts.bold"
icon="bold"
/>
<toolbar-button
tag="_"
:button-title="
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
"
:shortcuts="$options.shortcuts.italic"
icon="italic"
/>
<toolbar-button
:prepend="true"
:tag="tag"
:button-title="__('Insert a quote')"
icon="quote"
@click="handleQuote"
/>
<template v-if="canSuggest">
<gl-tabs content-class="gl-display-none">
<gl-tab
title-link-class="gl-pt-3 gl-px-3 js-md-write-button"
:title="$options.i18n.writeTabTitle"
:active="!previewMarkdown"
data-testid="write-tab"
@click="writeMarkdownTab($event)"
/>
<gl-tab
title-link-class="gl-pt-3 gl-px-3 js-md-preview-button"
:title="$options.i18n.previewTabTitle"
:active="previewMarkdown"
data-testid="preview-tab"
@click="previewMarkdownTab($event)"
/>
<template v-if="!previewMarkdown" #tabs-end>
<div class="md-header-toolbar gl-ml-auto gl-pb-3 gl-justify-content-center">
<toolbar-button
ref="suggestButton"
:tag="mdSuggestion"
:prepend="true"
:button-title="__('Insert suggestion')"
:cursor-offset="4"
:tag-content="lineContent"
icon="doc-code"
data-qa-selector="suggestion_button"
class="js-suggestion-btn"
@click="handleSuggestDismissed"
tag="**"
:button-title="
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
"
:shortcuts="$options.shortcuts.bold"
icon="bold"
/>
<gl-popover
v-if="suggestPopoverVisible"
:target="$refs.suggestButton.$el"
:css-classes="['diff-suggest-popover']"
placement="bottom"
:show="suggestPopoverVisible"
>
<strong>{{ __('New! Suggest changes directly') }}</strong>
<p class="mb-2">
{{
__(
'Suggest code changes which can be immediately applied in one click. Try it out!',
)
}}
</p>
<gl-button
variant="info"
category="primary"
size="small"
<toolbar-button
tag="_"
:button-title="
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
"
:shortcuts="$options.shortcuts.italic"
icon="italic"
/>
<toolbar-button
:prepend="true"
:tag="tag"
:button-title="__('Insert a quote')"
icon="quote"
@click="handleQuote"
/>
<template v-if="canSuggest">
<toolbar-button
ref="suggestButton"
:tag="mdSuggestion"
:prepend="true"
:button-title="__('Insert suggestion')"
:cursor-offset="4"
:tag-content="lineContent"
icon="doc-code"
data-qa-selector="suggestion_button"
class="js-suggestion-btn"
@click="handleSuggestDismissed"
/>
<gl-popover
v-if="suggestPopoverVisible"
:target="$refs.suggestButton.$el"
:css-classes="['diff-suggest-popover']"
placement="bottom"
:show="suggestPopoverVisible"
>
{{ __('Got it') }}
</gl-button>
</gl-popover>
</template>
<toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
<toolbar-button
tag="[{text}](url)"
tag-select="url"
:button-title="
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
"
:shortcuts="$options.shortcuts.link"
icon="link"
/>
<toolbar-button
:prepend="true"
tag="- "
:button-title="__('Add a bullet list')"
icon="list-bulleted"
/>
<toolbar-button
:prepend="true"
tag="1. "
:button-title="__('Add a numbered list')"
icon="list-numbered"
/>
<toolbar-button
:prepend="true"
tag="- [ ] "
:button-title="__('Add a task list')"
icon="list-task"
/>
<toolbar-button
:tag="mdCollapsibleSection"
:prepend="true"
tag-select="Click to expand"
:button-title="__('Add a collapsible section')"
icon="details-block"
/>
<toolbar-button
:tag="mdTable"
:prepend="true"
:button-title="__('Add a table')"
icon="table"
/>
<toolbar-button
class="js-zen-enter"
:prepend="true"
:button-title="__('Go full screen')"
icon="maximize"
/>
</li>
</ul>
<strong>{{ __('New! Suggest changes directly') }}</strong>
<p class="mb-2">
{{
__(
'Suggest code changes which can be immediately applied in one click. Try it out!',
)
}}
</p>
<gl-button
variant="info"
category="primary"
size="small"
@click="handleSuggestDismissed"
>
{{ __('Got it') }}
</gl-button>
</gl-popover>
</template>
<toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
<toolbar-button
tag="[{text}](url)"
tag-select="url"
:button-title="
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
"
:shortcuts="$options.shortcuts.link"
icon="link"
/>
<toolbar-button
:prepend="true"
tag="- "
:button-title="__('Add a bullet list')"
icon="list-bulleted"
/>
<toolbar-button
:prepend="true"
tag="1. "
:button-title="__('Add a numbered list')"
icon="list-numbered"
/>
<toolbar-button
:prepend="true"
tag="- [ ] "
:button-title="__('Add a task list')"
icon="list-task"
/>
<toolbar-button
:tag="mdCollapsibleSection"
:prepend="true"
tag-select="Click to expand"
:button-title="__('Add a collapsible section')"
icon="details-block"
/>
<toolbar-button
:tag="mdTable"
:prepend="true"
:button-title="__('Add a table')"
icon="table"
/>
<toolbar-button
class="js-zen-enter"
:prepend="true"
:button-title="__('Go full screen')"
icon="maximize"
/>
</div>
</template>
</gl-tabs>
</div>
</template>

View file

@ -67,6 +67,27 @@
}
}
}
.gl-tabs-nav {
@include media-breakpoint-down(xs) {
.nav-item {
flex: 1;
border-bottom: 1px solid $border-color;
}
.gl-tab-nav-item {
padding-top: $gl-padding-4;
padding-bottom: $gl-padding-8;
}
.md-header-toolbar {
width: 100%;
display: flex;
flex-wrap: wrap;
margin-top: $gl-padding-8;
}
}
}
}
.md-header-tab {

View file

@ -23,4 +23,42 @@ module StorageHelper
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters
end
def storage_enforcement_banner_info(namespace)
return if namespace.paid?
return unless namespace.storage_enforcement_date && namespace.storage_enforcement_date >= Date.today
return if user_dismissed_storage_enforcement_banner?(namespace)
{
text: html_escape_once(s_("UsageQuota|From %{storage_enforcement_date} storage limits will apply to this namespace. " \
"View and manage your usage in %{strong_start}Group Settings &gt; Usage quotas%{strong_end}.")).html_safe %
{ storage_enforcement_date: namespace.storage_enforcement_date, strong_start: "<strong>".html_safe, strong_end: "</strong>".html_safe },
variant: 'warning',
callouts_path: group_callouts_path,
callouts_feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace),
learn_more_link: link_to(_('Learn more.'), help_page_path('/'), rel: 'noopener noreferrer', target: '_blank') # TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632
}
end
private
def storage_enforcement_banner_user_callouts_feature_name(namespace)
"storage_enforcement_banner_#{storage_enforcement_banner_threshold(namespace)}_enforcement_threshold"
end
def storage_enforcement_banner_threshold(namespace)
days_to_enforcement_date = (namespace.storage_enforcement_date - Date.today)
return :first if days_to_enforcement_date > 30
return :second if days_to_enforcement_date > 15 && days_to_enforcement_date <= 30
return :third if days_to_enforcement_date > 7 && days_to_enforcement_date <= 15
return :fourth if days_to_enforcement_date > 0 && days_to_enforcement_date <= 7
end
def user_dismissed_storage_enforcement_banner?(namespace)
return false unless current_user
current_user.dismissed_callout_for_group?(feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace),
group: namespace)
end
end

View file

@ -79,7 +79,7 @@ class ContainerRepository < ApplicationRecord
)
end
state_machine :migration_state, initial: :default do
state_machine :migration_state, initial: :default, use_transactions: false do
state :pre_importing do
validates :migration_pre_import_started_at, presence: true
validates :migration_pre_import_done_at, presence: false

View file

@ -513,6 +513,12 @@ class Namespace < ApplicationRecord
Feature.enabled?(:create_project_namespace_on_project_create, self, default_enabled: :yaml)
end
def storage_enforcement_date
# should return something like Date.new(2022, 02, 03)
# TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632
nil
end
private
def expire_child_caches

View file

@ -10,7 +10,11 @@ module Users
enum feature_name: {
invite_members_banner: 1,
approaching_seat_count_threshold: 2 # EE-only
approaching_seat_count_threshold: 2, # EE-only
storage_enforcement_banner_first_enforcement_threshold: 43,
storage_enforcement_banner_second_enforcement_threshold: 44,
storage_enforcement_banner_third_enforcement_threshold: 45,
storage_enforcement_banner_fourth_enforcement_threshold: 46
}
validates :group, presence: true

View file

@ -1,7 +1,7 @@
- add_page_specific_style 'page_bundles/ci_status'
- runner_name = "##{@runner.id} (#{@runner.short_sha})"
- if Feature.enabled?(:runner_read_only_admin_view)
- if Feature.enabled?(:runner_read_only_admin_view, default_enabled: :yaml)
- breadcrumb_title _('Edit')
- page_title _('Edit'), runner_name
- add_to_breadcrumbs _('Runners'), admin_runners_path

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350164
milestone: '14.7'
type: development
group: group::runner
default_enabled: false
default_enabled: true

View file

@ -295,6 +295,36 @@ To use an external Prometheus server:
1. Reload the Prometheus server.
### Configure the storage retention size
Prometheus has several custom flags to configure local storage:
- `storage.tsdb.retention.time`: when to remove old data. Defaults to `15d`. Overrides
`storage.tsdb.retention` if this flag is set to anything other than the default.
- `storage.tsdb.retention.size`: [EXPERIMENTAL] the maximum number of bytes of storage blocks to
retain. The oldest data is removed first. Defaults to `0` (disabled). This flag is experimental
and may change in future releases. Units supported: `B`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB`. For
example, `512MB`.
To configure the storage retention size:
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
prometheus['flags'] = {
'storage.tsdb.path' => "/var/opt/gitlab/prometheus/data",
'storage.tsdb.retention.time' => "7d",
'storage.tsdb.retention.size' => "2GB",
'config.file' => "/var/opt/gitlab/prometheus/prometheus.yml"
}
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
## Viewing performance metrics
You can visit `http://localhost:9090` for the dashboard that Prometheus offers by default.
@ -402,3 +432,35 @@ To disable the monitoring of Kubernetes:
1. Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to
take effect.
### Troubleshooting
## `/var/opt/gitlab/prometheus` consumes too much disk space
If you are **not** using Prometheus monitoring:
1. [Disable Prometheus](index.md#configuring-prometheus).
1. Delete the data under `/var/opt/gitlab/prometheus`.
If you are using Prometheus monitoring:
1. Stop Prometheus (deleting data while it's running can cause data corruption):
```shell
gitlab-ctl stop prometheus
```
1. Delete the data under `/var/opt/gitlab/prometheus/data`.
1. Start the service again:
```shell
gitlab-ctl start prometheus
```
1. Verify the service is up and running:
```shell
gitlab-ctl status prometheus
```
1. Optional. [Configure the storage retention size](index.md#configure-the-storage-retention-size).

View file

@ -372,7 +372,8 @@ DELETE /projects/:id/registry/repositories/:repository_id/tags
| `keep_n` | integer | no | The amount of latest tags of given name to keep. |
| `older_than` | string | no | Tags to delete that are older than the given time, written in human readable form `1h`, `1d`, `1month`. |
This API call performs the following operations:
This API returns [HTTP response status code 202](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202)
if successful, and performs the following operations:
- It orders all tags by creation date. The creation date is the time of the
manifest creation, not the time of tag push.

View file

@ -1143,7 +1143,7 @@ Input type: `ConfigureSecretDetectionInput`
### `Mutation.corpusCreate`
Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Available only when feature flag `corpus_management` is enabled. This flag is enabled by default.
Input type: `CorpusCreateInput`
@ -13467,7 +13467,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectcontainerexpirationpolicy"></a>`containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy of the project. |
| <a id="projectcontainerregistryenabled"></a>`containerRegistryEnabled` | [`Boolean`](#boolean) | Indicates if Container Registry is enabled for the current user. |
| <a id="projectcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. |
| <a id="projectcorpuses"></a>`corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="projectcorpuses"></a>`corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. Available only when feature flag `corpus_management` is enabled. This flag is enabled by default. (see [Connections](#connections)) |
| <a id="projectcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of the project creation. |
| <a id="projectdastscannerprofiles"></a>`dastScannerProfiles` | [`DastScannerProfileConnection`](#dastscannerprofileconnection) | DAST scanner profiles associated with the project. (see [Connections](#connections)) |
| <a id="projectdastsiteprofiles"></a>`dastSiteProfiles` | [`DastSiteProfileConnection`](#dastsiteprofileconnection) | DAST Site Profiles associated with the project. (see [Connections](#connections)) |

View file

@ -6,7 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Install the GitLab Agent **(FREE)**
> [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5.
> - [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5.
> - [Introduced](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/merge_requests/594) multi-arch images in GitLab 14.8. The first multi-arch release is `v14.8.1`. It supports AMD64 and ARM64 architectures.
To connect a cluster to GitLab, you need to install the GitLab Agent
onto your cluster.

View file

@ -239,6 +239,17 @@ SPDY protocol.
We [plan to add support for these features](https://gitlab.com/gitlab-org/gitlab/-/issues/346248)
in a future version of GitLab.
### `kubectl` requires TLS
`kubectl` would never send credentials over an unencrypted connection. Self-managed users should ensure that their
GitLab instance is configured with TLS for the CI/CD tunnel feature to work. Trying to use it without TLS
would produce errors:
```shell
$ kubectl get pods
error: You must be logged in to the server (the server has asked for the client to provide credentials)
```
## Use impersonation to restrict project and group access **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345014) in GitLab 14.5.

View file

@ -39153,6 +39153,9 @@ msgstr ""
msgid "UsageQuota|File attachments and smaller design graphics."
msgstr ""
msgid "UsageQuota|From %{storage_enforcement_date} storage limits will apply to this namespace. View and manage your usage in %{strong_start}Group Settings &gt; Usage quotas%{strong_end}."
msgstr ""
msgid "UsageQuota|Git repository."
msgstr ""

View file

@ -57,7 +57,7 @@
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.5.0",
"@gitlab/ui": "36.1.0",
"@gitlab/ui": "36.6.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-6",
"@rails/ujs": "6.1.4-6",

View file

@ -55,7 +55,7 @@ module QA
# wait for the "Save comment" button to disappear
saved = has_no_element?(:save_comment_button)
raise ExpectationNotMet, %q(There was a problem while adding the annotation) unless saved
raise RSpec::Expectations::ExpectationNotMetError, %q(There was a problem while adding the annotation) unless saved
end
def add_design(design_file_path)

View file

@ -133,7 +133,7 @@ RSpec.describe 'Merge request > User posts notes', :js do
describe 'when previewing a note' do
it 'shows the toolbar buttons when editing a note' do
page.within('.js-main-target-form') do
expect(page).to have_css('.md-header-toolbar.active')
expect(page).to have_css('.md-header-toolbar')
end
end
@ -141,7 +141,7 @@ RSpec.describe 'Merge request > User posts notes', :js do
wait_for_requests
find('.js-md-preview-button').click
page.within('.js-main-target-form') do
expect(page).not_to have_css('.md-header-toolbar.active')
expect(page).not_to have_css('.md-header-toolbar')
end
end
end

View file

@ -38,8 +38,8 @@ exports[`packages_list_app renders 1`] = `
class="gl-font-size-h-display gl-line-height-36 h4"
>
There are no packages yet
There are no packages yet
</h1>
<p

View file

@ -38,8 +38,8 @@ exports[`PackagesListApp renders 1`] = `
class="gl-font-size-h-display gl-line-height-36 h4"
>
There are no packages yet
There are no packages yet
</h1>
<p

View file

@ -1,10 +1,10 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
@ -12,8 +12,8 @@ const textareaValue = 'testing\n123';
const uploadsPath = 'test/uploads';
function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) {
expect(writeLink.element.parentNode.classList.contains('active')).toBe(isWrite);
expect(previewLink.element.parentNode.classList.contains('active')).toBe(!isWrite);
expect(writeLink.element.children[0].classList.contains('active')).toBe(isWrite);
expect(previewLink.element.children[0].classList.contains('active')).toBe(!isWrite);
expect(wrapper.find('.md-preview-holder').element.style.display).toBe(isWrite ? 'none' : '');
}
@ -29,14 +29,13 @@ describe('Markdown field component', () => {
afterEach(() => {
subject.destroy();
subject = null;
axiosMock.restore();
});
function createSubject(lines = []) {
// We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression
// caused by mixing Vanilla JS and Vue.
subject = mount(
subject = mountExtended(
{
components: {
MarkdownField,
@ -72,8 +71,8 @@ describe('Markdown field component', () => {
);
}
const getPreviewLink = () => subject.find('.nav-links .js-preview-link');
const getWriteLink = () => subject.find('.nav-links .js-write-link');
const getPreviewLink = () => subject.findByTestId('preview-tab');
const getWriteLink = () => subject.findByTestId('write-tab');
const getMarkdownButton = () => subject.find('.js-md');
const getAllMarkdownButtons = () => subject.findAll('.js-md');
const getVideo = () => subject.find('video');
@ -107,15 +106,15 @@ describe('Markdown field component', () => {
it('sets preview link as active', async () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
previewLink.vm.$emit('click', { target: {} });
await nextTick();
expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
expect(previewLink.element.children[0].classList.contains('active')).toBe(true);
});
it('shows preview loading text', async () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
previewLink.vm.$emit('click', { target: {} });
await nextTick();
expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain('Loading…');
@ -126,7 +125,7 @@ describe('Markdown field component', () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
previewLink.vm.$emit('click', { target: {} });
await axios.waitFor(markdownPreviewPath);
expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
@ -135,7 +134,7 @@ describe('Markdown field component', () => {
it('calls video.pause() on comment input when isSubmitting is changed to true', async () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
previewLink.vm.$emit('click', { target: {} });
await axios.waitFor(markdownPreviewPath);
const video = getVideo();
@ -151,19 +150,19 @@ describe('Markdown field component', () => {
writeLink = getWriteLink();
previewLink = getPreviewLink();
writeLink.trigger('click');
writeLink.vm.$emit('click', { target: {} });
await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
writeLink.trigger('click');
writeLink.vm.$emit('click', { target: {} });
await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
previewLink.trigger('click');
previewLink.vm.$emit('click', { target: {} });
await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);
previewLink.trigger('click');
previewLink.vm.$emit('click', { target: {} });
await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);

View file

@ -1,21 +1,25 @@
import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
import { nextTick } from 'vue';
import { GlTabs } from '@gitlab/ui';
import HeaderComponent from '~/vue_shared/components/markdown/header.vue';
import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('Markdown field header component', () => {
let wrapper;
const createWrapper = (props) => {
wrapper = shallowMount(HeaderComponent, {
wrapper = shallowMountExtended(HeaderComponent, {
propsData: {
previewMarkdown: false,
...props,
},
stubs: { GlTabs },
});
};
const findWriteTab = () => wrapper.findByTestId('write-tab');
const findPreviewTab = () => wrapper.findByTestId('preview-tab');
const findToolbarButtons = () => wrapper.findAll(ToolbarButton);
const findToolbarButtonByProp = (prop, value) =>
findToolbarButtons()
@ -34,7 +38,6 @@ describe('Markdown field header component', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('markdown header buttons', () => {
@ -75,23 +78,26 @@ describe('Markdown field header component', () => {
});
});
it('renders `write` link as active when previewMarkdown is false', () => {
expect(wrapper.find('li:nth-child(1)').classes()).toContain('active');
it('activates `write` tab when previewMarkdown is false', () => {
expect(findWriteTab().attributes('active')).toBe('true');
expect(findPreviewTab().attributes('active')).toBeUndefined();
});
it('renders `preview` link as active when previewMarkdown is true', () => {
it('activates `preview` tab when previewMarkdown is true', () => {
createWrapper({ previewMarkdown: true });
expect(wrapper.find('li:nth-child(2)').classes()).toContain('active');
expect(findWriteTab().attributes('active')).toBeUndefined();
expect(findPreviewTab().attributes('active')).toBe('true');
});
it('emits toggle markdown event when clicking preview', async () => {
wrapper.find('.js-preview-link').trigger('click');
it('emits toggle markdown event when clicking preview tab', async () => {
const eventData = { target: {} };
findPreviewTab().vm.$emit('click', eventData);
await nextTick();
expect(wrapper.emitted('preview-markdown').length).toEqual(1);
wrapper.find('.js-write-link').trigger('click');
findWriteTab().vm.$emit('click', eventData);
await nextTick();
expect(wrapper.emitted('write-markdown').length).toEqual(1);
@ -109,12 +115,10 @@ describe('Markdown field header component', () => {
});
it('blurs preview link after click', () => {
const link = wrapper.find('li:nth-child(2) button');
jest.spyOn(HTMLElement.prototype, 'blur').mockImplementation();
const target = { blur: jest.fn() };
findPreviewTab().vm.$emit('click', { target });
link.trigger('click');
expect(link.element.blur).toHaveBeenCalled();
expect(target.blur).toHaveBeenCalled();
});
it('renders markdown table template', () => {

View file

@ -50,4 +50,87 @@ RSpec.describe StorageHelper do
expect(helper.storage_counters_details(namespace_stats)).to eq(message)
end
end
describe "storage_enforcement_banner" do
let_it_be_with_refind(:current_user) { create(:user) }
let_it_be(:free_group) { create(:group) }
let_it_be(:paid_group) { create(:group) }
before do
allow(helper).to receive(:current_user) { current_user }
allow(Gitlab).to receive(:com?).and_return(true)
allow(paid_group).to receive(:paid?).and_return(true)
end
describe "#storage_enforcement_banner_info" do
it 'returns nil when namespace is not free' do
expect(storage_enforcement_banner_info(paid_group)).to be(nil)
end
it 'returns nil when storage_enforcement_date is not set' do
allow(free_group).to receive(:storage_enforcement_date).and_return(nil)
expect(storage_enforcement_banner_info(free_group)).to be(nil)
end
it 'returns a hash when storage_enforcement_date is set' do
storage_enforcement_date = Date.today + 30
allow(free_group).to receive(:storage_enforcement_date).and_return(storage_enforcement_date)
expect(storage_enforcement_banner_info(free_group)).to eql({
text: "From #{storage_enforcement_date} storage limits will apply to this namespace. View and manage your usage in <strong>Group Settings &gt; Usage quotas</strong>.",
variant: 'warning',
callouts_feature_name: 'storage_enforcement_banner_second_enforcement_threshold',
callouts_path: '/-/users/group_callouts',
learn_more_link: '<a rel="noopener noreferrer" target="_blank" href="/help//">Learn more.</a>'
})
end
context 'when storage_enforcement_date is set and dismissed callout exists' do
before do
create(:group_callout,
user: current_user,
group_id: free_group.id,
feature_name: 'storage_enforcement_banner_second_enforcement_threshold')
storage_enforcement_date = Date.today + 30
allow(free_group).to receive(:storage_enforcement_date).and_return(storage_enforcement_date)
end
it { expect(storage_enforcement_banner_info(free_group)).to be(nil) }
end
context 'callouts_feature_name' do
let(:days_from_now) { 45 }
subject do
storage_enforcement_date = Date.today + days_from_now
allow(free_group).to receive(:storage_enforcement_date).and_return(storage_enforcement_date)
storage_enforcement_banner_info(free_group)[:callouts_feature_name]
end
it 'returns first callouts_feature_name' do
is_expected.to eq('storage_enforcement_banner_first_enforcement_threshold')
end
context 'returns second callouts_feature_name' do
let(:days_from_now) { 20 }
it { is_expected.to eq('storage_enforcement_banner_second_enforcement_threshold') }
end
context 'returns third callouts_feature_name' do
let(:days_from_now) { 13 }
it { is_expected.to eq('storage_enforcement_banner_third_enforcement_threshold') }
end
context 'returns fourth callouts_feature_name' do
let(:days_from_now) { 3 }
it { is_expected.to eq('storage_enforcement_banner_fourth_enforcement_threshold') }
end
end
end
end
end

View file

@ -339,6 +339,55 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end
end
context 'when triggering registry API requests' do
let(:repository_state) { nil }
let(:repository) { create(:container_repository, repository_state) }
shared_examples 'a state machine configured with use_transactions: false' do
it 'executes the registry API request outside of a transaction', :delete do
expect(repository).to receive(:save).and_call_original do
expect(ApplicationRecord.connection.transaction_open?).to be true
end
expect(repository).to receive(:try_import) do
expect(ApplicationRecord.connection.transaction_open?).to be false
end
subject
end
end
context 'when responding to a start_pre_import event' do
subject { repository.start_pre_import }
it_behaves_like 'a state machine configured with use_transactions: false'
end
context 'when responding to a retry_pre_import event' do
let(:repository_state) { :import_aborted }
subject { repository.retry_pre_import }
it_behaves_like 'a state machine configured with use_transactions: false'
end
context 'when responding to a start_import event' do
let(:repository_state) { :pre_import_done }
subject { repository.start_import }
it_behaves_like 'a state machine configured with use_transactions: false'
end
context 'when responding to a retry_import event' do
let(:repository_state) { :import_aborted }
subject { repository.retry_import }
it_behaves_like 'a state machine configured with use_transactions: false'
end
end
describe '#retry_aborted_migration' do
subject { repository.retry_aborted_migration }

View file

@ -2219,4 +2219,13 @@ RSpec.describe Namespace do
end
end
end
describe 'storage_enforcement_date' do
let_it_be(:namespace) { create(:group) }
# Date TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632
it 'returns false' do
expect(namespace.storage_enforcement_date).to be(nil)
end
end
end

View file

@ -986,10 +986,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.5.0.tgz#e0569916fa858462b1801cc90ef8dd9706a12e96"
integrity sha512-cH/EBs//wdkH6kG+kDpvRCIl63/A8JgjAhBJ+ZWucPgtNCDD6x6RDMGdQrxSqhYwcCKDoLStfcxmblBkuiSRXQ==
"@gitlab/ui@36.1.0":
version "36.1.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.1.0.tgz#1cd3d74fabd429a5ff5086eb1f6b4db22506e1b3"
integrity sha512-hTSG1l12AX+2SuGu+04bTc3lt1xE4FXej7O1UIrGELo197GfnpfnQM76/+JK0+b1w8vHw5MODBlt/c536dgaVg==
"@gitlab/ui@36.6.0":
version "36.6.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.6.0.tgz#902ec76623de3b46d450fbe2074d00a39a58d61c"
integrity sha512-hHuknkt4KTQVPEA8t+Cg29hocqMUv4bYfVH7Hinj3qFaIK32zMKUGQ2P/w5BG8R+cP9PTjw+WxNYc4WpRPpcUw==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"