Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
94ecc00f47
commit
0512d12bf1
2
Gemfile
2
Gemfile
|
@ -539,4 +539,4 @@ gem 'ipaddress', '~> 0.8.3'
|
|||
|
||||
gem 'parslet', '~> 1.8'
|
||||
|
||||
gem 'ipynbdiff', '0.3.7'
|
||||
gem 'ipynbdiff', '0.3.8'
|
||||
|
|
|
@ -641,7 +641,7 @@ GEM
|
|||
invisible_captcha (1.1.0)
|
||||
rails (>= 4.2)
|
||||
ipaddress (0.8.3)
|
||||
ipynbdiff (0.3.7)
|
||||
ipynbdiff (0.3.8)
|
||||
diffy (= 3.3.0)
|
||||
json (= 2.5.1)
|
||||
jaeger-client (1.1.0)
|
||||
|
@ -1510,7 +1510,7 @@ DEPENDENCIES
|
|||
icalendar
|
||||
invisible_captcha (~> 1.1.0)
|
||||
ipaddress (~> 0.8.3)
|
||||
ipynbdiff (= 0.3.7)
|
||||
ipynbdiff (= 0.3.8)
|
||||
jira-ruby (~> 2.1.4)
|
||||
js_regex (~> 3.7)
|
||||
json (~> 2.5.1)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80"><defs><style>.cls-1{fill:#7a869a;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}</style><linearGradient id="linear-gradient" x1="38.11" y1="18.54" x2="23.17" y2="33.48" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#344563"/><stop offset="1" stop-color="#7a869a"/></linearGradient><linearGradient id="linear-gradient-2" x1="42.07" y1="61.47" x2="56.98" y2="46.55" xlink:href="#linear-gradient"/></defs><title>jira software-icon-gradient-neutral</title><path class="cls-1" d="M74.18,38,43,6.9l-3-3h0L16.58,27.32h0L5.86,38a2.86,2.86,0,0,0,0,4.05L27.28,63.51,40,76.25,63.47,52.81l.36-.36L74.18,42.09A2.86,2.86,0,0,0,74.18,38ZM40,50.77l-10.7-10.7L40,29.37l10.7,10.7Z"/><path class="cls-2" d="M40,29.37A18,18,0,0,1,40,4L16.54,27.37,29.28,40.11,40,29.37Z"/><path class="cls-3" d="M50.75,40,40,50.77a18,18,0,0,1,0,25.48h0L63.5,52.78Z"/></svg>
|
Before Width: | Height: | Size: 1016 B |
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui';
|
||||
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlModalDirective } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants';
|
||||
|
||||
|
@ -8,46 +8,33 @@ export default {
|
|||
modalId: INSTALL_AGENT_MODAL_ID,
|
||||
multipleClustersDocsUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
|
||||
installDocsUrl: helpPagePath('administration/clusters/kas'),
|
||||
getStartedDocsUrl: helpPagePath('user/clusters/agent/index', {
|
||||
anchor: 'define-a-configuration-repository',
|
||||
}),
|
||||
components: {
|
||||
GlButton,
|
||||
GlEmptyState,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
GlAlert,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
inject: ['emptyStateImage', 'projectPath'],
|
||||
inject: ['emptyStateImage'],
|
||||
props: {
|
||||
hasConfigurations: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isChildComponent: {
|
||||
default: false,
|
||||
required: false,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
repositoryPath() {
|
||||
return `/${this.projectPath}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-empty-state :svg-path="emptyStateImage" title="" class="agents-empty-state">
|
||||
<template #description>
|
||||
<p class="mw-460 gl-mx-auto gl-text-left">
|
||||
<p class="gl-text-left">
|
||||
{{ $options.i18n.introText }}
|
||||
</p>
|
||||
<p class="mw-460 gl-mx-auto gl-text-left">
|
||||
<p class="gl-text-left">
|
||||
<gl-sprintf :message="$options.i18n.multipleClustersText">
|
||||
<template #link="{ content }">
|
||||
<gl-link
|
||||
|
@ -61,42 +48,17 @@ export default {
|
|||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<p class="mw-460 gl-mx-auto">
|
||||
<p>
|
||||
<gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link">
|
||||
{{ $options.i18n.learnMoreText }}
|
||||
</gl-link>
|
||||
</p>
|
||||
|
||||
<gl-alert
|
||||
v-if="!hasConfigurations"
|
||||
variant="warning"
|
||||
class="gl-mb-5 text-left"
|
||||
:dismissible="false"
|
||||
>
|
||||
{{ $options.i18n.warningText }}
|
||||
|
||||
<template #actions>
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="info"
|
||||
:href="$options.getStartedDocsUrl"
|
||||
target="_blank"
|
||||
class="gl-ml-0!"
|
||||
>
|
||||
{{ $options.i18n.readMoreText }}
|
||||
</gl-button>
|
||||
<gl-button category="secondary" variant="info" :href="repositoryPath">
|
||||
{{ $options.i18n.repositoryButtonText }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</gl-alert>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<gl-button
|
||||
v-if="!isChildComponent"
|
||||
v-gl-modal-directive="$options.modalId"
|
||||
:disabled="!hasConfigurations"
|
||||
data-testid="integration-primary-button"
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
|
|
|
@ -86,9 +86,6 @@ export default {
|
|||
treePageInfo() {
|
||||
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
|
||||
},
|
||||
hasConfigurations() {
|
||||
return Boolean(this.agents?.project?.repository?.tree?.trees?.nodes?.length);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reloadAgents() {
|
||||
|
@ -161,11 +158,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<agent-empty-state
|
||||
v-else
|
||||
:has-configurations="hasConfigurations"
|
||||
:is-child-component="isChildComponent"
|
||||
/>
|
||||
<agent-empty-state v-else :is-child-component="isChildComponent" />
|
||||
</section>
|
||||
|
||||
<gl-alert v-else variant="danger" :dismissible="false">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants';
|
||||
import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql';
|
||||
|
||||
export default {
|
||||
name: 'AvailableAgentsDropdown',
|
||||
|
@ -10,36 +9,22 @@ export default {
|
|||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
props: {
|
||||
isRegistering: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
agents: {
|
||||
query: agentConfigurations,
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.projectPath,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
this.populateAvailableAgents(data);
|
||||
},
|
||||
availableAgents: {
|
||||
required: true,
|
||||
type: Array,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
availableAgents: [],
|
||||
selectedAgent: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.agents.loading;
|
||||
},
|
||||
dropdownText() {
|
||||
if (this.isRegistering) {
|
||||
return this.$options.i18n.registeringAgent;
|
||||
|
@ -58,18 +43,11 @@ export default {
|
|||
isSelected(agent) {
|
||||
return this.selectedAgent === agent;
|
||||
},
|
||||
populateAvailableAgents(data) {
|
||||
const installedAgents = data?.project?.clusterAgents?.nodes.map((agent) => agent.name) ?? [];
|
||||
const configuredAgents =
|
||||
data?.project?.agentConfigurations?.nodes.map((config) => config.agentName) ?? [];
|
||||
|
||||
this.availableAgents = configuredAgents.filter((agent) => !installedAgents.includes(agent));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-dropdown :text="dropdownText" :loading="isLoading || isRegistering">
|
||||
<gl-dropdown :text="dropdownText" :loading="isRegistering">
|
||||
<gl-dropdown-item
|
||||
v-for="agent in availableAgents"
|
||||
:key="agent"
|
||||
|
|
|
@ -12,16 +12,16 @@ import { helpPagePath } from '~/helpers/help_page_helper';
|
|||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import CodeBlock from '~/vue_shared/components/code_block.vue';
|
||||
import { generateAgentRegistrationCommand } from '../clusters_util';
|
||||
import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL } from '../constants';
|
||||
import { addAgentToStore } from '../graphql/cache_update';
|
||||
import { INSTALL_AGENT_MODAL_ID, I18N_AGENT_MODAL, KAS_DISABLED_ERROR } from '../constants';
|
||||
import { addAgentToStore, addAgentConfigToStore } from '../graphql/cache_update';
|
||||
import createAgent from '../graphql/mutations/create_agent.mutation.graphql';
|
||||
import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
|
||||
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
|
||||
import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql';
|
||||
import AvailableAgentsDropdown from './available_agents_dropdown.vue';
|
||||
|
||||
export default {
|
||||
modalId: INSTALL_AGENT_MODAL_ID,
|
||||
i18n: I18N_INSTALL_AGENT_MODAL,
|
||||
components: {
|
||||
AvailableAgentsDropdown,
|
||||
ClipboardButton,
|
||||
|
@ -34,7 +34,7 @@ export default {
|
|||
GlModal,
|
||||
GlSprintf,
|
||||
},
|
||||
inject: ['projectPath', 'kasAddress'],
|
||||
inject: ['projectPath', 'kasAddress', 'emptyStateImage'],
|
||||
props: {
|
||||
defaultBranchName: {
|
||||
default: '.noBranch',
|
||||
|
@ -46,6 +46,22 @@ export default {
|
|||
type: Number,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
agents: {
|
||||
query: agentConfigurations,
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.projectPath,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
this.populateAvailableAgents(data);
|
||||
},
|
||||
error(error) {
|
||||
this.kasDisabled = error?.message?.indexOf(KAS_DISABLED_ERROR) >= 0;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
registering: false,
|
||||
|
@ -53,6 +69,8 @@ export default {
|
|||
agentToken: null,
|
||||
error: null,
|
||||
clusterAgent: null,
|
||||
availableAgents: [],
|
||||
kasDisabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -63,7 +81,7 @@ export default {
|
|||
return !this.registering && this.agentName !== null;
|
||||
},
|
||||
canCancel() {
|
||||
return !this.registered && !this.registering;
|
||||
return !this.registered && !this.registering && this.isRegisterModal;
|
||||
},
|
||||
agentRegistrationCommand() {
|
||||
return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
|
||||
|
@ -76,6 +94,9 @@ export default {
|
|||
advancedInstallPath() {
|
||||
return helpPagePath('user/clusters/agent/install/index', { anchor: 'advanced-installation' });
|
||||
},
|
||||
enableKasPath() {
|
||||
return helpPagePath('administration/clusters/kas');
|
||||
},
|
||||
getAgentsQueryVariables() {
|
||||
return {
|
||||
defaultBranchName: this.defaultBranchName,
|
||||
|
@ -84,6 +105,29 @@ export default {
|
|||
projectPath: this.projectPath,
|
||||
};
|
||||
},
|
||||
installAgentPath() {
|
||||
return helpPagePath('user/clusters/agent/index', {
|
||||
anchor: 'define-a-configuration-repository',
|
||||
});
|
||||
},
|
||||
i18n() {
|
||||
return I18N_AGENT_MODAL[this.modalType];
|
||||
},
|
||||
repositoryPath() {
|
||||
return `/${this.projectPath}`;
|
||||
},
|
||||
modalType() {
|
||||
return !this.availableAgents?.length && !this.registered ? 'install' : 'register';
|
||||
},
|
||||
modalSize() {
|
||||
return this.isInstallModal ? 'sm' : 'md';
|
||||
},
|
||||
isInstallModal() {
|
||||
return this.modalType === 'install';
|
||||
},
|
||||
isRegisterModal() {
|
||||
return this.modalType === 'register';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setAgentName(name) {
|
||||
|
@ -96,8 +140,16 @@ export default {
|
|||
this.registering = false;
|
||||
this.agentName = null;
|
||||
this.agentToken = null;
|
||||
this.clusterAgent = null;
|
||||
this.error = null;
|
||||
},
|
||||
populateAvailableAgents(data) {
|
||||
const installedAgents = data?.project?.clusterAgents?.nodes.map((agent) => agent.name) ?? [];
|
||||
const configuredAgents =
|
||||
data?.project?.agentConfigurations?.nodes.map((config) => config.agentName) ?? [];
|
||||
|
||||
this.availableAgents = configuredAgents.filter((agent) => !installedAgents.includes(agent));
|
||||
},
|
||||
createAgentMutation() {
|
||||
return this.$apollo
|
||||
.mutate({
|
||||
|
@ -117,7 +169,9 @@ export default {
|
|||
);
|
||||
},
|
||||
})
|
||||
.then(({ data: { createClusterAgent } }) => createClusterAgent);
|
||||
.then(({ data: { createClusterAgent } }) => {
|
||||
return createClusterAgent;
|
||||
});
|
||||
},
|
||||
createAgentTokenMutation(agendId) {
|
||||
return this.$apollo
|
||||
|
@ -129,6 +183,17 @@ export default {
|
|||
name: this.agentName,
|
||||
},
|
||||
},
|
||||
update: (store, { data: { clusterAgentTokenCreate } }) => {
|
||||
addAgentConfigToStore(
|
||||
store,
|
||||
clusterAgentTokenCreate,
|
||||
this.clusterAgent,
|
||||
agentConfigurations,
|
||||
{
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
);
|
||||
},
|
||||
})
|
||||
.then(({ data: { clusterAgentTokenCreate } }) => clusterAgentTokenCreate);
|
||||
},
|
||||
|
@ -158,7 +223,7 @@ export default {
|
|||
if (error) {
|
||||
this.error = error.message;
|
||||
} else {
|
||||
this.error = this.$options.i18n.unknownError;
|
||||
this.error = this.i18n.unknownError;
|
||||
}
|
||||
} finally {
|
||||
this.registering = false;
|
||||
|
@ -172,115 +237,142 @@ export default {
|
|||
<gl-modal
|
||||
ref="modal"
|
||||
:modal-id="$options.modalId"
|
||||
:title="$options.i18n.modalTitle"
|
||||
:title="i18n.modalTitle"
|
||||
:size="modalSize"
|
||||
static
|
||||
lazy
|
||||
@hidden="resetModal"
|
||||
>
|
||||
<template v-if="!registered">
|
||||
<p>
|
||||
<strong>{{ $options.i18n.selectAgentTitle }}</strong>
|
||||
</p>
|
||||
<template v-if="isRegisterModal">
|
||||
<template v-if="!registered">
|
||||
<p>
|
||||
<strong>{{ i18n.selectAgentTitle }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-sprintf :message="$options.i18n.selectAgentBody">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="basicInstallPath" target="_blank"> {{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<p class="gl-mb-0">{{ i18n.selectAgentBody }}</p>
|
||||
<p>
|
||||
<gl-link :href="basicInstallPath" target="_blank"> {{ i18n.learnMoreLink }}</gl-link>
|
||||
</p>
|
||||
|
||||
<form>
|
||||
<gl-form-group label-for="agent-name">
|
||||
<available-agents-dropdown
|
||||
class="gl-w-70p"
|
||||
:is-registering="registering"
|
||||
@agentSelected="setAgentName"
|
||||
/>
|
||||
</gl-form-group>
|
||||
</form>
|
||||
<form>
|
||||
<gl-form-group label-for="agent-name">
|
||||
<available-agents-dropdown
|
||||
class="gl-w-70p"
|
||||
:is-registering="registering"
|
||||
:available-agents="availableAgents"
|
||||
@agentSelected="setAgentName"
|
||||
/>
|
||||
</gl-form-group>
|
||||
</form>
|
||||
|
||||
<p v-if="error">
|
||||
<gl-alert
|
||||
:title="$options.i18n.registrationErrorTitle"
|
||||
variant="danger"
|
||||
:dismissible="false"
|
||||
>
|
||||
{{ error }}
|
||||
</gl-alert>
|
||||
</p>
|
||||
<p v-if="error">
|
||||
<gl-alert :title="i18n.registrationErrorTitle" variant="danger" :dismissible="false">
|
||||
{{ error }}
|
||||
</gl-alert>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<p>
|
||||
<strong>{{ i18n.tokenTitle }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-sprintf :message="i18n.tokenBody">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="basicInstallPath" target="_blank"> {{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-alert :title="i18n.tokenSingleUseWarningTitle" variant="warning" :dismissible="false">
|
||||
{{ i18n.tokenSingleUseWarningBody }}
|
||||
</gl-alert>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-form-input-group readonly :value="agentToken" :select-on-click="true">
|
||||
<template #append>
|
||||
<clipboard-button :text="agentToken" :title="i18n.copyToken" />
|
||||
</template>
|
||||
</gl-form-input-group>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>{{ i18n.basicInstallTitle }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ i18n.basicInstallBody }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code-block :code="agentRegistrationCommand" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>{{ i18n.advancedInstallTitle }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-sprintf :message="i18n.advancedInstallBody">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="advancedInstallPath" target="_blank"> {{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<p>
|
||||
<strong>{{ $options.i18n.tokenTitle }}</strong>
|
||||
</p>
|
||||
<div class="gl-text-center gl-mb-5">
|
||||
<img :alt="i18n.altText" :src="emptyStateImage" height="100" />
|
||||
</div>
|
||||
<p>{{ i18n.modalBody }}</p>
|
||||
|
||||
<p>
|
||||
<gl-sprintf :message="$options.i18n.tokenBody">
|
||||
<p v-if="kasDisabled">
|
||||
<gl-sprintf :message="i18n.enableKasText">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="basicInstallPath" target="_blank"> {{ content }}</gl-link>
|
||||
<gl-link :href="enableKasPath"> {{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-alert
|
||||
:title="$options.i18n.tokenSingleUseWarningTitle"
|
||||
variant="warning"
|
||||
:dismissible="false"
|
||||
>
|
||||
{{ $options.i18n.tokenSingleUseWarningBody }}
|
||||
</gl-alert>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-form-input-group readonly :value="agentToken" :select-on-click="true">
|
||||
<template #append>
|
||||
<clipboard-button :text="agentToken" :title="$options.i18n.copyToken" />
|
||||
</template>
|
||||
</gl-form-input-group>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>{{ $options.i18n.basicInstallTitle }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ $options.i18n.basicInstallBody }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code-block :code="agentRegistrationCommand" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>{{ $options.i18n.advancedInstallTitle }}</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<gl-sprintf :message="$options.i18n.advancedInstallBody">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="advancedInstallPath" target="_blank"> {{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<p class="gl-mb-0">
|
||||
<gl-link :href="installAgentPath">
|
||||
{{ i18n.docsLinkText }}
|
||||
</gl-link>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #modal-footer>
|
||||
<gl-button v-if="canCancel" @click="closeModal">{{ $options.i18n.cancel }} </gl-button>
|
||||
<gl-button v-if="canCancel" @click="closeModal">{{ i18n.cancel }} </gl-button>
|
||||
|
||||
<gl-button v-if="registered" variant="confirm" category="primary" @click="closeModal"
|
||||
>{{ $options.i18n.close }}
|
||||
>{{ i18n.close }}
|
||||
</gl-button>
|
||||
|
||||
<gl-button
|
||||
v-else
|
||||
v-else-if="isRegisterModal"
|
||||
:disabled="!nextButtonDisabled"
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
@click="registerAgent"
|
||||
>{{ $options.i18n.registerAgentButton }}
|
||||
>{{ i18n.registerAgentButton }}
|
||||
</gl-button>
|
||||
|
||||
<gl-button
|
||||
v-if="isInstallModal"
|
||||
:href="repositoryPath"
|
||||
variant="confirm"
|
||||
category="secondary"
|
||||
data-testid="agent-secondary-button"
|
||||
>{{ i18n.secondaryButton }}
|
||||
</gl-button>
|
||||
|
||||
<gl-button v-if="isInstallModal" variant="confirm" category="primary" @click="closeModal"
|
||||
>{{ i18n.done }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</gl-modal>
|
||||
|
|
|
@ -64,45 +64,63 @@ export const STATUSES = {
|
|||
creating: { title: __('Creating') },
|
||||
};
|
||||
|
||||
export const I18N_INSTALL_AGENT_MODAL = {
|
||||
registerAgentButton: s__('ClusterAgents|Register Agent'),
|
||||
close: __('Close'),
|
||||
cancel: __('Cancel'),
|
||||
export const I18N_AGENT_MODAL = {
|
||||
register: {
|
||||
registerAgentButton: s__('ClusterAgents|Register Agent'),
|
||||
close: __('Close'),
|
||||
cancel: __('Cancel'),
|
||||
|
||||
modalTitle: s__('ClusterAgents|Install new Agent'),
|
||||
modalTitle: s__('ClusterAgents|Connect with Agent'),
|
||||
|
||||
selectAgentTitle: s__('ClusterAgents|Select which Agent you want to install'),
|
||||
selectAgentBody: s__(
|
||||
`ClusterAgents|Select the Agent you want to register with GitLab and install on your cluster. To learn more about the Kubernetes Agent registration process %{linkStart}go to the documentation%{linkEnd}.`,
|
||||
),
|
||||
selectAgentTitle: s__('ClusterAgents|Select which Agent you want to install'),
|
||||
selectAgentBody: s__(
|
||||
'ClusterAgents|Select an Agent to register with GitLab and install on your cluster.',
|
||||
),
|
||||
learnMoreLink: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent registration.'),
|
||||
|
||||
copyToken: s__('ClusterAgents|Copy token'),
|
||||
tokenTitle: s__('ClusterAgents|Registration token'),
|
||||
tokenBody: s__(
|
||||
`ClusterAgents|The registration token will be used to connect the Agent on your cluster to GitLab. To learn more about the registration tokens and how they are used %{linkStart}go to the documentation%{linkEnd}.`,
|
||||
),
|
||||
copyToken: s__('ClusterAgents|Copy token'),
|
||||
tokenTitle: s__('ClusterAgents|Registration token'),
|
||||
tokenBody: s__(
|
||||
`ClusterAgents|The registration token will be used to connect the Agent on your cluster to GitLab. To learn more about the registration tokens and how they are used %{linkStart}go to the documentation%{linkEnd}.`,
|
||||
),
|
||||
|
||||
tokenSingleUseWarningTitle: s__(
|
||||
'ClusterAgents|The token value will not be shown again after you close this window.',
|
||||
),
|
||||
tokenSingleUseWarningBody: s__(
|
||||
`ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window.`,
|
||||
),
|
||||
tokenSingleUseWarningTitle: s__(
|
||||
'ClusterAgents|The token value will not be shown again after you close this window.',
|
||||
),
|
||||
tokenSingleUseWarningBody: s__(
|
||||
`ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window.`,
|
||||
),
|
||||
|
||||
basicInstallTitle: s__('ClusterAgents|Recommended installation method'),
|
||||
basicInstallBody: __(
|
||||
`Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command.`,
|
||||
),
|
||||
basicInstallTitle: s__('ClusterAgents|Recommended installation method'),
|
||||
basicInstallBody: __(
|
||||
`Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command.`,
|
||||
),
|
||||
|
||||
advancedInstallTitle: s__('ClusterAgents|Alternative installation methods'),
|
||||
advancedInstallBody: s__(
|
||||
'ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}.',
|
||||
),
|
||||
advancedInstallTitle: s__('ClusterAgents|Alternative installation methods'),
|
||||
advancedInstallBody: s__(
|
||||
'ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}.',
|
||||
),
|
||||
|
||||
registrationErrorTitle: __('Failed to register Agent'),
|
||||
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
|
||||
registrationErrorTitle: __('Failed to register Agent'),
|
||||
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
|
||||
},
|
||||
install: {
|
||||
modalTitle: s__('ClusterAgents|Install new Agent'),
|
||||
modalBody: s__(
|
||||
'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
|
||||
),
|
||||
docsLinkText: s__('ClusterAgents|Learn more about installing a GitLab Kubernetes Agent'),
|
||||
enableKasText: s__(
|
||||
'ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}',
|
||||
),
|
||||
altText: s__('ClusterAgents|GitLab Kubernetes Agent'),
|
||||
secondaryButton: s__('ClusterAgents|Go to the repository'),
|
||||
done: __('Done'),
|
||||
},
|
||||
};
|
||||
|
||||
export const KAS_DISABLED_ERROR = 'Gitlab::Kas::Client::ConfigurationError';
|
||||
|
||||
export const I18N_AVAILABLE_AGENTS_DROPDOWN = {
|
||||
selectAgent: s__('ClusterAgents|Select an Agent'),
|
||||
registeringAgent: s__('ClusterAgents|Registering Agent'),
|
||||
|
@ -149,11 +167,6 @@ export const I18N_AGENTS_EMPTY_STATE = {
|
|||
'ClusterAgents|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}',
|
||||
),
|
||||
learnMoreText: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent.'),
|
||||
warningText: s__(
|
||||
'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
|
||||
),
|
||||
readMoreText: s__('ClusterAgents|Read more about getting started'),
|
||||
repositoryButtonText: s__('ClusterAgents|Go to the repository'),
|
||||
primaryButtonText: s__('ClusterAgents|Connect with a GitLab Agent'),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,29 +1,64 @@
|
|||
import produce from 'immer';
|
||||
import { getAgentConfigPath } from '../clusters_util';
|
||||
|
||||
export const hasErrors = ({ errors = [] }) => errors?.length;
|
||||
|
||||
export function addAgentToStore(store, createClusterAgent, query, variables) {
|
||||
const { clusterAgent } = createClusterAgent;
|
||||
const sourceData = store.readQuery({
|
||||
query,
|
||||
variables,
|
||||
});
|
||||
if (!hasErrors(createClusterAgent)) {
|
||||
const { clusterAgent } = createClusterAgent;
|
||||
const sourceData = store.readQuery({
|
||||
query,
|
||||
variables,
|
||||
});
|
||||
|
||||
const data = produce(sourceData, (draftData) => {
|
||||
const configuration = {
|
||||
name: clusterAgent.name,
|
||||
path: getAgentConfigPath(clusterAgent.name),
|
||||
webPath: clusterAgent.webPath,
|
||||
__typename: 'TreeEntry',
|
||||
};
|
||||
const data = produce(sourceData, (draftData) => {
|
||||
const configuration = {
|
||||
name: clusterAgent.name,
|
||||
path: getAgentConfigPath(clusterAgent.name),
|
||||
webPath: clusterAgent.webPath,
|
||||
__typename: 'TreeEntry',
|
||||
};
|
||||
|
||||
draftData.project.clusterAgents.nodes.push(clusterAgent);
|
||||
draftData.project.clusterAgents.count += 1;
|
||||
draftData.project.repository.tree.trees.nodes.push(configuration);
|
||||
});
|
||||
draftData.project.clusterAgents.nodes.push(clusterAgent);
|
||||
draftData.project.clusterAgents.count += 1;
|
||||
draftData.project.repository.tree.trees.nodes.push(configuration);
|
||||
});
|
||||
|
||||
store.writeQuery({
|
||||
query,
|
||||
variables,
|
||||
data,
|
||||
});
|
||||
store.writeQuery({
|
||||
query,
|
||||
variables,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function addAgentConfigToStore(
|
||||
store,
|
||||
clusterAgentTokenCreate,
|
||||
clusterAgent,
|
||||
query,
|
||||
variables,
|
||||
) {
|
||||
if (!hasErrors(clusterAgentTokenCreate)) {
|
||||
const sourceData = store.readQuery({
|
||||
query,
|
||||
variables,
|
||||
});
|
||||
|
||||
const data = produce(sourceData, (draftData) => {
|
||||
const configuration = {
|
||||
agentName: clusterAgent.name,
|
||||
__typename: 'AgentConfiguration',
|
||||
};
|
||||
|
||||
draftData.project.clusterAgents.nodes.push(clusterAgent);
|
||||
draftData.project.agentConfigurations.nodes.push(configuration);
|
||||
});
|
||||
|
||||
store.writeQuery({
|
||||
query,
|
||||
variables,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<script>
|
||||
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
|
||||
import {
|
||||
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
|
||||
GlSafeHtmlDirective as SafeHtml,
|
||||
} from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
|
||||
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
|
||||
|
@ -17,6 +20,9 @@ export default {
|
|||
DiffViewer,
|
||||
ImageDiffOverlay,
|
||||
},
|
||||
directives: {
|
||||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
discussion: {
|
||||
type: Object,
|
||||
|
@ -92,11 +98,7 @@ export default {
|
|||
>
|
||||
<td :class="line.type" class="diff-line-num old_line">{{ line.old_line }}</td>
|
||||
<td :class="line.type" class="diff-line-num new_line">{{ line.new_line }}</td>
|
||||
<td
|
||||
:class="line.type"
|
||||
class="line_content"
|
||||
v-html="trimChar(line.rich_text) /* eslint-disable-line vue/no-v-html */"
|
||||
></td>
|
||||
<td v-safe-html="trimChar(line.rich_text)" :class="line.type" class="line_content"></td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="!hasTruncatedDiffLines" class="line_holder line-holder-placeholder">
|
||||
|
|
|
@ -7,20 +7,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.agents-empty-state {
|
||||
.text-content {
|
||||
@include gl-max-w-full;
|
||||
@include media-breakpoint-up(lg) {
|
||||
max-width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-alert-actions {
|
||||
@include gl-mt-0;
|
||||
@include gl-flex-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-card-body {
|
||||
@include media-breakpoint-up(sm) {
|
||||
@include gl-pt-2;
|
||||
|
|
|
@ -14,7 +14,7 @@ module Resolvers
|
|||
return [] unless can_read_agent_configuration?
|
||||
|
||||
kas_client.list_agent_config_files(project: project)
|
||||
rescue GRPC::BadStatus => e
|
||||
rescue GRPC::BadStatus, Gitlab::Kas::Client::ConfigurationError => e
|
||||
raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class BaseEdge < GraphQL::Types::Relay::BaseEdge
|
||||
field_class Types::BaseField
|
||||
end
|
||||
end
|
|
@ -78,6 +78,8 @@ module Types
|
|||
attr_reader :feature_flag
|
||||
|
||||
def field_authorized?(object, ctx)
|
||||
object = object.node if object.is_a?(GraphQL::Pagination::Connection::Edge)
|
||||
|
||||
authorization.ok?(object, ctx[:current_user])
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ module Types
|
|||
prepend Gitlab::Graphql::MarkdownField
|
||||
|
||||
field_class Types::BaseField
|
||||
edge_type_class Types::BaseEdge
|
||||
|
||||
def self.accepts(*types)
|
||||
@accepts ||= []
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Types
|
||||
module Ci
|
||||
# rubocop: disable Graphql/AuthorizeTypes
|
||||
class RunnerWebUrlEdge < GraphQL::Types::Relay::BaseEdge
|
||||
class RunnerWebUrlEdge < ::Types::BaseEdge
|
||||
include FindClosest
|
||||
|
||||
field :web_url, GraphQL::Types::String, null: true,
|
||||
|
|
|
@ -6,6 +6,7 @@ module MergeRequests
|
|||
return unless merge_request.discussions_resolved?
|
||||
|
||||
SystemNoteService.resolve_all_discussions(merge_request, project, current_user)
|
||||
execute_hooks(merge_request, 'update')
|
||||
notification_service.async.resolve_all_discussions(merge_request, current_user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2717,15 +2717,6 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: propagate_service_template
|
||||
:worker_name: PropagateServiceTemplateWorker
|
||||
:feature_category: :integrations
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent:
|
||||
:tags: []
|
||||
- :name: reactive_caching
|
||||
:worker_name: ReactiveCachingWorker
|
||||
:feature_category: :not_owned
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# No longer in use https://gitlab.com/groups/gitlab-org/-/epics/5672
|
||||
# To be removed https://gitlab.com/gitlab-org/gitlab/-/issues/335178
|
||||
class PropagateServiceTemplateWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ApplicationWorker
|
||||
|
||||
data_consistency :always
|
||||
|
||||
sidekiq_options retry: 3
|
||||
|
||||
feature_category :integrations
|
||||
|
||||
LEASE_TIMEOUT = 4.hours.to_i
|
||||
|
||||
def perform(template_id)
|
||||
return unless try_obtain_lease_for(template_id)
|
||||
|
||||
::Integrations::PropagateTemplateService.propagate(Integration.find_by_id(template_id))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def try_obtain_lease_for(template_id)
|
||||
Gitlab::ExclusiveLease
|
||||
.new("propagate_service_template_worker:#{template_id}", timeout: LEASE_TIMEOUT)
|
||||
.try_obtain
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: between_commits_via_list_commits
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74273
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345458
|
||||
milestone: '14.5'
|
||||
type: development
|
||||
group: group::gitaly
|
||||
default_enabled: false
|
|
@ -351,8 +351,6 @@
|
|||
- 1
|
||||
- - propagate_integration_project
|
||||
- 1
|
||||
- - propagate_service_template
|
||||
- 1
|
||||
- - reactive_caching
|
||||
- 1
|
||||
- - rebase
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleRecalculateVulnerabilityFindingSignaturesForFindings < Gitlab::Database::Migration[1.0]
|
||||
MIGRATION = 'RecalculateVulnerabilityFindingSignaturesForFindings'
|
||||
BATCH_SIZE = 1_000
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
return unless Gitlab.ee?
|
||||
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('vulnerability_finding_signatures'),
|
||||
MIGRATION,
|
||||
DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
track_jobs: true
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemovePropagateServiceTemplateWorker < Gitlab::Database::Migration[1.0]
|
||||
def up
|
||||
Sidekiq::Queue.new('propagate_service_template').clear
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
b372da05f40fa67680b6a28ddf9bed3dc4b95795c144bf4367e4826b5cd64d6b
|
|
@ -0,0 +1 @@
|
|||
d16d62b2984586540a99aa5fc67de6459a4cd473089ddbae8d45e8783863d78d
|
|
@ -400,7 +400,7 @@ limit is checked every time a new trigger is created.
|
|||
If a new trigger would cause the total number of pipeline triggers to exceed the
|
||||
limit, the trigger is considered invalid.
|
||||
|
||||
Set the limit to `0` to disable it. Defaults to `0` on self-managed instances.
|
||||
Set the limit to `0` to disable it. Defaults to `150` on self-managed instances.
|
||||
|
||||
To set this limit to `100` on a self-managed installation, run the following in the
|
||||
[GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session):
|
||||
|
|
|
@ -354,7 +354,7 @@ gitlab_rails['object_store']['connection'] = {
|
|||
'provider' => 'AzureRM',
|
||||
'azure_storage_account_name' => '<AZURE STORAGE ACCOUNT NAME>',
|
||||
'azure_storage_access_key' => '<AZURE STORAGE ACCESS KEY>',
|
||||
'azure_storage_domain' => '<AZURE STORAGE DOMAIN>',
|
||||
'azure_storage_domain' => '<AZURE STORAGE DOMAIN>'
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -173,9 +173,11 @@ The exceptions to the [original dotenv rules](https://github.com/motdotla/dotenv
|
|||
|
||||
- The variable key can contain only letters, digits, and underscores (`_`).
|
||||
- The maximum size of the `.env` file is 5 KB.
|
||||
- In GitLab 13.5 and older, the maximum number of inherited variables is 10.
|
||||
- In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/247913),
|
||||
the maximum number of inherited variables is 20.
|
||||
This limit [can be changed on self-managed instances](../../administration/instance_limits.md#limit-dotenv-file-size).
|
||||
- On GitLab.com, [the maximum number of inherited variables](../../user/gitlab_com/index.md#gitlab-cicd)
|
||||
is 50 for Free, 100 for Premium and 150 for Ultimate. The default for
|
||||
self-managed instances is 150, and can be changed by changing the
|
||||
`dotenv_variables` [application limit](../../administration/instance_limits.md#limit-dotenv-variables).
|
||||
- Variable substitution in the `.env` file is not supported.
|
||||
- The `.env` file can't have empty lines or comments (starting with `#`).
|
||||
- Key values in the `env` file cannot have spaces or newline characters (`\n`), including when using single or double quotes.
|
||||
|
|
|
@ -249,6 +249,9 @@ In line with our `CodeReuse/ActiveRecord` cop, you should only use forms like
|
|||
use the `ApplicationRecord`-provided `.pluck_primary_key` helper method instead.
|
||||
In the latter, you should add a small helper method to the relevant model.
|
||||
|
||||
If you have strong reasons to use `pluck`, it could make sense to limit the number
|
||||
of records plucked. `MAX_PLUCK` defaults to `1_000` in `ApplicationRecord`.
|
||||
|
||||
## Inherit from ApplicationRecord
|
||||
|
||||
Most models in the GitLab codebase should inherit from `ApplicationRecord`,
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 88 KiB |
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
|
@ -101,10 +101,10 @@ Consider this example:
|
|||
You can browse, search, and view issues from a selected Jira project directly in GitLab,
|
||||
if your GitLab administrator [has configured it](configure.md).
|
||||
|
||||
To do this, in GitLab, go to your project and select **Jira > Issues list**. The issue list
|
||||
To do this, in GitLab, go to your project and select **Issues > Jira issues**. The issue list
|
||||
sorts by **Created date** by default, with the newest issues listed at the top:
|
||||
|
||||
![Jira issues integration enabled](img/open_jira_issues_list_v13.2.png)
|
||||
![Jira issues integration enabled](img/open_jira_issues_list_v14_6.png)
|
||||
|
||||
- To display the most recently updated issues first, select **Last updated**.
|
||||
- You can [search and filter](#search-and-filter-the-issues-list) the issues list.
|
||||
|
|
|
@ -141,7 +141,7 @@ the related documentation.
|
|||
| [Scheduled Job Archival](../../user/admin_area/settings/continuous_integration.md#archive-jobs) | 3 months | Never |
|
||||
| Max test cases per [unit test report](../../ci/unit_test_reports.md) | `500_000` | Unlimited |
|
||||
| [Max registered runners](../../administration/instance_limits.md#number-of-registered-runners-per-scope) | Free tier: `50` per-group / `50` per-project <br/> All paid tiers: `1_000` per-group / `1_000` per-project | `1_000` per-group / `1_000` per-project |
|
||||
| [Limit dotenv variables](../../administration/instance_limits.md#limit-dotenv-variables) | Free tier: `50` / Premium tier: `100` / Ultimate tier: `150` | Unlimited |
|
||||
| [Limit dotenv variables](../../administration/instance_limits.md#limit-dotenv-variables) | Free tier: `50` / Premium tier: `100` / Ultimate tier: `150` | 150 |
|
||||
|
||||
## Account and limit settings
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module BulkImports
|
||||
module Projects
|
||||
module Pipelines
|
||||
class CiPipelinesPipeline
|
||||
include NdjsonPipeline
|
||||
|
||||
relation_name 'ci_pipelines'
|
||||
|
||||
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -43,6 +43,10 @@ module BulkImports
|
|||
pipeline: BulkImports::Projects::Pipelines::ProtectedBranchesPipeline,
|
||||
stage: 4
|
||||
},
|
||||
ci_pipelines: {
|
||||
pipeline: BulkImports::Projects::Pipelines::CiPipelinesPipeline,
|
||||
stage: 4
|
||||
},
|
||||
wiki: {
|
||||
pipeline: BulkImports::Common::Pipelines::WikiPipeline,
|
||||
stage: 5
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# rubocop: disable Style/Documentation
|
||||
class RecalculateVulnerabilityFindingSignaturesForFindings
|
||||
def perform(start_id, stop_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::BackgroundMigration::RecalculateVulnerabilityFindingSignaturesForFindings.prepend_mod
|
|
@ -204,19 +204,6 @@ module Gitlab
|
|||
Gitlab::Git::Commit.new(@repository, gitaly_commit)
|
||||
end
|
||||
|
||||
def between(from, to)
|
||||
return list_commits(["^" + from, to], reverse: true) if Feature.enabled?(:between_commits_via_list_commits)
|
||||
|
||||
request = Gitaly::CommitsBetweenRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
from: from,
|
||||
to: to
|
||||
)
|
||||
|
||||
response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request, timeout: GitalyClient.medium_timeout)
|
||||
consume_commits_response(response)
|
||||
end
|
||||
|
||||
def diff_stats(left_commit_sha, right_commit_sha)
|
||||
request = Gitaly::DiffStatsRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
|
|
|
@ -7,6 +7,7 @@ module Gitlab
|
|||
%i[
|
||||
assignee_id
|
||||
author_id
|
||||
blocking_discussions_resolved
|
||||
created_at
|
||||
description
|
||||
head_pipeline_id
|
||||
|
@ -57,7 +58,8 @@ module Gitlab
|
|||
human_time_estimate: merge_request.human_time_estimate,
|
||||
assignee_ids: merge_request.assignee_ids,
|
||||
assignee_id: merge_request.assignee_ids.first, # This key is deprecated
|
||||
state: merge_request.state # This key is deprecated
|
||||
state: merge_request.state, # This key is deprecated
|
||||
blocking_discussions_resolved: merge_request.mergeable_discussions_state?
|
||||
}
|
||||
|
||||
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
|
||||
|
|
|
@ -44,6 +44,14 @@ module Sidebars
|
|||
list[index] = new_element
|
||||
end
|
||||
|
||||
def remove_element(list, element_to_remove)
|
||||
index = index_of(list, element_to_remove)
|
||||
|
||||
return unless index
|
||||
|
||||
list.slice!(index)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Classes including this method will have to define
|
||||
|
|
|
@ -37,6 +37,10 @@ module Sidebars
|
|||
replace_element(@menus, menu_to_replace, new_menu)
|
||||
end
|
||||
|
||||
def remove_menu(menu_to_remove)
|
||||
remove_element(@menus, menu_to_remove)
|
||||
end
|
||||
|
||||
def set_scope_menu(scope_menu)
|
||||
@scope_menu = scope_menu
|
||||
end
|
||||
|
|
|
@ -7423,6 +7423,9 @@ msgstr ""
|
|||
msgid "ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|GitLab Kubernetes Agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Go to the repository"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7444,6 +7447,12 @@ msgstr ""
|
|||
msgid "ClusterAgents|Learn how to troubleshoot"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Learn more about installing a GitLab Kubernetes Agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent registration."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent."
|
||||
msgstr ""
|
||||
|
||||
|
@ -7468,9 +7477,6 @@ msgstr ""
|
|||
msgid "ClusterAgents|Not connected"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Read more about getting started"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Recommended"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7492,7 +7498,7 @@ msgstr ""
|
|||
msgid "ClusterAgents|Select an Agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Select the Agent you want to register with GitLab and install on your cluster. To learn more about the Kubernetes Agent registration process %{linkStart}go to the documentation%{linkEnd}."
|
||||
msgid "ClusterAgents|Select an Agent to register with GitLab and install on your cluster."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|Select which Agent you want to install"
|
||||
|
@ -7501,6 +7507,9 @@ msgstr ""
|
|||
msgid "ClusterAgents|The Agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window."
|
||||
msgstr ""
|
||||
|
||||
|
@ -19648,9 +19657,6 @@ msgstr ""
|
|||
msgid "Japanese language support using"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira Issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jira display name"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19780,18 +19786,12 @@ msgstr ""
|
|||
msgid "JiraService|If different from Web URL."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Issue List"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Issues created from vulnerabilities in this project will be Jira issues, even if GitLab issues are enabled."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira API URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira Issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira comments are created when an issue is referenced in a commit."
|
||||
msgstr ""
|
||||
|
||||
|
@ -19801,6 +19801,9 @@ msgstr ""
|
|||
msgid "JiraService|Jira issue type"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira project key"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module RuboCop
|
|||
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization'
|
||||
|
||||
# We want to exclude our own basetypes and scalars
|
||||
ALLOWED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType SubscriptionType
|
||||
ALLOWED_TYPES = %w[BaseEnum BaseEdge BaseScalar BasePermissionType MutationType SubscriptionType
|
||||
QueryType GraphQL::Schema BaseUnion BaseInputObject].freeze
|
||||
|
||||
def_node_search :authorize?, <<~PATTERN
|
||||
|
|
|
@ -26,8 +26,6 @@ function retrieve_tests_metadata() {
|
|||
fi
|
||||
|
||||
if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then
|
||||
# Fixed ID to get the report back to a good state after https://gitlab.com/gitlab-org/gitlab/-/issues/345798 / https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74617
|
||||
test_metadata_job_id=1766932099
|
||||
scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe 'Cluster agent registration', :js do
|
|||
|
||||
it 'allows the user to select an agent to install, and displays the resulting agent token' do
|
||||
click_button('Actions')
|
||||
expect(page).to have_content('Install new Agent')
|
||||
expect(page).to have_content('Register Agent')
|
||||
|
||||
click_button('Select an Agent')
|
||||
click_button('example-agent-2')
|
||||
|
|
|
@ -26,8 +26,7 @@ RSpec.describe 'User activates Jira', :js do
|
|||
unless Gitlab.ee?
|
||||
it 'adds Jira link to sidebar menu' do
|
||||
page.within('.nav-sidebar') do
|
||||
expect(page).not_to have_link('Jira Issues')
|
||||
expect(page).not_to have_link('Issue List', visible: false)
|
||||
expect(page).not_to have_link('Jira issues', visible: false)
|
||||
expect(page).not_to have_link('Open Jira', href: url, visible: false)
|
||||
expect(page).to have_link('Jira', href: url)
|
||||
end
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
import { GlAlert, GlEmptyState, GlSprintf } from '@gitlab/ui';
|
||||
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
|
||||
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
|
||||
import { INSTALL_AGENT_MODAL_ID } from '~/clusters_list/constants';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
||||
const emptyStateImage = '/path/to/image';
|
||||
const projectPath = 'path/to/project';
|
||||
const multipleClustersDocsUrl = helpPagePath('user/project/clusters/multiple_kubernetes_clusters');
|
||||
const installDocsUrl = helpPagePath('administration/clusters/kas');
|
||||
|
||||
describe('AgentEmptyStateComponent', () => {
|
||||
let wrapper;
|
||||
|
||||
const propsData = {
|
||||
hasConfigurations: false,
|
||||
};
|
||||
const provideData = {
|
||||
emptyStateImage,
|
||||
projectPath,
|
||||
};
|
||||
|
||||
const findConfigurationsAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link');
|
||||
const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link');
|
||||
const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button');
|
||||
|
@ -27,8 +22,10 @@ describe('AgentEmptyStateComponent', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMountExtended(AgentEmptyState, {
|
||||
propsData,
|
||||
provide: provideData,
|
||||
directives: {
|
||||
GlModalDirective: createMockDirective(),
|
||||
},
|
||||
stubs: { GlEmptyState, GlSprintf },
|
||||
});
|
||||
});
|
||||
|
@ -39,33 +36,22 @@ describe('AgentEmptyStateComponent', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('renders the empty state', () => {
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders button for the agent registration', () => {
|
||||
expect(findIntegrationButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders correct href attributes for the links', () => {
|
||||
expect(findMultipleClustersDocsLink().attributes('href')).toBe(multipleClustersDocsUrl);
|
||||
expect(findInstallDocsLink().attributes('href')).toBe(installDocsUrl);
|
||||
});
|
||||
|
||||
describe('when there are no agent configurations in repository', () => {
|
||||
it('should render notification message box', () => {
|
||||
expect(findConfigurationsAlert().exists()).toBe(true);
|
||||
});
|
||||
it('renders correct modal id for the agent registration modal', () => {
|
||||
const binding = getBinding(findIntegrationButton().element, 'gl-modal-directive');
|
||||
|
||||
it('should disable integration button', () => {
|
||||
expect(findIntegrationButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is a list of agent configurations', () => {
|
||||
beforeEach(() => {
|
||||
propsData.hasConfigurations = true;
|
||||
wrapper = shallowMountExtended(AgentEmptyState, {
|
||||
propsData,
|
||||
provide: provideData,
|
||||
});
|
||||
});
|
||||
it('should render content without notification message box', () => {
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
expect(findConfigurationsAlert().exists()).toBe(false);
|
||||
expect(findIntegrationButton().attributes('disabled')).toBeUndefined();
|
||||
});
|
||||
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,6 @@ describe('Agents', () => {
|
|||
};
|
||||
const provideData = {
|
||||
projectPath: 'path/to/project',
|
||||
kasAddress: 'kas.example.com',
|
||||
};
|
||||
|
||||
const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => {
|
||||
|
@ -216,24 +215,6 @@ describe('Agents', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when the agent configurations are present', () => {
|
||||
const trees = [
|
||||
{
|
||||
name: 'agent-1',
|
||||
path: '.gitlab/agents/agent-1',
|
||||
webPath: '/project/path/.gitlab/agents/agent-1',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
return createWrapper({ agents: [], trees });
|
||||
});
|
||||
|
||||
it('should pass the correct hasConfigurations boolean value to empty state component', () => {
|
||||
expect(findEmptyState().props('hasConfigurations')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when agents query has errored', () => {
|
||||
beforeEach(() => {
|
||||
return createWrapper({ agents: null });
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
|
||||
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '~/clusters_list/constants';
|
||||
import agentConfigurationsQuery from '~/clusters_list/graphql/queries/agent_configurations.query.graphql';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { agentConfigurationsResponse } from './mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('AvailableAgentsDropdown', () => {
|
||||
let wrapper;
|
||||
|
@ -18,46 +11,19 @@ describe('AvailableAgentsDropdown', () => {
|
|||
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
|
||||
const findConfiguredAgentItem = () => findDropdownItems().at(0);
|
||||
|
||||
const createWrapper = ({ propsData = {}, isLoading = false }) => {
|
||||
const provide = {
|
||||
projectPath: 'path/to/project',
|
||||
};
|
||||
|
||||
wrapper = (() => {
|
||||
if (isLoading) {
|
||||
const mocks = {
|
||||
$apollo: {
|
||||
queries: {
|
||||
agents: {
|
||||
loading: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return mount(AvailableAgentsDropdown, { mocks, provide, propsData });
|
||||
}
|
||||
|
||||
const apolloProvider = createMockApollo([
|
||||
[agentConfigurationsQuery, jest.fn().mockResolvedValue(agentConfigurationsResponse)],
|
||||
]);
|
||||
|
||||
return mount(AvailableAgentsDropdown, {
|
||||
localVue,
|
||||
apolloProvider,
|
||||
provide,
|
||||
propsData,
|
||||
});
|
||||
})();
|
||||
const createWrapper = ({ propsData }) => {
|
||||
wrapper = shallowMount(AvailableAgentsDropdown, {
|
||||
propsData,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('there are agents available', () => {
|
||||
const propsData = {
|
||||
availableAgents: ['configured-agent'],
|
||||
isRegistering: false,
|
||||
};
|
||||
|
||||
|
@ -69,12 +35,6 @@ describe('AvailableAgentsDropdown', () => {
|
|||
expect(findDropdown().props('text')).toBe(i18n.selectAgent);
|
||||
});
|
||||
|
||||
it('shows only agents that are not yet installed', () => {
|
||||
expect(findDropdownItems()).toHaveLength(1);
|
||||
expect(findConfiguredAgentItem().text()).toBe('configured-agent');
|
||||
expect(findConfiguredAgentItem().props('isChecked')).toBe(false);
|
||||
});
|
||||
|
||||
describe('click events', () => {
|
||||
beforeEach(() => {
|
||||
findConfiguredAgentItem().vm.$emit('click');
|
||||
|
@ -93,6 +53,7 @@ describe('AvailableAgentsDropdown', () => {
|
|||
|
||||
describe('registration in progress', () => {
|
||||
const propsData = {
|
||||
availableAgents: ['configured-agent'],
|
||||
isRegistering: true,
|
||||
};
|
||||
|
||||
|
@ -108,22 +69,4 @@ describe('AvailableAgentsDropdown', () => {
|
|||
expect(findDropdown().props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('agents query is loading', () => {
|
||||
const propsData = {
|
||||
isRegistering: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createWrapper({ propsData, isLoading: true });
|
||||
});
|
||||
|
||||
it('updates the text in the dropdown', () => {
|
||||
expect(findDropdown().text()).toBe(i18n.selectAgent);
|
||||
});
|
||||
|
||||
it('displays a loading icon', () => {
|
||||
expect(findDropdown().props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
|
||||
import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue';
|
||||
import { I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants';
|
||||
import { I18N_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants';
|
||||
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
|
||||
import getAgentConfigurations from '~/clusters_list/graphql/queries/agent_configurations.query.graphql';
|
||||
import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql';
|
||||
import createAgentTokenMutation from '~/clusters_list/graphql/mutations/create_agent_token.mutation.graphql';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
@ -23,6 +25,9 @@ const localVue = createLocalVue();
|
|||
localVue.use(VueApollo);
|
||||
|
||||
const projectPath = 'path/to/project';
|
||||
const kasAddress = 'kas.example.com';
|
||||
const kasEnabled = true;
|
||||
const emptyStateImage = 'path/to/image';
|
||||
const defaultBranchName = 'default';
|
||||
const maxAgents = MAX_LIST_COUNT;
|
||||
|
||||
|
@ -30,7 +35,16 @@ describe('InstallAgentModal', () => {
|
|||
let wrapper;
|
||||
let apolloProvider;
|
||||
|
||||
const i18n = I18N_INSTALL_AGENT_MODAL;
|
||||
const configurations = [{ agentName: 'agent-name' }];
|
||||
const apolloQueryResponse = {
|
||||
data: {
|
||||
project: {
|
||||
clusterAgents: { nodes: [] },
|
||||
agentConfigurations: { nodes: configurations },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.findComponent(ModalStub);
|
||||
const findAgentDropdown = () => findModal().findComponent(AvailableAgentsDropdown);
|
||||
const findAlert = () => findModal().findComponent(GlAlert);
|
||||
|
@ -40,6 +54,8 @@ describe('InstallAgentModal', () => {
|
|||
.wrappers.find((button) => button.props('variant') === variant);
|
||||
const findActionButton = () => findButtonByVariant('confirm');
|
||||
const findCancelButton = () => findButtonByVariant('default');
|
||||
const findSecondaryButton = () => wrapper.findByTestId('agent-secondary-button');
|
||||
const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.install.altText });
|
||||
|
||||
const expectDisabledAttribute = (element, disabled) => {
|
||||
if (disabled) {
|
||||
|
@ -52,7 +68,9 @@ describe('InstallAgentModal', () => {
|
|||
const createWrapper = () => {
|
||||
const provide = {
|
||||
projectPath,
|
||||
kasAddress: 'kas.example.com',
|
||||
kasAddress,
|
||||
kasEnabled,
|
||||
emptyStateImage,
|
||||
};
|
||||
|
||||
const propsData = {
|
||||
|
@ -60,7 +78,7 @@ describe('InstallAgentModal', () => {
|
|||
maxAgents,
|
||||
};
|
||||
|
||||
wrapper = shallowMount(InstallAgentModal, {
|
||||
wrapper = shallowMountExtended(InstallAgentModal, {
|
||||
attachTo: document.body,
|
||||
stubs: {
|
||||
GlModal: ModalStub,
|
||||
|
@ -85,10 +103,12 @@ describe('InstallAgentModal', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const mockSelectedAgentResponse = () => {
|
||||
const mockSelectedAgentResponse = async () => {
|
||||
createWrapper();
|
||||
writeQuery();
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.vm.setAgentName('agent-name');
|
||||
findActionButton().vm.$emit('click');
|
||||
|
||||
|
@ -96,121 +116,160 @@ describe('InstallAgentModal', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
|
||||
]);
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
apolloProvider = null;
|
||||
});
|
||||
|
||||
describe('initial state', () => {
|
||||
it('renders the dropdown for available agents', () => {
|
||||
expect(findAgentDropdown().isVisible()).toBe(true);
|
||||
expect(findModal().text()).not.toContain(i18n.basicInstallTitle);
|
||||
expect(findModal().findComponent(GlFormInputGroup).exists()).toBe(false);
|
||||
expect(findModal().findComponent(GlAlert).exists()).toBe(false);
|
||||
expect(findModal().findComponent(CodeBlock).exists()).toBe(false);
|
||||
describe('when agent configurations are present', () => {
|
||||
const i18n = I18N_AGENT_MODAL.register;
|
||||
|
||||
describe('initial state', () => {
|
||||
it('renders the dropdown for available agents', () => {
|
||||
expect(findAgentDropdown().isVisible()).toBe(true);
|
||||
expect(findModal().text()).not.toContain(i18n.basicInstallTitle);
|
||||
expect(findModal().findComponent(GlFormInputGroup).exists()).toBe(false);
|
||||
expect(findModal().findComponent(GlAlert).exists()).toBe(false);
|
||||
expect(findModal().findComponent(CodeBlock).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders a cancel button', () => {
|
||||
expect(findCancelButton().isVisible()).toBe(true);
|
||||
expectDisabledAttribute(findCancelButton(), false);
|
||||
});
|
||||
|
||||
it('renders a disabled next button', () => {
|
||||
expect(findActionButton().isVisible()).toBe(true);
|
||||
expect(findActionButton().text()).toBe(i18n.registerAgentButton);
|
||||
expectDisabledAttribute(findActionButton(), true);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a cancel button', () => {
|
||||
expect(findCancelButton().isVisible()).toBe(true);
|
||||
expectDisabledAttribute(findCancelButton(), false);
|
||||
describe('an agent is selected', () => {
|
||||
beforeEach(() => {
|
||||
findAgentDropdown().vm.$emit('agentSelected');
|
||||
});
|
||||
|
||||
it('enables the next button', () => {
|
||||
expect(findActionButton().isVisible()).toBe(true);
|
||||
expectDisabledAttribute(findActionButton(), false);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a disabled next button', () => {
|
||||
expect(findActionButton().isVisible()).toBe(true);
|
||||
expect(findActionButton().text()).toBe(i18n.registerAgentButton);
|
||||
expectDisabledAttribute(findActionButton(), true);
|
||||
describe('registering an agent', () => {
|
||||
const createAgentHandler = jest.fn().mockResolvedValue(createAgentResponse);
|
||||
const createAgentTokenHandler = jest.fn().mockResolvedValue(createAgentTokenResponse);
|
||||
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
|
||||
[createAgentMutation, createAgentHandler],
|
||||
[createAgentTokenMutation, createAgentTokenHandler],
|
||||
]);
|
||||
|
||||
return mockSelectedAgentResponse();
|
||||
});
|
||||
|
||||
it('creates an agent and token', () => {
|
||||
expect(createAgentHandler).toHaveBeenCalledWith({
|
||||
input: { name: 'agent-name', projectPath },
|
||||
});
|
||||
|
||||
expect(createAgentTokenHandler).toHaveBeenCalledWith({
|
||||
input: { clusterAgentId: 'agent-id', name: 'agent-name' },
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a close button', () => {
|
||||
expect(findActionButton().isVisible()).toBe(true);
|
||||
expect(findActionButton().text()).toBe(i18n.close);
|
||||
expectDisabledAttribute(findActionButton(), false);
|
||||
});
|
||||
|
||||
it('shows agent instructions', () => {
|
||||
const modalText = findModal().text();
|
||||
expect(modalText).toContain(i18n.basicInstallTitle);
|
||||
expect(modalText).toContain(i18n.basicInstallBody);
|
||||
|
||||
const token = findModal().findComponent(GlFormInputGroup);
|
||||
expect(token.props('value')).toBe('mock-agent-token');
|
||||
|
||||
const alert = findModal().findComponent(GlAlert);
|
||||
expect(alert.props('title')).toBe(i18n.tokenSingleUseWarningTitle);
|
||||
|
||||
const code = findModal().findComponent(CodeBlock).props('code');
|
||||
expect(code).toContain('--agent-token=mock-agent-token');
|
||||
expect(code).toContain('--kas-address=kas.example.com');
|
||||
});
|
||||
|
||||
describe('error creating agent', () => {
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
|
||||
[createAgentMutation, jest.fn().mockResolvedValue(createAgentErrorResponse)],
|
||||
]);
|
||||
|
||||
return mockSelectedAgentResponse();
|
||||
});
|
||||
|
||||
it('displays the error message', () => {
|
||||
expect(findAlert().text()).toBe(
|
||||
createAgentErrorResponse.data.createClusterAgent.errors[0],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error creating token', () => {
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
|
||||
[createAgentMutation, jest.fn().mockResolvedValue(createAgentResponse)],
|
||||
[createAgentTokenMutation, jest.fn().mockResolvedValue(createAgentTokenErrorResponse)],
|
||||
]);
|
||||
|
||||
return mockSelectedAgentResponse();
|
||||
});
|
||||
|
||||
it('displays the error message', async () => {
|
||||
expect(findAlert().text()).toBe(
|
||||
createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('an agent is selected', () => {
|
||||
beforeEach(() => {
|
||||
findAgentDropdown().vm.$emit('agentSelected');
|
||||
});
|
||||
|
||||
it('enables the next button', () => {
|
||||
expect(findActionButton().isVisible()).toBe(true);
|
||||
expectDisabledAttribute(findActionButton(), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registering an agent', () => {
|
||||
const createAgentHandler = jest.fn().mockResolvedValue(createAgentResponse);
|
||||
const createAgentTokenHandler = jest.fn().mockResolvedValue(createAgentTokenResponse);
|
||||
describe('when there are no agent configurations present', () => {
|
||||
const i18n = I18N_AGENT_MODAL.install;
|
||||
const apolloQueryEmptyResponse = {
|
||||
data: {
|
||||
project: {
|
||||
clusterAgents: { nodes: [] },
|
||||
agentConfigurations: { nodes: [] },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[createAgentMutation, createAgentHandler],
|
||||
[createAgentTokenMutation, createAgentTokenHandler],
|
||||
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryEmptyResponse)],
|
||||
]);
|
||||
|
||||
return mockSelectedAgentResponse(apolloProvider);
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
it('creates an agent and token', () => {
|
||||
expect(createAgentHandler).toHaveBeenCalledWith({
|
||||
input: { name: 'agent-name', projectPath },
|
||||
});
|
||||
|
||||
expect(createAgentTokenHandler).toHaveBeenCalledWith({
|
||||
input: { clusterAgentId: 'agent-id', name: 'agent-name' },
|
||||
});
|
||||
it('renders empty state image', () => {
|
||||
expect(findImage().attributes('src')).toBe(emptyStateImage);
|
||||
});
|
||||
|
||||
it('renders a close button', () => {
|
||||
expect(findActionButton().isVisible()).toBe(true);
|
||||
expect(findActionButton().text()).toBe(i18n.close);
|
||||
expectDisabledAttribute(findActionButton(), false);
|
||||
});
|
||||
|
||||
it('shows agent instructions', () => {
|
||||
const modalText = findModal().text();
|
||||
expect(modalText).toContain(i18n.basicInstallTitle);
|
||||
expect(modalText).toContain(i18n.basicInstallBody);
|
||||
|
||||
const token = findModal().findComponent(GlFormInputGroup);
|
||||
expect(token.props('value')).toBe('mock-agent-token');
|
||||
|
||||
const alert = findModal().findComponent(GlAlert);
|
||||
expect(alert.props('title')).toBe(i18n.tokenSingleUseWarningTitle);
|
||||
|
||||
const code = findModal().findComponent(CodeBlock).props('code');
|
||||
expect(code).toContain('--agent-token=mock-agent-token');
|
||||
expect(code).toContain('--kas-address=kas.example.com');
|
||||
});
|
||||
|
||||
describe('error creating agent', () => {
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[createAgentMutation, jest.fn().mockResolvedValue(createAgentErrorResponse)],
|
||||
]);
|
||||
|
||||
return mockSelectedAgentResponse();
|
||||
});
|
||||
|
||||
it('displays the error message', () => {
|
||||
expect(findAlert().text()).toBe(createAgentErrorResponse.data.createClusterAgent.errors[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error creating token', () => {
|
||||
beforeEach(() => {
|
||||
apolloProvider = createMockApollo([
|
||||
[createAgentMutation, jest.fn().mockResolvedValue(createAgentResponse)],
|
||||
[createAgentTokenMutation, jest.fn().mockResolvedValue(createAgentTokenErrorResponse)],
|
||||
]);
|
||||
|
||||
return mockSelectedAgentResponse();
|
||||
});
|
||||
|
||||
it('displays the error message', () => {
|
||||
expect(findAlert().text()).toBe(
|
||||
createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
|
||||
);
|
||||
});
|
||||
it('renders a secondary button', () => {
|
||||
expect(findSecondaryButton().isVisible()).toBe(true);
|
||||
expect(findSecondaryButton().text()).toBe(i18n.secondaryButton);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::BaseEdge do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:test_schema) do
|
||||
project_edge_type = Class.new(described_class) do
|
||||
field :proof_of_admin_rights, String,
|
||||
null: true, authorize: :admin_project
|
||||
|
||||
def proof_of_admin_rights
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
project_type = Class.new(::Types::BaseObject) do
|
||||
graphql_name 'Project'
|
||||
authorize :read_project
|
||||
edge_type_class project_edge_type
|
||||
|
||||
field :name, String, null: false
|
||||
end
|
||||
|
||||
Class.new(GraphQL::Schema) do
|
||||
lazy_resolve ::Gitlab::Graphql::Lazy, :force
|
||||
use ::GraphQL::Pagination::Connections
|
||||
use ::Gitlab::Graphql::Pagination::Connections
|
||||
|
||||
query(Class.new(::Types::BaseObject) do
|
||||
graphql_name 'Query'
|
||||
field :projects, project_type.connection_type, null: false
|
||||
|
||||
def projects
|
||||
context[:projects]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def document
|
||||
GraphQL.parse(<<~GQL)
|
||||
query {
|
||||
projects {
|
||||
edges {
|
||||
proofOfAdminRights
|
||||
node { name }
|
||||
}
|
||||
}
|
||||
}
|
||||
GQL
|
||||
end
|
||||
|
||||
it 'supports field authorization on edge fields' do
|
||||
user = create(:user)
|
||||
private_project = create(:project, :private)
|
||||
member_project = create(:project, :private)
|
||||
maintainer_project = create(:project, :private)
|
||||
public_project = create(:project, :public)
|
||||
|
||||
member_project.add_developer(user)
|
||||
maintainer_project.add_maintainer(user)
|
||||
projects = [private_project, member_project, maintainer_project, public_project]
|
||||
|
||||
data = { current_user: user, projects: projects }
|
||||
query = GraphQL::Query.new(test_schema, document: document, context: data)
|
||||
result = query.result.to_h
|
||||
|
||||
expect(graphql_dig_at(result, 'data', 'projects', 'edges', 'node', 'name'))
|
||||
.to contain_exactly(member_project.name, maintainer_project.name, public_project.name)
|
||||
|
||||
expect(graphql_dig_at(result, 'data', 'projects', 'edges', 'proofOfAdminRights'))
|
||||
.to contain_exactly(nil, 'ok', nil)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,176 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::CiPipelinesPipeline do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
|
||||
let_it_be(:entity) do
|
||||
create(
|
||||
:bulk_import_entity,
|
||||
:project_entity,
|
||||
project: project,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'My Destination Project',
|
||||
destination_namespace: group.full_path
|
||||
)
|
||||
end
|
||||
|
||||
let(:ci_pipeline_attributes) { {} }
|
||||
let(:ci_pipeline) do
|
||||
{
|
||||
sha: "fakesha",
|
||||
ref: "fakeref",
|
||||
project: project,
|
||||
source: "web"
|
||||
}.merge(ci_pipeline_attributes)
|
||||
end
|
||||
|
||||
let(:ci_pipeline2) do
|
||||
{
|
||||
sha: "fakesha2",
|
||||
ref: "fakeref2",
|
||||
project: project,
|
||||
source: "web"
|
||||
}.merge(ci_pipeline_attributes)
|
||||
end
|
||||
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
|
||||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
describe '#run' do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(
|
||||
BulkImports::Pipeline::ExtractedData.new(data: [ci_pipeline, ci_pipeline2])
|
||||
)
|
||||
end
|
||||
|
||||
allow_next_instance_of(Repository) do |repository|
|
||||
allow(repository).to receive(:fetch_source_branch!)
|
||||
end
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
it 'imports Ci::Pipeline into destination project' do
|
||||
expect(project.all_pipelines.count).to eq(2)
|
||||
expect(project.ci_pipelines.first.sha).to eq('fakesha')
|
||||
expect(project.ci_pipelines.second.sha).to eq('fakesha2')
|
||||
end
|
||||
|
||||
context 'notes' do
|
||||
let(:ci_pipeline_attributes) do
|
||||
{
|
||||
'notes' => [
|
||||
{
|
||||
'note' => 'test note',
|
||||
'author_id' => 22,
|
||||
'noteable_type' => 'Commit',
|
||||
'sha' => '',
|
||||
'author' => {
|
||||
'name' => 'User 22'
|
||||
},
|
||||
'commit_id' => 'fakesha',
|
||||
'updated_at' => '2016-06-14T15:02:47.770Z',
|
||||
'events' => [
|
||||
{
|
||||
'action' => 'created',
|
||||
'author_id' => 22
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports pipeline with notes' do
|
||||
note = project.all_pipelines.first.notes.first
|
||||
expect(note.note).to include('test note')
|
||||
expect(note.events.first.action).to eq('created')
|
||||
end
|
||||
end
|
||||
|
||||
context 'stages' do
|
||||
let(:ci_pipeline_attributes) do
|
||||
{
|
||||
'stages' => [
|
||||
{
|
||||
'name' => 'test stage',
|
||||
'statuses' => [
|
||||
{
|
||||
'name' => 'first status',
|
||||
'status' => 'created'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports pipeline with notes' do
|
||||
stage = project.all_pipelines.first.stages.first
|
||||
expect(stage.name).to eq('test stage')
|
||||
expect(stage.statuses.first.name).to eq('first status')
|
||||
end
|
||||
end
|
||||
|
||||
context 'external pull request' do
|
||||
let(:ci_pipeline_attributes) do
|
||||
{
|
||||
'source' => 'external_pull_request_event',
|
||||
'external_pull_request' => {
|
||||
'source_branch' => 'test source branch',
|
||||
'target_branch' => 'master',
|
||||
'source_sha' => 'testsha',
|
||||
'target_sha' => 'targetsha',
|
||||
'source_repository' => 'test repository',
|
||||
'target_repository' => 'test repository',
|
||||
'status' => 1,
|
||||
'pull_request_iid' => 1
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports pipeline with external pull request' do
|
||||
pull_request = project.all_pipelines.first.external_pull_request
|
||||
expect(pull_request.source_branch).to eq('test source branch')
|
||||
expect(pull_request.status).to eq('open')
|
||||
end
|
||||
end
|
||||
|
||||
context 'merge request' do
|
||||
let(:ci_pipeline_attributes) do
|
||||
{
|
||||
'source' => 'merge_request_event',
|
||||
'merge_request' => {
|
||||
'description' => 'test merge request',
|
||||
'title' => 'test MR',
|
||||
'source_branch' => 'test source branch',
|
||||
'target_branch' => 'master',
|
||||
'source_sha' => 'testsha',
|
||||
'target_sha' => 'targetsha',
|
||||
'source_repository' => 'test repository',
|
||||
'target_repository' => 'test repository',
|
||||
'target_project_id' => project.id,
|
||||
'source_project_id' => project.id,
|
||||
'author_id' => user.id
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports pipeline with external pull request' do
|
||||
merge_request = project.all_pipelines.first.merge_request
|
||||
expect(merge_request.source_branch).to eq('test source branch')
|
||||
expect(merge_request.description).to eq('test merge request')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,6 +14,7 @@ RSpec.describe BulkImports::Projects::Stage do
|
|||
[4, BulkImports::Projects::Pipelines::MergeRequestsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ProtectedBranchesPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::CiPipelinesPipeline],
|
||||
[5, BulkImports::Common::Pipelines::WikiPipeline],
|
||||
[5, BulkImports::Common::Pipelines::UploadsPipeline],
|
||||
[6, BulkImports::Common::Pipelines::EntityFinisher]
|
||||
|
|
|
@ -53,7 +53,7 @@ RSpec.describe Gitlab::Diff::File do
|
|||
|
||||
describe 'initialize' do
|
||||
context 'when file is ipynb with a change after transformation' do
|
||||
let(:commit) { project.commit("f6b7a707") }
|
||||
let(:commit) { project.commit("532c837") }
|
||||
let(:diff) { commit.raw_diffs.first }
|
||||
let(:diff_file) { described_class.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
|
||||
|
||||
|
@ -63,7 +63,7 @@ RSpec.describe Gitlab::Diff::File do
|
|||
end
|
||||
|
||||
it 'recreates the diff by transforming the files' do
|
||||
expect(diff_file.diff.diff).not_to include('"| Fake')
|
||||
expect(diff_file.diff.diff).not_to include('cell_type')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -73,7 +73,7 @@ RSpec.describe Gitlab::Diff::File do
|
|||
end
|
||||
|
||||
it 'does not recreate the diff' do
|
||||
expect(diff_file.diff.diff).to include('"| Fake')
|
||||
expect(diff_file.diff.diff).to include('cell_type')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -108,45 +108,6 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#between' do
|
||||
let(:from) { 'master' }
|
||||
let(:to) { Gitlab::Git::EMPTY_TREE_ID }
|
||||
|
||||
context 'with between_commits_via_list_commits enabled' do
|
||||
before do
|
||||
stub_feature_flags(between_commits_via_list_commits: true)
|
||||
end
|
||||
|
||||
it 'sends an RPC request' do
|
||||
request = Gitaly::ListCommitsRequest.new(
|
||||
repository: repository_message, revisions: ["^" + from, to], reverse: true
|
||||
)
|
||||
|
||||
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:list_commits)
|
||||
.with(request, kind_of(Hash)).and_return([])
|
||||
|
||||
described_class.new(repository).between(from, to)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with between_commits_via_list_commits disabled' do
|
||||
before do
|
||||
stub_feature_flags(between_commits_via_list_commits: false)
|
||||
end
|
||||
|
||||
it 'sends an RPC request' do
|
||||
request = Gitaly::CommitsBetweenRequest.new(
|
||||
repository: repository_message, from: from, to: to
|
||||
)
|
||||
|
||||
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:commits_between)
|
||||
.with(request, kind_of(Hash)).and_return([])
|
||||
|
||||
described_class.new(repository).between(from, to)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diff_stats' do
|
||||
let(:left_commit_id) { 'master' }
|
||||
let(:right_commit_id) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
|
||||
|
|
|
@ -15,6 +15,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
|
|||
assignee_id
|
||||
assignee_ids
|
||||
author_id
|
||||
blocking_discussions_resolved
|
||||
created_at
|
||||
description
|
||||
head_pipeline_id
|
||||
|
|
|
@ -153,6 +153,25 @@ RSpec.describe Sidebars::Menu do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#remove_element' do
|
||||
let(:item1) { Sidebars::MenuItem.new(title: 'foo1', link: 'foo1', active_routes: {}, item_id: :foo1) }
|
||||
let(:item2) { Sidebars::MenuItem.new(title: 'foo2', link: 'foo2', active_routes: {}, item_id: :foo2) }
|
||||
let(:item3) { Sidebars::MenuItem.new(title: 'foo3', link: 'foo3', active_routes: {}, item_id: :foo3) }
|
||||
let(:list) { [item1, item2, item3] }
|
||||
|
||||
it 'removes specific element' do
|
||||
menu.remove_element(list, :foo2)
|
||||
|
||||
expect(list).to eq [item1, item3]
|
||||
end
|
||||
|
||||
it 'does not remove nil elements' do
|
||||
menu.remove_element(list, nil)
|
||||
|
||||
expect(list).to eq [item1, item2, item3]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#container_html_options' do
|
||||
before do
|
||||
allow(menu).to receive(:title).and_return('Foo Menu')
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleRecalculateVulnerabilityFindingSignaturesForFindings, :migration do
|
||||
before do
|
||||
allow(Gitlab).to receive(:ee?).and_return(ee?)
|
||||
stub_const("#{described_class.name}::BATCH_SIZE", 2)
|
||||
end
|
||||
|
||||
context 'when the Gitlab instance is FOSS' do
|
||||
let(:ee?) { false }
|
||||
|
||||
it 'does not run the migration' do
|
||||
expect { migrate! }.not_to change { BackgroundMigrationWorker.jobs.size }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the Gitlab instance is EE' do
|
||||
let(:ee?) { true }
|
||||
|
||||
let_it_be(:namespaces) { table(:namespaces) }
|
||||
let_it_be(:projects) { table(:projects) }
|
||||
let_it_be(:findings) { table(:vulnerability_occurrences) }
|
||||
let_it_be(:scanners) { table(:vulnerability_scanners) }
|
||||
let_it_be(:identifiers) { table(:vulnerability_identifiers) }
|
||||
let_it_be(:vulnerability_finding_signatures) { table(:vulnerability_finding_signatures) }
|
||||
|
||||
let_it_be(:namespace) { namespaces.create!(name: 'test', path: 'test') }
|
||||
let_it_be(:project) { projects.create!(namespace_id: namespace.id, name: 'gitlab', path: 'gitlab') }
|
||||
|
||||
let_it_be(:scanner) do
|
||||
scanners.create!(project_id: project.id, external_id: 'trivy', name: 'Security Scanner')
|
||||
end
|
||||
|
||||
let_it_be(:identifier) do
|
||||
identifiers.create!(project_id: project.id,
|
||||
fingerprint: 'd432c2ad2953e8bd587a3a43b3ce309b5b0154c123',
|
||||
external_type: 'SECURITY_ID',
|
||||
external_id: 'SECURITY_0',
|
||||
name: 'SECURITY_IDENTIFIER 0')
|
||||
end
|
||||
|
||||
let_it_be(:finding1) { findings.create!(finding_params) }
|
||||
let_it_be(:signature1) { vulnerability_finding_signatures.create!(finding_id: finding1.id, algorithm_type: 0, signature_sha: ::Digest::SHA1.digest(SecureRandom.hex(50))) }
|
||||
|
||||
let_it_be(:finding2) { findings.create!(finding_params) }
|
||||
let_it_be(:signature2) { vulnerability_finding_signatures.create!(finding_id: finding2.id, algorithm_type: 0, signature_sha: ::Digest::SHA1.digest(SecureRandom.hex(50))) }
|
||||
|
||||
let_it_be(:finding3) { findings.create!(finding_params) }
|
||||
let_it_be(:signature3) { vulnerability_finding_signatures.create!(finding_id: finding3.id, algorithm_type: 0, signature_sha: ::Digest::SHA1.digest(SecureRandom.hex(50))) }
|
||||
|
||||
it 'schedules the background jobs', :aggregate_failure do
|
||||
Sidekiq::Testing.fake! do
|
||||
freeze_time do
|
||||
migrate!
|
||||
|
||||
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
|
||||
expect(described_class::MIGRATION)
|
||||
.to be_scheduled_migration_with_multiple_args(signature1.id, signature2.id)
|
||||
expect(described_class::MIGRATION)
|
||||
.to be_scheduled_migration_with_multiple_args(signature3.id, signature3.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def finding_params
|
||||
uuid = SecureRandom.uuid
|
||||
|
||||
{
|
||||
severity: 0,
|
||||
confidence: 5,
|
||||
report_type: 2,
|
||||
project_id: project.id,
|
||||
scanner_id: scanner.id,
|
||||
primary_identifier_id: identifier.id,
|
||||
location: nil,
|
||||
project_fingerprint: SecureRandom.hex(20),
|
||||
location_fingerprint: Digest::SHA1.hexdigest(SecureRandom.hex(10)),
|
||||
uuid: uuid,
|
||||
name: "Vulnerability Finding #{uuid}",
|
||||
metadata_version: '1.3',
|
||||
raw_metadata: '{}'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -68,7 +68,7 @@ RSpec.describe 'package details' do
|
|||
subject
|
||||
|
||||
expect(graphql_data_at(:package, :versions, :nodes, :version)).to be_present
|
||||
expect(graphql_data_at(:package, :versions, :nodes, :versions, :nodes)).to be_empty
|
||||
expect(graphql_data_at(:package, :versions, :nodes, :versions, :nodes)).to eq [nil, nil]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,12 @@ RSpec.describe MergeRequests::ResolvedDiscussionNotificationService do
|
|||
|
||||
subject.execute(merge_request)
|
||||
end
|
||||
|
||||
it "doesn't send a webhook" do
|
||||
expect_any_instance_of(MergeRequests::BaseService).not_to receive(:execute_hooks)
|
||||
|
||||
subject.execute(merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context "when all discussions are resolved" do
|
||||
|
@ -44,6 +50,12 @@ RSpec.describe MergeRequests::ResolvedDiscussionNotificationService do
|
|||
|
||||
subject.execute(merge_request)
|
||||
end
|
||||
|
||||
it "sends a webhook" do
|
||||
expect_any_instance_of(MergeRequests::BaseService).to receive(:execute_hooks).with(merge_request, 'update')
|
||||
|
||||
subject.execute(merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -514,8 +514,13 @@ module GraphqlHelpers
|
|||
# Allows for array indexing, like this
|
||||
# ['project', 'boards', 'edges', 0, 'node', 'lists']
|
||||
keys.reduce(data) do |memo, key|
|
||||
if memo.is_a?(Array)
|
||||
key.is_a?(Integer) ? memo[key] : memo.flat_map { |e| Array.wrap(e[key]) }
|
||||
if memo.is_a?(Array) && key.is_a?(Integer)
|
||||
memo[key]
|
||||
elsif memo.is_a?(Array)
|
||||
memo.compact.flat_map do |e|
|
||||
x = e[key]
|
||||
x.nil? ? [x] : Array.wrap(x)
|
||||
end
|
||||
else
|
||||
memo&.dig(key)
|
||||
end
|
||||
|
|
|
@ -53,7 +53,7 @@ module TestEnv
|
|||
'wip' => 'b9238ee',
|
||||
'csv' => '3dd0896',
|
||||
'v1.1.0' => 'b83d6e3',
|
||||
'add-ipython-files' => '2b5ef814',
|
||||
'add-ipython-files' => '532c837',
|
||||
'add-pdf-file' => 'e774ebd',
|
||||
'squash-large-files' => '54cec52',
|
||||
'add-pdf-text-binary' => '79faa7b',
|
||||
|
|
|
@ -55,7 +55,7 @@ RSpec.shared_examples 'group and project packages query' do
|
|||
end
|
||||
|
||||
it 'deals with metadata' do
|
||||
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
|
||||
expect(target_shas.compact).to contain_exactly(composer_metadatum.target_sha)
|
||||
end
|
||||
|
||||
it 'returns the count of the packages' do
|
||||
|
|
|
@ -43,6 +43,21 @@ RSpec.describe GraphqlHelpers do
|
|||
|
||||
expect(graphql_dig_at(data, :foo, :nodes, :bar, :nodes, :id)).to eq([1, 2, 3, 4])
|
||||
end
|
||||
|
||||
it 'does not omit nils at the leaves' do
|
||||
data = {
|
||||
'foo' => {
|
||||
'nodes' => [
|
||||
{ 'bar' => { 'nodes' => [{ 'id' => nil }, { 'id' => 2 }] } },
|
||||
{ 'bar' => { 'nodes' => [{ 'id' => 3 }, { 'id' => nil }] } },
|
||||
{ 'bar' => nil }
|
||||
]
|
||||
},
|
||||
'irrelevant' => 'the field is a red-herring'
|
||||
}
|
||||
|
||||
expect(graphql_dig_at(data, :foo, :nodes, :bar, :nodes, :id)).to eq([nil, 2, 3, nil])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'var' do
|
||||
|
|
|
@ -398,7 +398,6 @@ RSpec.describe 'Every Sidekiq worker' do
|
|||
'PropagateIntegrationInheritWorker' => 3,
|
||||
'PropagateIntegrationProjectWorker' => 3,
|
||||
'PropagateIntegrationWorker' => 3,
|
||||
'PropagateServiceTemplateWorker' => 3,
|
||||
'PurgeDependencyProxyCacheWorker' => 3,
|
||||
'ReactiveCachingWorker' => 3,
|
||||
'RebaseWorker' => 3,
|
||||
|
|
Loading…
Reference in New Issue