Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-05-22 12:10:37 +00:00
parent 672f729cf2
commit f2d662be68
25 changed files with 215 additions and 185 deletions

View File

@ -6,7 +6,7 @@ workflow:
include:
- project: gitlab-org/quality/pipeline-common
ref: 5.1.1
ref: 5.2.1
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml

View File

@ -33,6 +33,7 @@ variables:
FF_NETWORK_PER_BUILD: 1
GDK_URL: http://gdk.test:3000
before_script:
- echo "SUITE_RAN=true" > suite_status.env
- echo -e "\e[0Ksection_start:`date +%s`:pull_image\r\e[0KPull GDK QA image"
- docker pull ${GDK_IMAGE}
- echo -e "\e[0Ksection_end:`date +%s`:pull_image\r\e[0K"
@ -55,7 +56,6 @@ variables:
- cd qa && bundle install
- retry_exponential test_url ${GDK_URL}/users/sign_in
- echo -e "\e[0Ksection_end:`date +%s`:launch_gdk\r\e[0K"
- echo "SUITE_RAN=true" > suite_status.env
script:
- echo -e "\e[0Ksection_start:`date +%s`:run_tests\r\e[0KRun E2E tests"
- QA_COMMAND="bundle exec bin/qa Test::Instance::All ${GDK_URL} -- ${RSPEC_TAGS} ${RSPEC_REPORT_OPTS}"

View File

@ -43,7 +43,7 @@ export default {
});
this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } });
}
if (this.designCollection.copyState === 'ERROR') {
if (this.designCollection?.copyState === 'ERROR') {
createAlert({
message: s__(
'DesignManagement|There was an error moving your designs. Please upload your designs below.',

View File

@ -38,6 +38,11 @@ import {
} from '../utils/error_messages';
import { trackDesignCreate, trackDesignUpdate } from '../utils/tracking';
export const i18n = {
dropzoneDescriptionText: __('Drag your designs here or %{linkStart}click to upload%{linkEnd}.'),
designLoadingError: __('An error occurred while loading designs. Please try again.'),
};
export default {
components: {
GlLoadingIcon,
@ -346,9 +351,7 @@ export default {
animation: 200,
ghostClass: 'gl-visibility-hidden',
},
i18n: {
dropzoneDescriptionText: __('Drag your designs here or %{linkStart}click to upload%{linkEnd}.'),
},
i18n,
};
</script>
@ -427,7 +430,7 @@ export default {
<div :class="designContentWrapperClass">
<gl-loading-icon v-if="isLoading" size="lg" />
<gl-alert v-else-if="error" variant="danger" :dismissible="false">
{{ __('An error occurred while loading designs. Please try again.') }}
{{ $options.i18n.designLoadingError }}
</gl-alert>
<header
v-else-if="isDesignCollectionCopying"

View File

@ -6,7 +6,7 @@ query projectUsersSearch($search: String!, $fullPath: ID!, $after: String, $firs
id
users: projectMembers(
search: $search
relations: [DIRECT, INHERITED, INVITED_GROUPS]
relations: [DIRECT, INHERITED, INVITED_GROUPS, SHARED_INTO_ANCESTORS]
first: $first
after: $after
sort: USER_FULL_NAME_ASC

View File

@ -14,7 +14,10 @@ query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false)
}
project(fullPath: $fullPath) @include(if: $isProject) {
id
projectMembers(search: $search, relations: [DIRECT, INHERITED, INVITED_GROUPS]) {
projectMembers(
search: $search
relations: [DIRECT, INHERITED, INVITED_GROUPS, SHARED_INTO_ANCESTORS]
) {
nodes {
id
user {

View File

@ -688,21 +688,6 @@ export const getCookie = (name) => Cookies.get(name);
export const removeCookie = (name) => Cookies.remove(name);
/**
* Returns the status of a feature flag.
* Currently, there is no way to access feature
* flags in Vuex other than directly tapping into
* window.gon.
*
* This should only be used on Vuex. If feature flags
* need to be accessed in Vue components consider
* using the Vue feature flag mixin.
*
* @param {String} flag Feature flag
* @returns {Boolean} on/off
*/
export const isFeatureFlagEnabled = (flag) => window.gon.features?.[flag];
/**
* This method takes in array with snake_case strings
* and returns a new array with camelCase strings

View File

@ -1,27 +1,14 @@
<script>
import { GlButton, GlSprintf, GlLink } from '@gitlab/ui';
import { GlSprintf, GlLink } from '@gitlab/ui';
import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-merge-requests-md.svg?url';
import api from '~/api';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
name: 'MRWidgetNothingToMerge',
components: {
GlButton,
GlSprintf,
GlLink,
},
props: {
mr: {
type: Object,
required: true,
},
},
methods: {
onClickNewFile() {
api.trackRedisHllUserEvent('i_code_review_widget_nothing_merge_click_new_file');
},
},
ciHelpPage: helpPagePath('ci/quick_start/index.html'),
EMPTY_STATE_SVG_URL,
};
@ -31,42 +18,30 @@ export default {
<div class="mr-widget-body mr-widget-empty-state">
<div class="row">
<div
class="col-md-3 col-12 text-center d-flex justify-content-center align-items-center svg-content svg-150 pb-0 pt-0"
class="col-md-3 col-12 text-center d-flex justify-content-center align-items-center svg-content svg-130 pb-0 pt-0"
>
<img
:alt="s__('mrWidgetNothingToMerge|This merge request contains no changes.')"
:src="$options.EMPTY_STATE_SVG_URL"
/>
<img :src="$options.EMPTY_STATE_SVG_URL" :alt="''" />
</div>
<div class="text col-md-9 col-12">
<p class="highlight">
{{ s__('mrWidgetNothingToMerge|This merge request contains no changes.') }}
<p class="highlight mt-3">
{{ s__('mrWidgetNothingToMerge|Merge request contains no changes') }}
</p>
<p data-testid="nothing-to-merge-body">
<gl-sprintf
:message="
s__(
'mrWidgetNothingToMerge|Use merge requests to propose changes to your project and discuss them with your team. To make changes, push a commit or edit this merge request to use a different branch. With %{linkStart}CI/CD%{linkEnd}, automatically test your changes before merging.',
'mrWidgetNothingToMerge|Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the %{boldStart}Code%{boldEnd} dropdown list above, then test them with %{linkStart}CI/CD%{linkEnd} before merging.',
)
"
>
<template #bold="{ content }">
<b>{{ content }}</b>
</template>
<template #link="{ content }">
<gl-link :href="$options.ciHelpPage" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<div>
<gl-button
v-if="mr.newBlobPath"
:href="mr.newBlobPath"
category="primary"
variant="confirm"
data-testid="createFileButton"
@click="onClickNewFile"
>
{{ __('Create file') }}
</gl-button>
</div>
</div>
</div>
</div>

View File

@ -46,7 +46,8 @@ module PreferencesHelper
[
[s_('ProjectView|Files and Readme (default)'), :files],
[s_('ProjectView|Activity'), :activity],
[s_('ProjectView|Readme'), :readme]
[s_('ProjectView|Readme'), :readme],
[s_('ProjectView|Wiki'), :wiki]
]
end

View File

@ -22,6 +22,8 @@ class NamespaceSetting < ApplicationRecord
before_validation :normalize_default_branch_name
after_create :set_code_suggestions_default
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval
chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
@ -87,6 +89,14 @@ class NamespaceSetting < ApplicationRecord
self.default_branch_name = default_branch_name.presence
end
def set_code_suggestions_default
# users should have code suggestions disabled by default
return if namespace&.user_namespace?
# groups should have code suggestions enabled by default
update_column(:code_suggestions, true)
end
def allow_mfa_for_group
if namespace&.subgroup? && allow_mfa_for_subgroups == false
errors.add(:allow_mfa_for_subgroups, _('is not allowed since the group is not top-level group.'))

View File

@ -344,7 +344,7 @@ class User < ApplicationRecord
enum dashboard: { projects: 0, stars: 1, your_activity: 10, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8, followed_user_activity: 9 }
# User's Project preference
enum project_view: { readme: 0, activity: 1, files: 2 }
enum project_view: { readme: 0, activity: 1, files: 2, wiki: 3 }
# User's role
enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true

View File

@ -35,6 +35,15 @@ DB_MIGRATION_TESTING_REQUIRED_MESSAGE = <<~MSG
requesting review to test your migrations against production data.
MSG
DB_OLD_MIGRATIONS_MESSAGE = <<~MSG
**Migration Timestamp Out of Date**
The following migrations have timestamps that are over three weeks old:
%<old_migrations>s
Please double check the timestamps and update them if possible. [Why does this matter?](https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-timestamp-age)
MSG
DATABASE_APPROVED_LABEL = 'database::approved'
non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/structure\.sql}).empty?
@ -77,3 +86,11 @@ if helper.mr_labels.include?('database') || db_paths_to_review.any?
helper.labels_to_add << 'database::review pending'
end
end
cutoff = Date.today - 21 # Three weeks ago
old_migrations = database.find_migration_files_before(git.added_files, cutoff)
if old_migrations.present?
warn format(DB_OLD_MIGRATIONS_MESSAGE, old_migrations: old_migrations.map { |m| "* #{m}" }.join("\n"))
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
require_relative '../../tooling/danger/database'
module Danger
class Database < ::Danger::Plugin
# Put the helper code somewhere it can be tested
include Tooling::Danger::Database
end
end

View File

@ -48,3 +48,13 @@ by the server responses that are returned. You can create new responses by editi
and then select **Execute** once again.
![API viewer screenshot](img/apiviewer03-fs8.png)
## Vision
The API code is the single source of truth, and the API documentation should be tightly coupled to its implementation. The OpenAPI specification provides a standardized and comprehensive way to document APIs. It should be the go-to format for documenting the GitLab REST API. This will result in more accurate, reliable, and user-friendly documentation that enhances the overall experience of using the GitLab REST API.
To achieve this it should be a requirement to update the OpenAPI specification with every API code change. By doing so, we ensure that the documentation is always up-to-date and accurate, reducing the risk of confusion as well as errors for our users.
The OpenAPI documentation should be autogenerated from the API code, so that it is easy to keep it up to date and accurate. This will save time and effort for our documentation team.
You can follow the current progress of this vision in the [Document the REST API in OpenAPI V2 epic](https://gitlab.com/groups/gitlab-org/-/epics/8926).

View File

@ -419,7 +419,7 @@ listed in the descriptions of the relevant settings.
| `max_export_size` | integer | no | Maximum export size in MB. 0 for unlimited. Default = 0 (unlimited). |
| `max_import_size` | integer | no | Maximum import size in MB. 0 for unlimited. Default = 0 (unlimited). [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50 MB to 0 in GitLab 13.8. |
| `max_pages_size` | integer | no | Maximum size of pages repositories in MB. |
| `max_personal_access_token_lifetime` **(ULTIMATE SELF)** | integer | no | Maximum allowable lifetime for access tokens in days. |
| `max_personal_access_token_lifetime` **(ULTIMATE SELF)** | integer | no | Maximum allowable lifetime for access tokens in days. When left blank, default value of 365 is applied. When set, value must be 365 or less. When changed, existing access tokens with an expiration date beyond the maximum allowable lifetime are revoked.|
| `max_ssh_key_lifetime` **(ULTIMATE SELF)** | integer | no | Maximum allowable lifetime for SSH keys in days. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1007) in GitLab 14.6. |
| `max_terraform_state_size_bytes` | integer | no | Maximum size in bytes of the [Terraform state](../administration/terraform_state.md) files. Set this to 0 for unlimited file size. |
| `metrics_method_call_threshold` | integer | no | A method call is only tracked when it takes longer than the given amount of milliseconds. |

View File

@ -334,7 +334,7 @@ With this configuration, GitLab adds **artifact 1** as a link to `file.txt` to t
By default artifacts are always kept for successful pipelines for the most recent commit on
each ref. This means that the latest artifacts do not immediately expire according
to the `expire_in` specification.
to the `expire_in` configuration.
If a pipeline for a new commit on the same ref completes successfully, the previous pipeline's
artifacts are deleted according to the `expire_in` configuration. The artifacts
@ -350,6 +350,10 @@ a project, you can disable this behavior to save space:
1. Expand **Artifacts**.
1. Clear the **Keep artifacts from most recent successful jobs** checkbox.
After disabling this setting, all new artifacts expire according to the `expire_in` configuration.
Artifacts in old pipelines continue to be kept until a new pipeline runs for the same ref.
Then the artifacts in the earlier pipeline for that ref are allowed to expire too.
You can disable this behavior for all projects on a self-managed instance in the
[instance's CI/CD settings](../../user/admin_area/settings/continuous_integration.md#keep-the-latest-artifacts-for-all-jobs-in-the-latest-successful-pipelines).

View File

@ -168,9 +168,7 @@ When you use merge request pipelines, you can use:
- All the same [predefined variables](../variables/predefined_variables.md) that are
available in branch pipelines.
- [Additional predefined variables](../variables/predefined_variables.md#predefined-variables-for-merge-request-pipelines)
available only to jobs in merge request pipelines. These variables contain
information from the associated merge request, which can be when calling the
[GitLab Merge Request API endpoint](../../api/merge_requests.md) from a job.
available only to jobs in merge request pipelines.
## Troubleshooting

View File

@ -35950,6 +35950,9 @@ msgstr ""
msgid "ProjectView|Readme"
msgstr ""
msgid "ProjectView|Wiki"
msgstr ""
msgid "Projects"
msgstr ""
@ -50651,10 +50654,10 @@ msgstr ""
msgid "When enabled, cleanup policies execute faster but put more load on Redis."
msgstr ""
msgid "When enabled, existing access tokens may be revoked. Leave blank for no limit."
msgid "When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces."
msgstr ""
msgid "When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces."
msgid "When left blank, default value of 365 is applied. When set, value must be 365 or less. When changed, existing access tokens with an expiration date beyond the maximum allowable lifetime are revoked."
msgstr ""
msgid "When merge requests and commits in the default branch close, any issues they reference also close."
@ -53788,10 +53791,10 @@ msgstr ""
msgid "mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}."
msgstr ""
msgid "mrWidgetNothingToMerge|This merge request contains no changes."
msgid "mrWidgetNothingToMerge|Merge request contains no changes"
msgstr ""
msgid "mrWidgetNothingToMerge|Use merge requests to propose changes to your project and discuss them with your team. To make changes, push a commit or edit this merge request to use a different branch. With %{linkStart}CI/CD%{linkEnd}, automatically test your changes before merging."
msgid "mrWidgetNothingToMerge|Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the %{boldStart}Code%{boldEnd} dropdown list above, then test them with %{linkStart}CI/CD%{linkEnd} before merging."
msgstr ""
msgid "mrWidget|%{boldHeaderStart}Looks like there's no pipeline here.%{boldHeaderEnd}"

View File

@ -110,8 +110,8 @@ RSpec.describe 'Dropdown assignee', :js, feature_category: :team_planning do
expect(page).to have_text group_user.name
expect(page).to have_text subgroup_user.name
expect(page).to have_text invited_to_project_group_user.name
expect(page).to have_text invited_to_group_group_user.name
expect(page).not_to have_text subsubgroup_user.name
expect(page).not_to have_text invited_to_group_group_user.name
end
end
end

View File

@ -1,60 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management index page designs renders error 1`] = `
<div
class="gl-mt-4"
data-testid="designs-root"
>
<!---->
<!---->
<div
class="gl-bg-gray-10 gl-border gl-border-t-0 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-5"
>
<gl-alert-stub
dismisslabel="Dismiss"
primarybuttonlink=""
primarybuttontext=""
secondarybuttonlink=""
secondarybuttontext=""
showicon="true"
title=""
variant="danger"
>
An error occurred while loading designs. Please try again.
</gl-alert-stub>
</div>
<router-view-stub
name="default"
/>
</div>
`;
exports[`Design management index page designs renders loading icon 1`] = `
<div
class="gl-mt-4"
data-testid="designs-root"
>
<!---->
<!---->
<div
class="gl-bg-gray-10 gl-border gl-border-t-0 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-5"
>
<gl-loading-icon-stub
color="dark"
label="Loading"
size="lg"
/>
</div>
<router-view-stub
name="default"
/>
</div>
`;

View File

@ -1,4 +1,4 @@
import { GlEmptyState } from '@gitlab/ui';
import { GlEmptyState, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo, { ApolloMutation } from 'vue-apollo';
@ -16,7 +16,7 @@ import DesignDestroyer from '~/design_management/components/design_destroyer.vue
import Design from '~/design_management/components/list/item.vue';
import moveDesignMutation from '~/design_management/graphql/mutations/move_design.mutation.graphql';
import uploadDesignMutation from '~/design_management/graphql/mutations/upload_design.mutation.graphql';
import Index from '~/design_management/pages/index.vue';
import Index, { i18n } from '~/design_management/pages/index.vue';
import createRouter from '~/design_management/router';
import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants';
import * as utils from '~/design_management/utils/design_management_utils';
@ -117,6 +117,8 @@ describe('Design management index page', () => {
const findDesignUploadButton = () => wrapper.findByTestId('design-upload-button');
const findDesignToolbarWrapper = () => wrapper.findByTestId('design-toolbar-wrapper');
const findDesignUpdateAlert = () => wrapper.findByTestId('design-update-alert');
const findLoadinIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(GlAlert);
async function moveDesigns(localWrapper) {
await waitForPromises();
@ -177,13 +179,14 @@ describe('Design management index page', () => {
function createComponentWithApollo({
permissionsHandler = jest.fn().mockResolvedValue(getPermissionsQueryResponse()),
moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse),
getDesignListHandler = jest.fn().mockResolvedValue(getDesignListQueryResponse()),
}) {
Vue.use(VueApollo);
permissionsQueryHandler = permissionsHandler;
moveDesignHandler = moveHandler;
const requestHandlers = [
[getDesignListQuery, jest.fn().mockResolvedValue(getDesignListQueryResponse())],
[getDesignListQuery, getDesignListHandler],
[permissionsQuery, permissionsQueryHandler],
[moveDesignMutation, moveDesignHandler],
];
@ -203,24 +206,12 @@ describe('Design management index page', () => {
describe('designs', () => {
it('renders loading icon', () => {
createComponent({ loading: true });
expect(wrapper.element).toMatchSnapshot();
});
it('renders error', async () => {
createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ error: true });
await nextTick();
expect(wrapper.element).toMatchSnapshot();
expect(findLoadinIcon().exists()).toBe(true);
});
it('renders a toolbar with buttons when there are designs', () => {
createComponent({ allVersions: [mockVersion] });
expect(findLoadinIcon().exists()).toBe(false);
expect(findToolbar().exists()).toBe(true);
});
@ -382,9 +373,8 @@ describe('Design management index page', () => {
it('updates state appropriately after upload complete', async () => {
createComponent({ stubs: { GlEmptyState } });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
const designDropzone = findFirstDropzoneWithDesign();
designDropzone.vm.$emit('change', 'test');
wrapper.vm.onUploadDesignDone(designUploadMutationCreatedResponse);
await nextTick();
@ -396,10 +386,8 @@ describe('Design management index page', () => {
it('updates state appropriately after upload error', async () => {
createComponent({ stubs: { GlEmptyState } });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
const designDropzone = findFirstDropzoneWithDesign();
designDropzone.vm.$emit('change', 'test');
wrapper.vm.onUploadDesignError();
await nextTick();
expect(wrapper.vm.filesToBeSaved).toEqual([]);
@ -752,6 +740,16 @@ describe('Design management index page', () => {
});
describe('with mocked Apollo client', () => {
it('renders error', async () => {
// eslint-disable-next-line no-console
console.error = jest.fn();
createComponentWithApollo({
getDesignListHandler: jest.fn().mockRejectedValue(new Error('GraphQL error')),
});
await waitForPromises();
expect(findAlert().text()).toBe(i18n.designLoadingError);
});
it('has a design with id 1 as a first one', async () => {
createComponentWithApollo({});
await waitForPromises();

View File

@ -4,26 +4,15 @@ import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing
describe('NothingToMerge', () => {
let wrapper;
const newBlobPath = '/foo';
const defaultProps = {
mr: {
newBlobPath,
},
};
const createComponent = (props = defaultProps) => {
const createComponent = () => {
wrapper = shallowMountExtended(NothingToMerge, {
propsData: {
...props,
},
stubs: {
GlSprintf,
},
});
};
const findCreateButton = () => wrapper.findByTestId('createFileButton');
const findNothingToMergeTextBody = () => wrapper.findByTestId('nothing-to-merge-body');
describe('With Blob link', () => {
@ -32,27 +21,10 @@ describe('NothingToMerge', () => {
});
it('shows the component with the correct text and highlights', () => {
expect(wrapper.text()).toContain('This merge request contains no changes.');
expect(wrapper.text()).toContain('Merge request contains no changes');
expect(findNothingToMergeTextBody().text()).toContain(
'Use merge requests to propose changes to your project and discuss them with your team. To make changes, push a commit or edit this merge request to use a different branch.',
'Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the Code dropdown list above, then test them with CI/CD before merging.',
);
});
it('shows the Create file button with the correct attributes', () => {
const createButton = findCreateButton();
expect(createButton.exists()).toBe(true);
expect(createButton.attributes('href')).toBe(newBlobPath);
});
});
describe('Without Blob link', () => {
beforeEach(() => {
createComponent({ mr: { newBlobPath: '' } });
});
it('does not show the Create file button', () => {
expect(findCreateButton().exists()).toBe(false);
});
});
});

View File

@ -54,6 +54,36 @@ RSpec.describe NamespaceSetting, feature_category: :subgroups, type: :model do
end
end
describe '#code_suggestions' do
context 'when group namespaces' do
let(:settings) { group.namespace_settings }
let(:group) { create(:group) }
context 'when group is created' do
it 'sets default code_suggestions value to true' do
expect(settings.code_suggestions).to eq true
end
end
context 'when setting is updated' do
it 'persists the code suggestions setting' do
settings.update!(code_suggestions: false)
expect(settings.code_suggestions).to eq false
end
end
end
context 'when user namespace' do
let(:user) { create(:user) }
let(:settings) { user.namespace.namespace_settings }
it 'defaults to false' do
expect(settings.code_suggestions).to eq false
end
end
end
describe '#allow_mfa_for_group' do
let(:settings) { group.namespace_settings }

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'gitlab-dangerfiles'
require 'danger'
require 'danger/plugins/internal/helper'
require 'gitlab/dangerfiles/spec_helper'
require_relative '../../../tooling/danger/database'
RSpec.describe Tooling::Danger::Database, feature_category: :tooling do
include_context "with dangerfile"
let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
let(:migration_files) do
[
# regular migrations
'db/migrate/20220901010203_add_widgets_table.rb',
'db/migrate/20220909010203_add_properties_column.rb',
'db/migrate/20220910010203_drop_tools_table.rb',
'db/migrate/20220912010203_add_index_to_widgets_table.rb',
# post migrations
'db/post_migrate/20220901010203_add_widgets_table.rb',
'db/post_migrate/20220909010203_add_properties_column.rb',
'db/post_migrate/20220910010203_drop_tools_table.rb',
'db/post_migrate/20220912010203_add_index_to_widgets_table.rb',
# ee migrations
'ee/db/migrate/20220901010203_add_widgets_table.rb',
'ee/db/migrate/20220909010203_add_properties_column.rb',
'ee/db/migrate/20220910010203_drop_tools_table.rb',
'ee/db/migrate/20220912010203_add_index_to_widgets_table.rb',
# geo migrations
'ee/db/geo/migrate/20220901010203_add_widgets_table.rb',
'ee/db/geo/migrate/20220909010203_add_properties_column.rb',
'ee/db/geo/migrate/20220910010203_drop_tools_table.rb',
'ee/db/geo/migrate/20220912010203_add_index_to_widgets_table.rb'
]
end
let(:cutoff) { Date.parse('2022-10-01') - 21 }
subject(:database) { fake_danger.new }
describe '#find_migration_files_before' do
it 'returns migrations that are before the cutoff' do
expect(database.find_migration_files_before(migration_files, cutoff).length).to eq(8)
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Tooling
module Danger
module Database
TIMESTAMP_MATCHER = /(?<timestamp>\d{14})/
MIGRATION_MATCHER = %r{\A(ee/)?db/(geo/)?(post_)?migrate/}
def find_migration_files_before(file_names, cutoff)
migrations = file_names.select { |f| f.match?(MIGRATION_MATCHER) }
migrations.select do |migration|
next unless match = TIMESTAMP_MATCHER.match(migration)
timestamp = Date.parse(match[:timestamp])
timestamp < cutoff
end
end
end
end
end