Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-26 03:18:41 +00:00
parent 7d4bff6fe8
commit 77da08b6e8
25 changed files with 384 additions and 425 deletions

View File

@ -42,24 +42,15 @@ export default {
isLoading: false,
};
},
computed: {
oauthEnabled() {
return this.glFeatures.jiraConnectOauth;
},
},
methods: {
...mapActions(['addSubscription']),
async onClick() {
if (this.oauthEnabled) {
this.isLoading = true;
await this.addSubscription({
namespacePath: this.group.full_path,
subscriptionsPath: this.subscriptionsPath,
});
this.isLoading = false;
} else {
this.deprecatedAddSubscription();
}
this.isLoading = true;
await this.addSubscription({
namespacePath: this.group.full_path,
subscriptionsPath: this.subscriptionsPath,
});
this.isLoading = false;
},
deprecatedAddSubscription() {
this.isLoading = true;

View File

@ -45,21 +45,14 @@ export default {
return !isEmpty(this.subscriptions);
},
userSignedIn() {
if (this.isOauthEnabled) {
return Boolean(this.currentUser);
}
return Boolean(!this.usersPath);
},
isOauthEnabled() {
return this.glFeatures.jiraConnectOauth;
return Boolean(this.currentUser);
},
/**
* Returns false if the GitLab for Jira app doesn't support the user's browser.
* Any web API that the GitLab for Jira app depends on should be checked here.
*/
isBrowserSupported() {
return !this.isOauthEnabled || AccessorUtilities.canUseCrypto();
return AccessorUtilities.canUseCrypto();
},
gitlabUrl() {
return gon.gitlab_url;
@ -80,11 +73,10 @@ export default {
}),
...mapActions(['fetchSubscriptions']),
/**
* Fetch subscriptions from the REST API,
* if the jiraConnectOauth flag is enabled.
* Fetch subscriptions from the REST API.
*/
fetchSubscriptionsOauth() {
if (!this.isOauthEnabled || !this.userSignedIn) return;
if (!this.userSignedIn) return;
this.fetchSubscriptions(this.subscriptionsPath);
},

View File

@ -54,9 +54,6 @@ export default {
? this.$options.i18n.signedInAsUserText
: this.$options.i18n.signedInText;
},
isOauthEnabled() {
return this.glFeatures.jiraConnectOauth;
},
},
async created() {
this.signInURL = await getGitlabSignInURL(this.usersPath);
@ -79,13 +76,9 @@ export default {
</gl-sprintf>
<template v-else-if="hasSubscriptions">
<sign-in-oauth-button v-if="isOauthEnabled" category="tertiary">
<sign-in-oauth-button category="tertiary">
{{ $options.i18n.signInText }}
</sign-in-oauth-button>
<gl-link v-else data-testid="sign-in-link" :href="signInURL" target="_blank">
{{ $options.i18n.signInText }}
</gl-link>
</template>
</div>
</template>

View File

@ -9,7 +9,6 @@ export default {
name: 'SignInGitlabCom',
components: {
SubscriptionsList,
SignInLegacyButton: () => import('../../components/sign_in_legacy_button.vue'),
SignInOauthButton: () => import('../../components/sign_in_oauth_button.vue'),
},
mixins: [glFeatureFlagMixin()],
@ -20,11 +19,6 @@ export default {
required: true,
},
},
computed: {
useSignInOauthButton() {
return this.glFeatures.jiraConnectOauth;
},
},
i18n: {
signInButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
signInText: s__('JiraService|Sign in to GitLab to get started.'),
@ -44,16 +38,12 @@ export default {
<div v-if="hasSubscriptions">
<div class="gl-display-flex gl-justify-content-end gl-mb-3">
<sign-in-oauth-button
v-if="useSignInOauthButton"
:gitlab-base-path="$options.GITLAB_COM_BASE_PATH"
@sign-in="$emit('sign-in-oauth', $event)"
@error="onSignInError"
>
{{ $options.i18n.signInButtonTextWithSubscriptions }}
</sign-in-oauth-button>
<sign-in-legacy-button v-else :users-path="usersPath">
{{ $options.i18n.signInButtonTextWithSubscriptions }}
</sign-in-legacy-button>
</div>
<subscriptions-list />
@ -61,12 +51,10 @@ export default {
<div v-else class="gl-text-center">
<p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
<sign-in-oauth-button
v-if="useSignInOauthButton"
:gitlab-base-path="$options.GITLAB_COM_BASE_PATH"
@sign-in="$emit('sign-in-oauth', $event)"
@error="onSignInError"
/>
<sign-in-legacy-button v-else class="gl-mb-7" :users-path="usersPath" />
</div>
</div>
</template>

View File

@ -19,7 +19,7 @@ export default {
},
computed: {
isOauthSelfManagedEnabled() {
return this.glFeatures.jiraConnectOauth && this.publicKeyStorageEnabled;
return this.publicKeyStorageEnabled;
},
},
};

View File

@ -59,7 +59,7 @@ export default ({ editorAiActions = [] } = {}) => {
showTimelineViewToggle,
reportAbusePath: notesDataset.reportAbusePath,
newCommentTemplatePath: notesDataset.newCommentTemplatePath,
editorAiActions: editorAiActions.map((factory) => factory(notesDataset)),
editorAiActions: editorAiActions.map((factory) => factory(noteableData)),
},
data() {
return {

View File

@ -5,6 +5,10 @@ module Gitlab
# Module that provides methods shared by the various workers used for
# importing GitHub projects.
module ReschedulingMethods
include JobDelayCalculator
ENQUEUED_JOB_COUNT = 'github-importer/enqueued_job_count/%{project}/%{collection}'
# project_id - The ID of the GitLab project to import the note into.
# hash - A Hash containing the details of the GitHub object to import.
# notify_key - The Redis key to notify upon completion, if any.
@ -18,10 +22,7 @@ module Gitlab
if try_import(project, client, hash)
notify_waiter(notify_key)
else
# In the event of hitting the rate limit we want to reschedule the job
# so its retried after our rate limit has been reset.
self.class
.perform_in(client.rate_limit_resets_in, project.id, hash, notify_key)
reschedule_job(project, client, hash, notify_key)
end
end
@ -35,6 +36,20 @@ module Gitlab
def notify_waiter(key = nil)
JobWaiter.notify(key, jid) if key
end
def reschedule_job(project, client, hash, notify_key)
# In the event of hitting the rate limit we want to reschedule the job
# so its retried after our rate limit has been reset with additional delay
# to spread the load.
enqueued_job_count_key = format(ENQUEUED_JOB_COUNT, project: project.id, collection: object_type)
enqueued_job_counter =
Gitlab::Cache::Import::Caching.increment(enqueued_job_count_key, timeout: client.rate_limit_resets_in)
job_delay = client.rate_limit_resets_in + calculate_job_delay(enqueued_job_counter)
self.class
.perform_in(job_delay, project.id, hash, notify_key)
end
end
end
end

View File

@ -16,6 +16,10 @@ module Gitlab
def object_type
:pull_request
end
def parallel_import_batch
{ size: 200, delay: 1.minute }
end
end
end
end

View File

@ -0,0 +1,16 @@
- title: "Auto DevOps no longer provisions a database by default"
announcement_milestone: "15.8"
removal_milestone: "16.0"
breaking_change: true
reporter: tigerwnz
stage: Environments
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343988
body: |
Currently, Auto DevOps provisions an in-cluster PostgreSQL database by default.
In GitLab 16.0, databases will be provisioned only for users who opt in. This
change supports production deployments that require more robust database management.
If you want Auto DevOps to provision an in-cluster database,
set the `POSTGRES_ENABLED` CI/CD variable to `true`.
tiers: [Core, Premium, Ultimate]
documentation_url: https://docs.gitlab.com/ee/topics/autodevops/stages.html#auto-deploy

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class RemovePhabricatorFromApplicationSettings < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
class ApplicationSetting < MigrationRecord
end
def up
# rubocop: disable Style/GuardClause
unless import_sources.empty?
ApplicationSetting.update_all(import_sources: import_sources.reject { |x| x == "phabricator" })
end
# rubocop: enable Style/GuardClause
end
def down
## a reversion is not needed as Phabricator is no longer a supported import source
# and attempting to save it as one will result in a ActiveRecord error.
end
def import_sources
## the last ApplicationSetting record is used to determine application settings
import_sources = ApplicationSetting.last&.import_sources
import_sources.nil? ? [] : YAML.safe_load(import_sources)
end
end

View File

@ -0,0 +1 @@
c8e606b6e85bee317017cb15bd9d4249378b4d2b9225fb493e85086063f0f23d

View File

@ -5439,7 +5439,7 @@ Input type: `ScanExecutionPolicyCommitInput`
| ---- | ---- | ----------- |
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationscanexecutionpolicycommitfullpath"></a>`fullPath` | [`String`](#string) | Full path of the project. |
| <a id="mutationscanexecutionpolicycommitname"></a>`name` | [`String`](#string) | Name of the policy. If the name is null, the `name` field from `policy_yaml` is used. |
| <a id="mutationscanexecutionpolicycommitname"></a>`name` | [`String!`](#string) | Name of the policy. If the name is null, the `name` field from `policy_yaml` is used. |
| <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. |
| <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. |
| <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` **{warning-solid}** | [`ID`](#id) | **Deprecated:** Use `fullPath`. Deprecated in 14.10. |

View File

@ -270,6 +270,12 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
every Sidekiq process also listens to those queues to ensure all jobs are processed across
all queues. This behavior does not apply if you have configured the [routing rules](../administration/sidekiq/processing_specific_job_classes.md#routing-rules).
### 15.11.0
- Upgrades to GitLab 15.11 directly from GitLab versions 15.5.0 and earlier on self-managed installs will fail due to a missing migration until the fix for [issue 408304](https://gitlab.com/gitlab-org/gitlab/-/issues/408304) is released in 15.11.1. Affected users wanting to upgrade to 15.11.x can either:
- Perform an intermediate upgrade to any version between 15.5 and 15.10 before upgrading to 15.11, or
- Target the forthcoming 15.11.1 patch.
### 15.10.0
- Gitaly configuration changes significantly in Omnibus GitLab 16.0. You can begin migrating to the new structure in Omnibus GitLab 15.10 while backwards compatibility is

View File

@ -36,6 +36,19 @@ For removal reviewers (Technical Writers only):
## Removed in 16.0
### Auto DevOps no longer provisions a database by default
WARNING:
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
Review the details carefully before upgrading.
Currently, Auto DevOps provisions an in-cluster PostgreSQL database by default.
In GitLab 16.0, databases will be provisioned only for users who opt in. This
change supports production deployments that require more robust database management.
If you want Auto DevOps to provision an in-cluster database,
set the `POSTGRES_ENABLED` CI/CD variable to `true`.
### Project REST API field `operations_access_level` removed
WARNING:

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
# Used to calculate delay to spread sidekiq jobs on fetching records during import
# and upon job reschedule when the rate limit is reached
module JobDelayCalculator
# Default batch settings for parallel import (can be redefined in Importer/Worker classes)
def parallel_import_batch
{ size: 1000, delay: 1.minute }
end
private
def calculate_job_delay(job_index)
multiplier = (job_index / parallel_import_batch[:size])
(multiplier * parallel_import_batch[:delay]) + 1.second
end
end
end
end

View File

@ -3,6 +3,8 @@
module Gitlab
module GithubImport
module ParallelScheduling
include JobDelayCalculator
attr_reader :project, :client, :page_counter, :already_imported_cache_key,
:job_waiter_cache_key, :job_waiter_remaining_cache_key
@ -197,11 +199,6 @@ module Gitlab
raise NotImplementedError
end
# Default batch settings for parallel import (can be redefined in Importer classes)
def parallel_import_batch
{ size: 1000, delay: 1.minute }
end
def abort_on_failure
false
end
@ -243,12 +240,6 @@ module Gitlab
JobWaiter.new(jobs_remaining, key)
end
end
def calculate_job_delay(job_index)
multiplier = (job_index / parallel_import_batch[:size])
(multiplier * parallel_import_batch[:delay]) + 1.second
end
end
end
end

View File

@ -10949,6 +10949,9 @@ msgstr ""
msgid "ComplianceFrameworks|Description is required"
msgstr ""
msgid "ComplianceFrameworks|Edit compliance framework"
msgstr ""
msgid "ComplianceFrameworks|Edit framework"
msgstr ""
@ -10991,6 +10994,9 @@ msgstr ""
msgid "ComplianceFrameworks|Required format: %{codeStart}path/file.y[a]ml@group-name/project-name%{codeEnd}. %{linkStart}See some examples%{linkEnd}."
msgstr ""
msgid "ComplianceFrameworks|Saved changes to compliance framework"
msgstr ""
msgid "ComplianceFrameworks|Set default"
msgstr ""

View File

@ -1,22 +1,11 @@
import { GlButton } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import * as JiraConnectApi from '~/jira_connect/subscriptions/api';
import GroupItemName from '~/jira_connect/subscriptions/components/group_item_name.vue';
import GroupsListItem from '~/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue';
import { persistAlert, reloadPage } from '~/jira_connect/subscriptions/utils';
import {
I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
INTEGRATIONS_DOC_LINK,
} from '~/jira_connect/subscriptions/constants';
import createStore from '~/jira_connect/subscriptions/store';
import { mockGroup1 } from '../../mock_data';
jest.mock('~/jira_connect/subscriptions/utils');
describe('GroupsListItem', () => {
let wrapper;
let store;
@ -61,88 +50,24 @@ describe('GroupsListItem', () => {
});
describe('on Link button click', () => {
describe('when jiraConnectOauth feature flag is disabled', () => {
let addSubscriptionSpy;
const mockSubscriptionsPath = '/subscriptions';
beforeEach(() => {
createComponent({ mountFn: mount });
addSubscriptionSpy = jest.spyOn(JiraConnectApi, 'addSubscription').mockResolvedValue();
});
it('sets button to loading and sends request', async () => {
expect(findLinkButton().props('loading')).toBe(false);
clickLinkButton();
await nextTick();
expect(findLinkButton().props('loading')).toBe(true);
await waitForPromises();
expect(addSubscriptionSpy).toHaveBeenCalledWith(
mockAddSubscriptionsPath,
mockGroup1.full_path,
);
expect(persistAlert).toHaveBeenCalledWith({
linkUrl: INTEGRATIONS_DOC_LINK,
message: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
title: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
variant: 'success',
});
});
describe('when request is successful', () => {
it('reloads the page', async () => {
clickLinkButton();
await waitForPromises();
expect(reloadPage).toHaveBeenCalled();
});
});
describe('when request has errors', () => {
const mockErrorMessage = 'error message';
const mockError = { response: { data: { error: mockErrorMessage } } };
beforeEach(() => {
addSubscriptionSpy = jest
.spyOn(JiraConnectApi, 'addSubscription')
.mockRejectedValue(mockError);
});
it('emits `error` event', async () => {
clickLinkButton();
await waitForPromises();
expect(reloadPage).not.toHaveBeenCalled();
expect(wrapper.emitted('error')[0][0]).toBe(mockErrorMessage);
});
beforeEach(() => {
createComponent({
mountFn: mount,
provide: {
subscriptionsPath: mockSubscriptionsPath,
},
});
});
describe('when jiraConnectOauth feature flag is enabled', () => {
const mockSubscriptionsPath = '/subscriptions';
it('dispatches `addSubscription` action', () => {
clickLinkButton();
beforeEach(() => {
createComponent({
mountFn: mount,
provide: {
subscriptionsPath: mockSubscriptionsPath,
glFeatures: { jiraConnectOauth: true },
},
});
});
it('dispatches `addSubscription` action', async () => {
clickLinkButton();
await nextTick();
expect(store.dispatch).toHaveBeenCalledWith('addSubscription', {
namespacePath: mockGroup1.full_path,
subscriptionsPath: mockSubscriptionsPath,
});
expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith('addSubscription', {
namespacePath: mockGroup1.full_path,
subscriptionsPath: mockSubscriptionsPath,
});
});
});

View File

@ -1,6 +1,6 @@
import { GlLink } from '@gitlab/ui';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
import SignInPage from '~/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue';
@ -10,74 +10,48 @@ import BrowserSupportAlert from '~/jira_connect/subscriptions/components/browser
import createStore from '~/jira_connect/subscriptions/store';
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import { I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE } from '~/jira_connect/subscriptions/constants';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import { __ } from '~/locale';
import AccessorUtilities from '~/lib/utils/accessor';
import * as api from '~/jira_connect/subscriptions/api';
import { mockSubscription } from '../mock_data';
jest.mock('~/jira_connect/subscriptions/utils', () => ({
retrieveAlert: jest.fn().mockReturnValue({ message: 'error message' }),
getGitlabSignInURL: jest.fn(),
}));
jest.mock('~/jira_connect/subscriptions/utils');
describe('JiraConnectApp', () => {
let wrapper;
let store;
const mockCurrentUser = { name: 'root' };
const findAlert = () => wrapper.findByTestId('jira-connect-persisted-alert');
const findJiraConnectApp = () => wrapper.findByTestId('jira-connect-app');
const findAlertLink = () => findAlert().findComponent(GlLink);
const findSignInPage = () => wrapper.findComponent(SignInPage);
const findSubscriptionsPage = () => wrapper.findComponent(SubscriptionsPage);
const findUserLink = () => wrapper.findComponent(UserLink);
const findBrowserSupportAlert = () => wrapper.findComponent(BrowserSupportAlert);
const createComponent = ({ provide, mountFn = shallowMountExtended, initialState = {} } = {}) => {
const createComponent = ({ provide, initialState = {} } = {}) => {
store = createStore({ ...initialState, subscriptions: [mockSubscription] });
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = mountFn(JiraConnectApp, {
wrapper = shallowMountExtended(JiraConnectApp, {
store,
provide,
stubs: {
GlSprintf,
},
});
};
describe('template', () => {
describe.each`
scenario | usersPath | shouldRenderSignInPage | shouldRenderSubscriptionsPage
${'user is not signed in'} | ${'/users'} | ${true} | ${false}
${'user is signed in'} | ${undefined} | ${false} | ${true}
`('when $scenario', ({ usersPath, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
beforeEach(() => {
createComponent({
provide: {
usersPath,
},
});
});
it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
expect(findSignInPage().isVisible()).toBe(shouldRenderSignInPage);
if (shouldRenderSignInPage) {
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
}
});
it(`${
shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
} subscriptions page`, () => {
expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
if (shouldRenderSubscriptionsPage) {
expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
}
});
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(true);
});
it('renders UserLink component', () => {
createComponent({
provide: {
usersPath: '/user',
},
});
createComponent();
const userLink = findUserLink();
expect(userLink.exists()).toBe(true);
@ -87,148 +61,171 @@ describe('JiraConnectApp', () => {
userSignedIn: false,
});
});
});
describe('alert', () => {
it.each`
message | variant | alertShouldRender
${'Test error'} | ${'danger'} | ${true}
${'Test notice'} | ${'info'} | ${true}
${''} | ${undefined} | ${false}
${undefined} | ${undefined} | ${false}
it('renders only Jira Connect app', () => {
createComponent();
expect(findBrowserSupportAlert().exists()).toBe(false);
expect(findJiraConnectApp().exists()).toBe(true);
});
it('renders only BrowserSupportAlert when canUseCrypto is false', () => {
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(false);
createComponent();
expect(findBrowserSupportAlert().exists()).toBe(true);
expect(findJiraConnectApp().exists()).toBe(false);
});
describe.each`
scenario | currentUser | shouldRenderSignInPage | shouldRenderSubscriptionsPage
${'user is not signed in'} | ${undefined} | ${true} | ${false}
${'user is signed in'} | ${mockCurrentUser} | ${false} | ${true}
`(
'renders correct alert when message is `$message` and variant is `$variant`',
async ({ message, alertShouldRender, variant }) => {
createComponent();
'when $scenario',
({ currentUser, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
beforeEach(() => {
createComponent({
initialState: {
currentUser,
},
});
});
store.commit(SET_ALERT, { message, variant });
await nextTick();
it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
expect(findSignInPage().isVisible()).toBe(shouldRenderSignInPage);
if (shouldRenderSignInPage) {
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
}
});
const alert = findAlert();
expect(alert.exists()).toBe(alertShouldRender);
if (alertShouldRender) {
expect(alert.isVisible()).toBe(alertShouldRender);
expect(alert.html()).toContain(message);
expect(alert.props('variant')).toBe(variant);
expect(findAlertLink().exists()).toBe(false);
}
it(`${
shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
} subscriptions page`, () => {
expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
if (shouldRenderSubscriptionsPage) {
expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
}
});
},
);
it('hides alert on @dismiss event', async () => {
createComponent();
store.commit(SET_ALERT, { message: 'test message' });
await nextTick();
findAlert().vm.$emit('dismiss');
await nextTick();
expect(findAlert().exists()).toBe(false);
});
it('renders link when `linkUrl` is set', async () => {
createComponent({ provide: { usersPath: '' }, mountFn: mountExtended });
store.commit(SET_ALERT, {
message: __('test message %{linkStart}test link%{linkEnd}'),
linkUrl: 'https://gitlab.com',
});
await nextTick();
const alertLink = findAlertLink();
expect(alertLink.exists()).toBe(true);
expect(alertLink.text()).toContain('test link');
expect(alertLink.attributes('href')).toBe('https://gitlab.com');
});
describe('when alert is set in localStoage', () => {
it('renders alert on mount', () => {
createComponent();
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.html()).toContain('error message');
});
});
});
describe('when user signed out', () => {
describe('when sign in page emits `error` event', () => {
beforeEach(async () => {
createComponent({
provide: {
usersPath: '/mock',
},
});
beforeEach(() => {
createComponent();
findSignInPage().vm.$emit('error');
await nextTick();
});
it('displays alert', () => {
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.html()).toContain(I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE);
expect(alert.text()).toContain(I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE);
expect(alert.props('variant')).toBe('danger');
});
});
});
describe.each`
jiraConnectOauthEnabled | canUseCrypto | shouldShowAlert
${false} | ${false} | ${false}
${false} | ${true} | ${false}
${true} | ${false} | ${true}
${true} | ${true} | ${false}
`(
'when `jiraConnectOauth` feature flag is $jiraConnectOauthEnabled and `AccessorUtilities.canUseCrypto` returns $canUseCrypto',
({ jiraConnectOauthEnabled, canUseCrypto, shouldShowAlert }) => {
describe('when sign in page emits `sign-in-oauth` event', () => {
const mockSubscriptionsPath = '/mockSubscriptionsPath';
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(canUseCrypto);
jest.spyOn(api, 'fetchSubscriptions').mockResolvedValue({ data: { subscriptions: [] } });
createComponent({ provide: { glFeatures: { jiraConnectOauth: jiraConnectOauthEnabled } } });
createComponent({
initialState: {
currentUser: mockCurrentUser,
},
provide: {
subscriptionsPath: mockSubscriptionsPath,
},
});
findSignInPage().vm.$emit('sign-in-oauth');
});
it(`does ${shouldShowAlert ? '' : 'not'} render BrowserSupportAlert component`, () => {
expect(findBrowserSupportAlert().exists()).toBe(shouldShowAlert);
});
it(`does ${!shouldShowAlert ? '' : 'not'} render the main Jira Connect app template`, () => {
expect(wrapper.findByTestId('jira-connect-app').exists()).toBe(!shouldShowAlert);
});
},
);
describe('when `jiraConnectOauth` feature flag is enabled', () => {
const mockSubscriptionsPath = '/mockSubscriptionsPath';
beforeEach(async () => {
jest.spyOn(api, 'fetchSubscriptions').mockResolvedValue({ data: { subscriptions: [] } });
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(true);
createComponent({
initialState: {
currentUser: { name: 'root' },
},
provide: {
glFeatures: { jiraConnectOauth: true },
subscriptionsPath: mockSubscriptionsPath,
},
});
findSignInPage().vm.$emit('sign-in-oauth');
await nextTick();
});
describe('when oauth button emits `sign-in-oauth` event', () => {
it('dispatches `fetchSubscriptions` action', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchSubscriptions', mockSubscriptionsPath);
});
});
describe('alert', () => {
const mockAlertData = { message: 'error message' };
describe.each`
alertData | expectAlert
${undefined} | ${false}
${mockAlertData} | ${true}
`('when retrieveAlert returns $alertData', ({ alertData, expectAlert }) => {
beforeEach(() => {
retrieveAlert.mockReturnValue(alertData);
createComponent();
});
it(`${expectAlert ? 'renders' : 'does not render'} alert on mount`, () => {
const alert = findAlert();
expect(alert.exists()).toBe(expectAlert);
if (expectAlert) {
expect(alert.text()).toContain(mockAlertData.message);
}
});
});
it.each`
message | variant | alertShouldRender
${'Test error'} | ${'danger'} | ${true}
${'Test notice'} | ${'info'} | ${true}
${''} | ${undefined} | ${false}
${undefined} | ${undefined} | ${false}
`(
'renders correct alert when message is `$message` and variant is `$variant`',
async ({ message, alertShouldRender, variant }) => {
createComponent();
store.commit(SET_ALERT, { message, variant });
await nextTick();
const alert = findAlert();
expect(alert.exists()).toBe(alertShouldRender);
if (alertShouldRender) {
expect(alert.isVisible()).toBe(alertShouldRender);
expect(alert.text()).toContain(message);
expect(alert.props('variant')).toBe(variant);
expect(findAlertLink().exists()).toBe(false);
}
},
);
it('hides alert on @dismiss event', async () => {
createComponent();
store.commit(SET_ALERT, { message: 'test message' });
await nextTick();
findAlert().vm.$emit('dismiss');
await nextTick();
expect(findAlert().exists()).toBe(false);
});
it('renders link when `linkUrl` is set', async () => {
createComponent();
store.commit(SET_ALERT, {
message: __('test message %{linkStart}test link%{linkEnd}'),
linkUrl: 'https://gitlab.com',
});
await nextTick();
const alertLink = findAlertLink();
expect(alertLink.exists()).toBe(true);
expect(alertLink.text()).toContain('test link');
expect(alertLink.attributes('href')).toBe('https://gitlab.com');
});
});
});
});

View File

@ -3,7 +3,6 @@ import UserLink from '~/jira_connect/subscriptions/components/user_link.vue';
import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/jira_connect/subscriptions/utils', () => ({
getGitlabSignInURL: jest.fn().mockImplementation((path) => Promise.resolve(path)),
@ -23,28 +22,19 @@ describe('UserLink', () => {
});
};
const findSignInLink = () => wrapper.findByTestId('sign-in-link');
const findGitlabUserLink = () => wrapper.findByTestId('gitlab-user-link');
const findSprintf = () => wrapper.findComponent(GlSprintf);
const findOauthButton = () => wrapper.findComponent(SignInOauthButton);
describe.each`
userSignedIn | hasSubscriptions | expectGlSprintf | expectGlLink | expectOauthButton | jiraConnectOauthEnabled
${true} | ${false} | ${true} | ${false} | ${false} | ${false}
${false} | ${true} | ${false} | ${true} | ${false} | ${false}
${true} | ${true} | ${true} | ${false} | ${false} | ${false}
${false} | ${false} | ${false} | ${false} | ${false} | ${false}
${false} | ${true} | ${false} | ${false} | ${true} | ${true}
userSignedIn | hasSubscriptions | expectGlSprintf | expectOauthButton
${false} | ${false} | ${false} | ${false}
${false} | ${true} | ${false} | ${true}
${true} | ${false} | ${true} | ${false}
${true} | ${true} | ${true} | ${false}
`(
'when `userSignedIn` is $userSignedIn, `hasSubscriptions` is $hasSubscriptions, `jiraConnectOauthEnabled` is $jiraConnectOauthEnabled',
({
userSignedIn,
hasSubscriptions,
expectGlSprintf,
expectGlLink,
expectOauthButton,
jiraConnectOauthEnabled,
}) => {
'when `userSignedIn` is $userSignedIn, `hasSubscriptions` is $hasSubscriptions',
({ userSignedIn, hasSubscriptions, expectGlSprintf, expectOauthButton }) => {
it('renders template correctly', () => {
createComponent(
{
@ -53,39 +43,17 @@ describe('UserLink', () => {
},
{
provide: {
glFeatures: {
jiraConnectOauth: jiraConnectOauthEnabled,
},
oauthMetadata: {},
},
},
);
expect(findSprintf().exists()).toBe(expectGlSprintf);
expect(findSignInLink().exists()).toBe(expectGlLink);
expect(findOauthButton().exists()).toBe(expectOauthButton);
});
},
);
describe('sign in link', () => {
it('renders with correct href', async () => {
const mockUsersPath = '/user';
createComponent(
{
userSignedIn: false,
hasSubscriptions: true,
},
{ provide: { usersPath: mockUsersPath } },
);
await waitForPromises();
expect(findSignInLink().exists()).toBe(true);
expect(findSignInLink().attributes('href')).toBe(mockUsersPath);
});
});
describe('gitlab user link', () => {
describe.each`
current_username | gitlabUserPath | user | expectedUserHandle | expectedUserLink

View File

@ -19,20 +19,16 @@ describe('SignInGitlabCom', () => {
let wrapper;
let store;
const findSignInLegacyButton = () => wrapper.findComponent(SignInLegacyButton);
const findSignInOauthButton = () => wrapper.findComponent(SignInOauthButton);
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
const createComponent = ({ props, jiraConnectOauthEnabled } = {}) => {
const createComponent = ({ props } = {}) => {
store = createStore();
wrapper = shallowMount(SignInGitlabCom, {
store,
provide: {
...defaultProvide,
glFeatures: {
jiraConnectOauth: jiraConnectOauthEnabled,
},
},
propsData: props,
stubs: {
@ -48,57 +44,37 @@ describe('SignInGitlabCom', () => {
${'with subscriptions'} | ${true} | ${SignInGitlabCom.i18n.signInButtonTextWithSubscriptions}
${'without subscriptions'} | ${false} | ${I18N_DEFAULT_SIGN_IN_BUTTON_TEXT}
`('$scenario', ({ hasSubscriptions, signInButtonText }) => {
describe('when `jiraConnectOauthEnabled` feature flag is disabled', () => {
beforeEach(() => {
createComponent({
jiraConnectOauthEnabled: false,
props: {
hasSubscriptions,
},
});
});
it('renders legacy sign in button', () => {
const button = findSignInLegacyButton();
expect(button.props('usersPath')).toBe(mockUsersPath);
expect(button.text()).toMatchInterpolatedText(signInButtonText);
beforeEach(() => {
createComponent({
props: {
hasSubscriptions,
},
});
});
describe('when `jiraConnectOauthEnabled` feature flag is enabled', () => {
beforeEach(() => {
createComponent({
jiraConnectOauthEnabled: true,
props: {
hasSubscriptions,
},
describe('oauth sign in button', () => {
it('renders oauth sign in button', () => {
const button = findSignInOauthButton();
expect(button.text()).toMatchInterpolatedText(signInButtonText);
});
describe('when button emits `sign-in` event', () => {
it('emits `sign-in-oauth` event', () => {
const button = findSignInOauthButton();
const mockUser = { name: 'test' };
button.vm.$emit('sign-in', mockUser);
expect(wrapper.emitted('sign-in-oauth')[0]).toEqual([mockUser]);
});
});
describe('oauth sign in button', () => {
it('renders oauth sign in button', () => {
describe('when button emits `error` event', () => {
it('emits `error` event', () => {
const button = findSignInOauthButton();
expect(button.text()).toMatchInterpolatedText(signInButtonText);
});
button.vm.$emit('error');
describe('when button emits `sign-in` event', () => {
it('emits `sign-in-oauth` event', () => {
const button = findSignInOauthButton();
const mockUser = { name: 'test' };
button.vm.$emit('sign-in', mockUser);
expect(wrapper.emitted('sign-in-oauth')[0]).toEqual([mockUser]);
});
});
describe('when button emits `error` event', () => {
it('emits `error` event', () => {
const button = findSignInOauthButton();
button.vm.$emit('error');
expect(wrapper.emitted('error')).toHaveLength(1);
});
expect(wrapper.emitted('error')).toHaveLength(1);
});
});
});

View File

@ -12,20 +12,11 @@ describe('SignInPage', () => {
const findSignInGitlabCom = () => wrapper.findComponent(SignInGitlabCom);
const findSignInGitabMultiversion = () => wrapper.findComponent(SignInGitlabMultiversion);
const createComponent = ({
props = {},
jiraConnectOauthEnabled,
publicKeyStorageEnabled,
} = {}) => {
const createComponent = ({ props = {}, publicKeyStorageEnabled } = {}) => {
store = createStore();
wrapper = shallowMount(SignInPage, {
store,
provide: {
glFeatures: {
jiraConnectOauth: jiraConnectOauthEnabled,
},
},
propsData: {
hasSubscriptions: false,
publicKeyStorageEnabled,
@ -35,20 +26,13 @@ describe('SignInPage', () => {
};
it.each`
jiraConnectOauthEnabled | publicKeyStorageEnabled | shouldRenderDotCom | shouldRenderMultiversion
${false} | ${true} | ${true} | ${false}
${false} | ${false} | ${true} | ${false}
${true} | ${true} | ${false} | ${true}
${true} | ${false} | ${true} | ${false}
publicKeyStorageEnabled | shouldRenderDotCom | shouldRenderMultiversion
${true} | ${false} | ${true}
${false} | ${true} | ${false}
`(
'renders correct component when jiraConnectOauth is $jiraConnectOauthEnabled',
({
jiraConnectOauthEnabled,
publicKeyStorageEnabled,
shouldRenderDotCom,
shouldRenderMultiversion,
}) => {
createComponent({ jiraConnectOauthEnabled, publicKeyStorageEnabled });
'renders correct component when publicKeyStorageEnabled is $publicKeyStorageEnabled',
({ publicKeyStorageEnabled, shouldRenderDotCom, shouldRenderMultiversion }) => {
createComponent({ publicKeyStorageEnabled });
expect(findSignInGitlabCom().exists()).toBe(shouldRenderDotCom);
expect(findSignInGitabMultiversion().exists()).toBe(shouldRenderMultiversion);

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe RemovePhabricatorFromApplicationSettings, feature_category: :importers do
let(:settings) { table(:application_settings) }
let(:import_sources_with_phabricator) { %w[phabricator github git bitbucket bitbucket_server] }
let(:import_sources_without_phabricator) { %w[github git bitbucket bitbucket_server] }
describe "#up" do
it 'removes phabricator and preserves existing valid import sources' do
settings.create!(import_sources: import_sources_with_phabricator)
migrate!
expect(YAML.safe_load(ApplicationSetting.last.import_sources)).to eq(import_sources_without_phabricator)
end
end
end

View File

@ -25,9 +25,15 @@ RSpec.describe Gitlab::GithubImport::ReschedulingMethods, feature_category: :imp
end
end
context 'with an existing project' do
context 'with an existing project', :clean_gitlab_redis_cache do
let(:project) { create(:project, import_url: 'https://t0ken@github.com/repo/repo.git') }
before do
allow_next_instance_of(Gitlab::GithubImport::Client) do |instance|
allow(instance).to receive(:rate_limit_resets_in).and_return(14)
end
end
it 'notifies any waiters upon successfully importing the data' do
expect(worker)
.to receive(:try_import)
@ -57,13 +63,13 @@ RSpec.describe Gitlab::GithubImport::ReschedulingMethods, feature_category: :imp
expect(worker)
.not_to receive(:notify_waiter)
expect_next_instance_of(Gitlab::GithubImport::Client) do |instance|
expect(instance).to receive(:rate_limit_resets_in).and_return(14)
end
expect(worker)
.to receive(:object_type)
.and_return(:pull_request)
expect(worker.class)
.to receive(:perform_in)
.with(14, project.id, { 'number' => 2 }, '123')
.with(15, project.id, { 'number' => 2 }, '123')
worker.perform(project.id, { 'number' => 2 }, '123')
end

View File

@ -50,5 +50,21 @@ RSpec.describe Gitlab::GithubImport::ImportPullRequestWorker, feature_category:
worker.import(project, client, hash)
end
describe '#importer_class' do
it { expect(worker.importer_class).to eq Gitlab::GithubImport::Importer::PullRequestImporter }
end
describe '#representation_class' do
it { expect(worker.representation_class).to eq Gitlab::GithubImport::Representation::PullRequest }
end
describe '#object_type' do
it { expect(worker.object_type).to eq(:pull_request) }
end
describe '#parallel_import_batch' do
it { expect(worker.parallel_import_batch).to eq({ size: 200, delay: 1.minute }) }
end
end
end