Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cd6e1ccea4
commit
c39912f553
|
@ -1,154 +1,23 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { GlButton, GlModalDirective, GlTooltip } from '@gitlab/ui';
|
||||||
GlButton,
|
import { s__ } from '~/locale';
|
||||||
GlModalDirective,
|
import { CREATE_TOKEN_MODAL } from '../constants';
|
||||||
GlTooltip,
|
|
||||||
GlModal,
|
|
||||||
GlFormGroup,
|
|
||||||
GlFormInput,
|
|
||||||
GlFormTextarea,
|
|
||||||
GlAlert,
|
|
||||||
} from '@gitlab/ui';
|
|
||||||
import { s__, __ } from '~/locale';
|
|
||||||
import Tracking from '~/tracking';
|
|
||||||
import AgentToken from '~/clusters_list/components/agent_token.vue';
|
|
||||||
import {
|
|
||||||
CREATE_TOKEN_MODAL,
|
|
||||||
EVENT_LABEL_MODAL,
|
|
||||||
EVENT_ACTIONS_OPEN,
|
|
||||||
EVENT_ACTIONS_CLICK,
|
|
||||||
TOKEN_NAME_LIMIT,
|
|
||||||
TOKEN_STATUS_ACTIVE,
|
|
||||||
} from '../constants';
|
|
||||||
import createNewAgentToken from '../graphql/mutations/create_new_agent_token.mutation.graphql';
|
|
||||||
import getClusterAgentQuery from '../graphql/queries/get_cluster_agent.query.graphql';
|
|
||||||
import { addAgentTokenToStore } from '../graphql/cache_update';
|
|
||||||
|
|
||||||
const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_MODAL });
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AgentToken,
|
|
||||||
GlButton,
|
GlButton,
|
||||||
GlTooltip,
|
GlTooltip,
|
||||||
GlModal,
|
|
||||||
GlFormGroup,
|
|
||||||
GlFormInput,
|
|
||||||
GlFormTextarea,
|
|
||||||
GlAlert,
|
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
GlModalDirective,
|
GlModalDirective,
|
||||||
},
|
},
|
||||||
mixins: [trackingMixin],
|
inject: ['canAdminCluster'],
|
||||||
inject: ['agentName', 'projectPath', 'canAdminCluster'],
|
|
||||||
props: {
|
|
||||||
clusterAgentId: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
required: true,
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
modalId: CREATE_TOKEN_MODAL,
|
modalId: CREATE_TOKEN_MODAL,
|
||||||
EVENT_ACTIONS_OPEN,
|
|
||||||
EVENT_ACTIONS_CLICK,
|
|
||||||
EVENT_LABEL_MODAL,
|
|
||||||
TOKEN_NAME_LIMIT,
|
|
||||||
i18n: {
|
i18n: {
|
||||||
createTokenButton: s__('ClusterAgents|Create token'),
|
createTokenButton: s__('ClusterAgents|Create token'),
|
||||||
modalTitle: s__('ClusterAgents|Create agent access token'),
|
|
||||||
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
|
|
||||||
errorTitle: s__('ClusterAgents|Failed to create a token'),
|
|
||||||
dropdownDisabledHint: s__(
|
dropdownDisabledHint: s__(
|
||||||
'ClusterAgents|Requires a Maintainer or greater role to perform these actions',
|
'ClusterAgents|Requires a Maintainer or greater role to perform these actions',
|
||||||
),
|
),
|
||||||
modalCancel: __('Cancel'),
|
|
||||||
modalClose: __('Close'),
|
|
||||||
tokenNameLabel: __('Name'),
|
|
||||||
tokenDescriptionLabel: __('Description (optional)'),
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
token: {
|
|
||||||
name: null,
|
|
||||||
description: null,
|
|
||||||
},
|
|
||||||
agentToken: null,
|
|
||||||
error: null,
|
|
||||||
loading: false,
|
|
||||||
variables: {
|
|
||||||
agentName: this.agentName,
|
|
||||||
projectPath: this.projectPath,
|
|
||||||
tokenStatus: TOKEN_STATUS_ACTIVE,
|
|
||||||
...this.cursor,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
modalBtnDisabled() {
|
|
||||||
return this.loading || !this.hasTokenName;
|
|
||||||
},
|
|
||||||
hasTokenName() {
|
|
||||||
return Boolean(this.token.name?.length);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async createToken() {
|
|
||||||
this.loading = true;
|
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { errors: tokenErrors, secret } = await this.createAgentTokenMutation();
|
|
||||||
|
|
||||||
if (tokenErrors?.length > 0) {
|
|
||||||
throw new Error(tokenErrors[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.agentToken = secret;
|
|
||||||
} catch (error) {
|
|
||||||
if (error) {
|
|
||||||
this.error = error.message;
|
|
||||||
} else {
|
|
||||||
this.error = this.$options.i18n.unknownError;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetModal() {
|
|
||||||
this.agentToken = null;
|
|
||||||
this.token.name = null;
|
|
||||||
this.token.description = null;
|
|
||||||
this.error = null;
|
|
||||||
},
|
|
||||||
closeModal() {
|
|
||||||
this.$refs.modal.hide();
|
|
||||||
},
|
|
||||||
createAgentTokenMutation() {
|
|
||||||
return this.$apollo
|
|
||||||
.mutate({
|
|
||||||
mutation: createNewAgentToken,
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
clusterAgentId: this.clusterAgentId,
|
|
||||||
name: this.token.name,
|
|
||||||
description: this.token.description,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: (store, { data: { clusterAgentTokenCreate } }) => {
|
|
||||||
addAgentTokenToStore(
|
|
||||||
store,
|
|
||||||
clusterAgentTokenCreate,
|
|
||||||
getClusterAgentQuery,
|
|
||||||
this.variables,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(({ data: { clusterAgentTokenCreate } }) => clusterAgentTokenCreate);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -170,82 +39,5 @@ export default {
|
||||||
:title="$options.i18n.dropdownDisabledHint"
|
:title="$options.i18n.dropdownDisabledHint"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<gl-modal
|
|
||||||
ref="modal"
|
|
||||||
:modal-id="$options.modalId"
|
|
||||||
:title="$options.i18n.modalTitle"
|
|
||||||
static
|
|
||||||
lazy
|
|
||||||
@hidden="resetModal"
|
|
||||||
@show="track($options.EVENT_ACTIONS_OPEN)"
|
|
||||||
>
|
|
||||||
<gl-alert
|
|
||||||
v-if="error"
|
|
||||||
:title="$options.i18n.errorTitle"
|
|
||||||
:dismissible="false"
|
|
||||||
variant="danger"
|
|
||||||
class="gl-mb-5"
|
|
||||||
>
|
|
||||||
{{ error }}
|
|
||||||
</gl-alert>
|
|
||||||
|
|
||||||
<template v-if="!agentToken">
|
|
||||||
<gl-form-group :label="$options.i18n.tokenNameLabel">
|
|
||||||
<gl-form-input
|
|
||||||
v-model="token.name"
|
|
||||||
:max-length="$options.TOKEN_NAME_LIMIT"
|
|
||||||
:disabled="loading"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</gl-form-group>
|
|
||||||
|
|
||||||
<gl-form-group :label="$options.i18n.tokenDescriptionLabel">
|
|
||||||
<gl-form-textarea v-model="token.description" :disabled="loading" name="description" />
|
|
||||||
</gl-form-group>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<agent-token
|
|
||||||
v-else
|
|
||||||
:agent-name="agentName"
|
|
||||||
:agent-token="agentToken"
|
|
||||||
:modal-id="$options.modalId"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #modal-footer>
|
|
||||||
<gl-button
|
|
||||||
v-if="!agentToken && !loading"
|
|
||||||
:data-track-action="$options.EVENT_ACTIONS_CLICK"
|
|
||||||
:data-track-label="$options.EVENT_LABEL_MODAL"
|
|
||||||
data-track-property="close"
|
|
||||||
data-testid="agent-token-close-button"
|
|
||||||
@click="closeModal"
|
|
||||||
>{{ $options.i18n.modalCancel }}
|
|
||||||
</gl-button>
|
|
||||||
|
|
||||||
<gl-button
|
|
||||||
v-if="!agentToken"
|
|
||||||
:disabled="modalBtnDisabled"
|
|
||||||
:loading="loading"
|
|
||||||
:data-track-action="$options.EVENT_ACTIONS_CLICK"
|
|
||||||
:data-track-label="$options.EVENT_LABEL_MODAL"
|
|
||||||
data-track-property="create-token"
|
|
||||||
variant="confirm"
|
|
||||||
type="submit"
|
|
||||||
@click="createToken"
|
|
||||||
>{{ $options.i18n.createTokenButton }}
|
|
||||||
</gl-button>
|
|
||||||
|
|
||||||
<gl-button
|
|
||||||
v-else
|
|
||||||
:data-track-action="$options.EVENT_ACTIONS_CLICK"
|
|
||||||
:data-track-label="$options.EVENT_LABEL_MODAL"
|
|
||||||
data-track-property="close"
|
|
||||||
variant="confirm"
|
|
||||||
@click="closeModal"
|
|
||||||
>{{ $options.i18n.modalClose }}
|
|
||||||
</gl-button>
|
|
||||||
</template>
|
|
||||||
</gl-modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
<script>
|
||||||
|
import { GlButton, GlModal, GlFormGroup, GlFormInput, GlFormTextarea, GlAlert } from '@gitlab/ui';
|
||||||
|
import { s__, __ } from '~/locale';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
|
import AgentToken from '~/clusters_list/components/agent_token.vue';
|
||||||
|
import {
|
||||||
|
CREATE_TOKEN_MODAL,
|
||||||
|
EVENT_LABEL_MODAL,
|
||||||
|
EVENT_ACTIONS_OPEN,
|
||||||
|
EVENT_ACTIONS_CLICK,
|
||||||
|
TOKEN_NAME_LIMIT,
|
||||||
|
TOKEN_STATUS_ACTIVE,
|
||||||
|
} from '../constants';
|
||||||
|
import createNewAgentToken from '../graphql/mutations/create_new_agent_token.mutation.graphql';
|
||||||
|
import getClusterAgentQuery from '../graphql/queries/get_cluster_agent.query.graphql';
|
||||||
|
import { addAgentTokenToStore } from '../graphql/cache_update';
|
||||||
|
|
||||||
|
const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_MODAL });
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
AgentToken,
|
||||||
|
GlButton,
|
||||||
|
GlModal,
|
||||||
|
GlFormGroup,
|
||||||
|
GlFormInput,
|
||||||
|
GlFormTextarea,
|
||||||
|
GlAlert,
|
||||||
|
},
|
||||||
|
mixins: [trackingMixin],
|
||||||
|
inject: ['agentName', 'projectPath'],
|
||||||
|
props: {
|
||||||
|
clusterAgentId: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modalId: CREATE_TOKEN_MODAL,
|
||||||
|
EVENT_ACTIONS_OPEN,
|
||||||
|
EVENT_ACTIONS_CLICK,
|
||||||
|
EVENT_LABEL_MODAL,
|
||||||
|
TOKEN_NAME_LIMIT,
|
||||||
|
i18n: {
|
||||||
|
createTokenButton: s__('ClusterAgents|Create token'),
|
||||||
|
modalTitle: s__('ClusterAgents|Create agent access token'),
|
||||||
|
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
|
||||||
|
errorTitle: s__('ClusterAgents|Failed to create a token'),
|
||||||
|
modalCancel: __('Cancel'),
|
||||||
|
modalClose: __('Close'),
|
||||||
|
tokenNameLabel: __('Name'),
|
||||||
|
tokenDescriptionLabel: __('Description (optional)'),
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
token: {
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
},
|
||||||
|
agentToken: null,
|
||||||
|
error: null,
|
||||||
|
loading: false,
|
||||||
|
variables: {
|
||||||
|
agentName: this.agentName,
|
||||||
|
projectPath: this.projectPath,
|
||||||
|
tokenStatus: TOKEN_STATUS_ACTIVE,
|
||||||
|
...this.cursor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modalBtnDisabled() {
|
||||||
|
return this.loading || !this.hasTokenName;
|
||||||
|
},
|
||||||
|
hasTokenName() {
|
||||||
|
return Boolean(this.token.name?.length);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async createToken() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { errors: tokenErrors, secret } = await this.createAgentTokenMutation();
|
||||||
|
|
||||||
|
if (tokenErrors?.length > 0) {
|
||||||
|
throw new Error(tokenErrors[0]);
|
||||||
|
}
|
||||||
|
this.agentToken = secret;
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error ? error.message : this.$options.i18n.unknownError;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetModal() {
|
||||||
|
this.agentToken = null;
|
||||||
|
this.token.name = null;
|
||||||
|
this.token.description = null;
|
||||||
|
this.error = null;
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.$refs.modal.hide();
|
||||||
|
},
|
||||||
|
createAgentTokenMutation() {
|
||||||
|
return this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: createNewAgentToken,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
clusterAgentId: this.clusterAgentId,
|
||||||
|
name: this.token.name,
|
||||||
|
description: this.token.description,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: (store, { data: { clusterAgentTokenCreate } }) => {
|
||||||
|
addAgentTokenToStore(
|
||||||
|
store,
|
||||||
|
clusterAgentTokenCreate,
|
||||||
|
getClusterAgentQuery,
|
||||||
|
this.variables,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ data: { clusterAgentTokenCreate } }) => clusterAgentTokenCreate);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<gl-modal
|
||||||
|
ref="modal"
|
||||||
|
:modal-id="$options.modalId"
|
||||||
|
:title="$options.i18n.modalTitle"
|
||||||
|
static
|
||||||
|
lazy
|
||||||
|
@hidden="resetModal"
|
||||||
|
@show="track($options.EVENT_ACTIONS_OPEN)"
|
||||||
|
>
|
||||||
|
<gl-alert
|
||||||
|
v-if="error"
|
||||||
|
:title="$options.i18n.errorTitle"
|
||||||
|
:dismissible="false"
|
||||||
|
variant="danger"
|
||||||
|
class="gl-mb-5"
|
||||||
|
>
|
||||||
|
{{ error }}
|
||||||
|
</gl-alert>
|
||||||
|
|
||||||
|
<template v-if="!agentToken">
|
||||||
|
<gl-form-group :label="$options.i18n.tokenNameLabel" label-for="token-name">
|
||||||
|
<gl-form-input
|
||||||
|
id="token-name"
|
||||||
|
v-model="token.name"
|
||||||
|
:max-length="$options.TOKEN_NAME_LIMIT"
|
||||||
|
:disabled="loading"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</gl-form-group>
|
||||||
|
|
||||||
|
<gl-form-group :label="$options.i18n.tokenDescriptionLabel" label-for="token-description">
|
||||||
|
<gl-form-textarea
|
||||||
|
id="token-description"
|
||||||
|
v-model="token.description"
|
||||||
|
:disabled="loading"
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</gl-form-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<agent-token
|
||||||
|
v-else
|
||||||
|
:agent-name="agentName"
|
||||||
|
:agent-token="agentToken"
|
||||||
|
:modal-id="$options.modalId"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template #modal-footer>
|
||||||
|
<gl-button
|
||||||
|
v-if="!agentToken && !loading"
|
||||||
|
:data-track-action="$options.EVENT_ACTIONS_CLICK"
|
||||||
|
:data-track-label="$options.EVENT_LABEL_MODAL"
|
||||||
|
data-track-property="close"
|
||||||
|
data-testid="agent-token-close-button"
|
||||||
|
@click="closeModal"
|
||||||
|
>{{ $options.i18n.modalCancel }}
|
||||||
|
</gl-button>
|
||||||
|
|
||||||
|
<gl-button
|
||||||
|
v-if="!agentToken"
|
||||||
|
:disabled="modalBtnDisabled"
|
||||||
|
:loading="loading"
|
||||||
|
:data-track-action="$options.EVENT_ACTIONS_CLICK"
|
||||||
|
:data-track-label="$options.EVENT_LABEL_MODAL"
|
||||||
|
data-track-property="create-token"
|
||||||
|
variant="confirm"
|
||||||
|
type="submit"
|
||||||
|
@click="createToken"
|
||||||
|
>{{ $options.i18n.createTokenButton }}
|
||||||
|
</gl-button>
|
||||||
|
|
||||||
|
<gl-button
|
||||||
|
v-else
|
||||||
|
:data-track-action="$options.EVENT_ACTIONS_CLICK"
|
||||||
|
:data-track-label="$options.EVENT_LABEL_MODAL"
|
||||||
|
data-track-property="close"
|
||||||
|
variant="confirm"
|
||||||
|
@click="closeModal"
|
||||||
|
>{{ $options.i18n.modalClose }}
|
||||||
|
</gl-button>
|
||||||
|
</template>
|
||||||
|
</gl-modal>
|
||||||
|
</template>
|
|
@ -148,7 +148,7 @@ export default {
|
||||||
},
|
},
|
||||||
hideModal() {
|
hideModal() {
|
||||||
this.resetModal();
|
this.resetModal();
|
||||||
this.$refs.modal.hide();
|
this.$refs.modal?.hide();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { GlEmptyState, GlTable, GlTooltip, GlTruncate } from '@gitlab/ui';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||||
import CreateTokenButton from './create_token_button.vue';
|
import CreateTokenButton from './create_token_button.vue';
|
||||||
|
import CreateTokenModal from './create_token_modal.vue';
|
||||||
import RevokeTokenButton from './revoke_token_button.vue';
|
import RevokeTokenButton from './revoke_token_button.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -13,6 +14,7 @@ export default {
|
||||||
GlTruncate,
|
GlTruncate,
|
||||||
TimeAgoTooltip,
|
TimeAgoTooltip,
|
||||||
CreateTokenButton,
|
CreateTokenButton,
|
||||||
|
CreateTokenModal,
|
||||||
RevokeTokenButton,
|
RevokeTokenButton,
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
|
@ -85,57 +87,57 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="tokens.length">
|
<div>
|
||||||
<create-token-button
|
<div v-if="tokens.length">
|
||||||
class="gl-text-right gl-my-5"
|
<create-token-button class="gl-text-right gl-my-5" />
|
||||||
:cluster-agent-id="clusterAgentId"
|
|
||||||
:cursor="cursor"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<gl-table
|
<gl-table
|
||||||
:items="tokens"
|
:items="tokens"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
fixed
|
fixed
|
||||||
stacked="md"
|
stacked="md"
|
||||||
head-variant="white"
|
head-variant="white"
|
||||||
thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
|
thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
|
||||||
>
|
>
|
||||||
<template #cell(lastUsed)="{ item }">
|
<template #cell(lastUsed)="{ item }">
|
||||||
<time-ago-tooltip v-if="item.lastUsedAt" :time="item.lastUsedAt" />
|
<time-ago-tooltip v-if="item.lastUsedAt" :time="item.lastUsedAt" />
|
||||||
<span v-else>{{ $options.i18n.neverUsed }}</span>
|
<span v-else>{{ $options.i18n.neverUsed }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(createdAt)="{ item }">
|
||||||
|
<time-ago-tooltip :time="item.createdAt" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(createdBy)="{ item }">
|
||||||
|
<span>{{ createdByName(item) }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(description)="{ item }">
|
||||||
|
<div v-if="item.description" :id="`tooltip-description-container-${item.id}`">
|
||||||
|
<gl-truncate :id="`tooltip-description-${item.id}`" :text="item.description" />
|
||||||
|
|
||||||
|
<gl-tooltip
|
||||||
|
:container="`tooltip-description-container-${item.id}`"
|
||||||
|
:target="`tooltip-description-${item.id}`"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
{{ item.description }}
|
||||||
|
</gl-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(actions)="{ item }">
|
||||||
|
<revoke-token-button :token="item" :cluster-agent-id="clusterAgentId" :cursor="cursor" />
|
||||||
|
</template>
|
||||||
|
</gl-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<gl-empty-state v-else :title="$options.i18n.noTokens">
|
||||||
|
<template #actions>
|
||||||
|
<create-token-button />
|
||||||
</template>
|
</template>
|
||||||
|
</gl-empty-state>
|
||||||
|
|
||||||
<template #cell(createdAt)="{ item }">
|
<create-token-modal :cluster-agent-id="clusterAgentId" :cursor="cursor" />
|
||||||
<time-ago-tooltip :time="item.createdAt" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #cell(createdBy)="{ item }">
|
|
||||||
<span>{{ createdByName(item) }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #cell(description)="{ item }">
|
|
||||||
<div v-if="item.description" :id="`tooltip-description-container-${item.id}`">
|
|
||||||
<gl-truncate :id="`tooltip-description-${item.id}`" :text="item.description" />
|
|
||||||
|
|
||||||
<gl-tooltip
|
|
||||||
:container="`tooltip-description-container-${item.id}`"
|
|
||||||
:target="`tooltip-description-${item.id}`"
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
{{ item.description }}
|
|
||||||
</gl-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #cell(actions)="{ item }">
|
|
||||||
<revoke-token-button :token="item" :cluster-agent-id="clusterAgentId" :cursor="cursor" />
|
|
||||||
</template>
|
|
||||||
</gl-table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<gl-empty-state v-else :title="$options.i18n.noTokens">
|
|
||||||
<template #actions>
|
|
||||||
<create-token-button :cluster-agent-id="clusterAgentId" :cursor="cursor" />
|
|
||||||
</template>
|
|
||||||
</gl-empty-state>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
module Clusters
|
module Clusters
|
||||||
module Applications
|
module Applications
|
||||||
class Runner < ApplicationRecord
|
class Runner < ApplicationRecord
|
||||||
VERSION = '0.41.0'
|
VERSION = '0.42.0'
|
||||||
|
|
||||||
self.table_name = 'clusters_applications_runners'
|
self.table_name = 'clusters_applications_runners'
|
||||||
|
|
||||||
|
|
|
@ -108,13 +108,9 @@ class Deployment < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
after_transition any => :running do |deployment|
|
after_transition any => :running do |deployment, transition|
|
||||||
deployment.run_after_commit do
|
deployment.run_after_commit do
|
||||||
if Feature.enabled?(:deployment_hooks_skip_worker, deployment.project)
|
Deployments::HooksWorker.perform_async(deployment_id: id, status: transition.to, status_changed_at: Time.current)
|
||||||
deployment.execute_hooks(Time.current)
|
|
||||||
else
|
|
||||||
Deployments::HooksWorker.perform_async(deployment_id: id, status_changed_at: Time.current)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -126,13 +122,9 @@ class Deployment < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
after_transition any => FINISHED_STATUSES do |deployment|
|
after_transition any => FINISHED_STATUSES do |deployment, transition|
|
||||||
deployment.run_after_commit do
|
deployment.run_after_commit do
|
||||||
if Feature.enabled?(:deployment_hooks_skip_worker, deployment.project)
|
Deployments::HooksWorker.perform_async(deployment_id: id, status: transition.to, status_changed_at: Time.current)
|
||||||
deployment.execute_hooks(Time.current)
|
|
||||||
else
|
|
||||||
Deployments::HooksWorker.perform_async(deployment_id: id, status_changed_at: Time.current)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -269,8 +261,8 @@ class Deployment < ApplicationRecord
|
||||||
Commit.truncate_sha(sha)
|
Commit.truncate_sha(sha)
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_hooks(status_changed_at)
|
def execute_hooks(status, status_changed_at)
|
||||||
deployment_data = Gitlab::DataBuilder::Deployment.build(self, status_changed_at)
|
deployment_data = Gitlab::DataBuilder::Deployment.build(self, status, status_changed_at)
|
||||||
project.execute_hooks(deployment_data, :deployment_hooks)
|
project.execute_hooks(deployment_data, :deployment_hooks)
|
||||||
project.execute_integrations(deployment_data, :deployment_hooks)
|
project.execute_integrations(deployment_data, :deployment_hooks)
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,7 +63,7 @@ module Integrations
|
||||||
|
|
||||||
return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present?
|
return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present?
|
||||||
|
|
||||||
Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
|
Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current)
|
||||||
end
|
end
|
||||||
|
|
||||||
def releases_events_data
|
def releases_events_data
|
||||||
|
|
|
@ -11,5 +11,6 @@
|
||||||
= form_tag search_path, method: :get, class: 'form-inline-flex' do |f|
|
= form_tag search_path, method: :get, class: 'form-inline-flex' do |f|
|
||||||
.field
|
.field
|
||||||
= search_field_tag :search, '', placeholder: _('Search for projects, issues, etc.'), class: 'form-control'
|
= search_field_tag :search, '', placeholder: _('Search for projects, issues, etc.'), class: 'form-control'
|
||||||
= button_tag _('Search'), class: 'gl-button btn btn-sm btn-success', name: nil, type: 'submit'
|
= render Pajamas::ButtonComponent.new(variant: :confirm, size: :small, type: :submit) do
|
||||||
|
= _('Search')
|
||||||
= render 'errors/footer'
|
= render 'errors/footer'
|
||||||
|
|
|
@ -45,7 +45,8 @@
|
||||||
·
|
·
|
||||||
%span.js-expires-in-text{ class: "has-tooltip#{' text-warning' if member.expires_soon?}", title: (member.expires_at.to_time.in_time_zone.to_s(:medium) if member.expires?) }
|
%span.js-expires-in-text{ class: "has-tooltip#{' text-warning' if member.expires_soon?}", title: (member.expires_at.to_time.in_time_zone.to_s(:medium) if member.expires?) }
|
||||||
- if member.expires?
|
- if member.expires?
|
||||||
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(member.expires_at) }
|
- preposition = current_user.time_display_relative ? '' : 'on'
|
||||||
|
= _("Expires %{preposition} %{expires_at}").html_safe % { expires_at: time_ago_with_tooltip(member.expires_at), preposition: preposition }
|
||||||
|
|
||||||
- else
|
- else
|
||||||
= image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
|
= image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
|
||||||
|
|
|
@ -16,7 +16,7 @@ module Deployments
|
||||||
log_extra_metadata_on_done(:deployment_project_id, deploy.project.id)
|
log_extra_metadata_on_done(:deployment_project_id, deploy.project.id)
|
||||||
log_extra_metadata_on_done(:deployment_id, params[:deployment_id])
|
log_extra_metadata_on_done(:deployment_id, params[:deployment_id])
|
||||||
|
|
||||||
deploy.execute_hooks(params[:status_changed_at].to_time)
|
deploy.execute_hooks(params[:status], params[:status_changed_at].to_time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: deployment_hooks_skip_worker
|
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83351
|
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/356468
|
|
||||||
milestone: '14.10'
|
|
||||||
type: development
|
|
||||||
group: group::integrations
|
|
||||||
default_enabled: false
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: gitaly_revlist_for_repo_size
|
||||||
|
introduced_by_url:
|
||||||
|
rollout_issue_url:
|
||||||
|
milestone:
|
||||||
|
type: undefined
|
||||||
|
group:
|
||||||
|
default_enabled: false
|
|
@ -19669,8 +19669,9 @@ The status of the security scan.
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
| ----- | ----------- |
|
| ----- | ----------- |
|
||||||
| <a id="securitypolicyrelationtypedirect"></a>`DIRECT` | Policies defined for the project only. |
|
| <a id="securitypolicyrelationtypedirect"></a>`DIRECT` | Policies defined for the project/group only. |
|
||||||
| <a id="securitypolicyrelationtypeinherited"></a>`INHERITED` | Policies defined for the project and project's ancestor groups. |
|
| <a id="securitypolicyrelationtypeinherited"></a>`INHERITED` | Policies defined for the project/group and ancestor groups. |
|
||||||
|
| <a id="securitypolicyrelationtypeinherited_only"></a>`INHERITED_ONLY` | Policies defined for the project/group's ancestor groups only. |
|
||||||
|
|
||||||
### `SecurityReportTypeEnum`
|
### `SecurityReportTypeEnum`
|
||||||
|
|
||||||
|
|
|
@ -79,12 +79,10 @@ page, with these behaviors:
|
||||||
- **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:`
|
- **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:`
|
||||||
- **At capacity** - 🔴 `:red_circle:`
|
- **At capacity** - 🔴 `:red_circle:`
|
||||||
- **Focus mode** - 💡 `:bulb:` (focusing on their team's work)
|
- **Focus mode** - 💡 `:bulb:` (focusing on their team's work)
|
||||||
1. [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
|
|
||||||
are three times as likely to be picked as other reviewers.
|
|
||||||
1. Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji
|
1. Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji
|
||||||
is 🔵 `:large_blue_circle:` are more likely to be picked. This applies to both reviewers and trainee maintainers.
|
is 🔵 `:large_blue_circle:` are more likely to be picked. This applies to both reviewers and trainee maintainers.
|
||||||
- Reviewers with 🔵 `:large_blue_circle:` are two times as likely to be picked as other reviewers.
|
- Reviewers with 🔵 `:large_blue_circle:` are two times as likely to be picked as other reviewers.
|
||||||
- Trainee maintainers with 🔵 `:large_blue_circle:` are four times as likely to be picked as other reviewers.
|
- [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer) with 🔵 `:large_blue_circle:` are three times as likely to be picked as other reviewers.
|
||||||
1. People whose [GitLab status](../user/profile/index.md#set-your-current-status) emoji
|
1. People whose [GitLab status](../user/profile/index.md#set-your-current-status) emoji
|
||||||
is 🔶 `:large_orange_diamond:` or 🔸 `:small_orange_diamond:` are half as likely to be picked.
|
is 🔶 `:large_orange_diamond:` or 🔸 `:small_orange_diamond:` are half as likely to be picked.
|
||||||
1. It always picks the same reviewers and maintainers for the same
|
1. It always picks the same reviewers and maintainers for the same
|
||||||
|
|
|
@ -668,6 +668,19 @@ The most popular public email domains cannot be restricted, such as:
|
||||||
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
||||||
- `msn.com`, `live.com`, `outlook.com`
|
- `msn.com`, `live.com`, `outlook.com`
|
||||||
|
|
||||||
|
## Restrict Git access protocols
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1.
|
||||||
|
|
||||||
|
Access to the group's repositories via SSH or HTTP(S) can be restricted to individual protocols. This setting is overridden by the instance setting configured in the GitLab Admin.
|
||||||
|
|
||||||
|
To alter the permitted Git access protocols:
|
||||||
|
|
||||||
|
1. Go to the group's **Settings > General** page.
|
||||||
|
1. Expand the **Permissions and group features** section.
|
||||||
|
1. Choose the allowed protocols from **Enable Git access protocols**
|
||||||
|
1. Select **Save changes**
|
||||||
|
|
||||||
## Group file templates **(PREMIUM)**
|
## Group file templates **(PREMIUM)**
|
||||||
|
|
||||||
Use group file templates to share a set of templates for common file
|
Use group file templates to share a set of templates for common file
|
||||||
|
|
|
@ -5,7 +5,8 @@ module Gitlab
|
||||||
module Deployment
|
module Deployment
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
def build(deployment, status_changed_at)
|
# NOTE: Time-sensitive attributes should be explicitly passed as argument instead of reading from database.
|
||||||
|
def build(deployment, status, status_changed_at)
|
||||||
# Deployments will not have a deployable when created using the API.
|
# Deployments will not have a deployable when created using the API.
|
||||||
deployable_url =
|
deployable_url =
|
||||||
if deployment.deployable
|
if deployment.deployable
|
||||||
|
@ -22,9 +23,13 @@ module Gitlab
|
||||||
Gitlab::UrlBuilder.build(deployment.deployed_by)
|
Gitlab::UrlBuilder.build(deployment.deployed_by)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `status` argument could be `nil` during the upgrade. We can remove `deployment.status` in GitLab 15.5.
|
||||||
|
# See https://docs.gitlab.com/ee/development/multi_version_compatibility.html for more info.
|
||||||
|
deployment_status = status || deployment.status
|
||||||
|
|
||||||
{
|
{
|
||||||
object_kind: 'deployment',
|
object_kind: 'deployment',
|
||||||
status: deployment.status,
|
status: deployment_status,
|
||||||
status_changed_at: status_changed_at,
|
status_changed_at: status_changed_at,
|
||||||
deployment_id: deployment.id,
|
deployment_id: deployment.id,
|
||||||
deployable_id: deployment.deployable_id,
|
deployable_id: deployment.deployable_id,
|
||||||
|
|
|
@ -15315,7 +15315,7 @@ msgstr ""
|
||||||
msgid "Expires"
|
msgid "Expires"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Expires in %{expires_at}"
|
msgid "Expires %{preposition} %{expires_at}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Expires on"
|
msgid "Expires on"
|
||||||
|
|
|
@ -218,6 +218,31 @@ module QA
|
||||||
run_git('git --no-pager branch --list --remotes --format="%(refname:lstrip=3)"').to_s.split("\n")
|
run_git('git --no-pager branch --list --remotes --format="%(refname:lstrip=3)"').to_s.split("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets the size of the repository using `git rev-list --all --objects --use-bitmap-index --disk-usage` as
|
||||||
|
# Gitaly does (see https://gitlab.com/gitlab-org/gitlab/-/issues/357680)
|
||||||
|
def local_size
|
||||||
|
internal_refs = %w[
|
||||||
|
refs/keep-around/
|
||||||
|
refs/merge-requests/
|
||||||
|
refs/pipelines/
|
||||||
|
refs/remotes/
|
||||||
|
refs/tmp/
|
||||||
|
refs/environments/
|
||||||
|
]
|
||||||
|
cmd = <<~CMD
|
||||||
|
git rev-list #{internal_refs.map { |r| "--exclude='#{r}*'" }.join(' ')} \
|
||||||
|
--not --alternate-refs --not \
|
||||||
|
--all --objects --use-bitmap-index --disk-usage
|
||||||
|
CMD
|
||||||
|
|
||||||
|
run_git(cmd).to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# Performs garbage collection
|
||||||
|
def run_gc
|
||||||
|
run_git('git gc')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :uri, :username, :password, :ssh, :use_lfs
|
attr_reader :uri, :username, :password, :ssh, :use_lfs
|
||||||
|
|
|
@ -224,6 +224,10 @@ module QA
|
||||||
"#{api_get_path}/releases"
|
"#{api_get_path}/releases"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def api_housekeeping_path
|
||||||
|
"/projects/#{id}/housekeeping"
|
||||||
|
end
|
||||||
|
|
||||||
def api_post_body
|
def api_post_body
|
||||||
post_body = {
|
post_body = {
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -447,6 +451,31 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Calls the API endpoint that triggers the backend service that performs repository housekeeping (garbage
|
||||||
|
# collection and similar tasks).
|
||||||
|
def perform_housekeeping
|
||||||
|
Runtime::Logger.debug("Calling API endpoint #{api_housekeeping_path}")
|
||||||
|
|
||||||
|
response = post(request_url(api_housekeeping_path), nil)
|
||||||
|
|
||||||
|
unless response.code == HTTP_STATUS_CREATED
|
||||||
|
raise ResourceQueryError,
|
||||||
|
"Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gets project statistics.
|
||||||
|
#
|
||||||
|
# @return [Hash] the project usage data including repository size.
|
||||||
|
def statistics
|
||||||
|
response = get(request_url("#{api_get_path}?statistics=true"))
|
||||||
|
data = parse_body(response)
|
||||||
|
|
||||||
|
raise "Could not get project usage statistics" unless data.key?(:statistics)
|
||||||
|
|
||||||
|
data[:statistics]
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# Return subset of fields for comparing projects
|
# Return subset of fields for comparing projects
|
||||||
|
|
|
@ -10,7 +10,7 @@ module QA
|
||||||
let(:differ) { RSpec::Support::Differ.new(color: true) }
|
let(:differ) { RSpec::Support::Differ.new(color: true) }
|
||||||
let(:gitlab_group) { ENV['QA_LARGE_IMPORT_GROUP'] || 'gitlab-migration' }
|
let(:gitlab_group) { ENV['QA_LARGE_IMPORT_GROUP'] || 'gitlab-migration' }
|
||||||
let(:gitlab_project) { ENV['QA_LARGE_IMPORT_REPO'] || 'dri' }
|
let(:gitlab_project) { ENV['QA_LARGE_IMPORT_REPO'] || 'dri' }
|
||||||
let(:gitlab_source_address) { 'https://staging.gitlab.com' }
|
let(:gitlab_source_address) { ENV['QA_LARGE_IMPORT_SOURCE_URL'] || 'https://staging.gitlab.com' }
|
||||||
|
|
||||||
let(:import_wait_duration) do
|
let(:import_wait_duration) do
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module QA
|
||||||
|
RSpec.describe 'Create' do
|
||||||
|
describe 'Repository Usage Quota', :skip_live_env, feature_flag: {
|
||||||
|
name: 'gitaly_revlist_for_repo_size',
|
||||||
|
scope: :global
|
||||||
|
} do
|
||||||
|
let(:project_name) { "repository-usage-#{SecureRandom.hex(8)}" }
|
||||||
|
let!(:flag_enabled) { Runtime::Feature.enabled?(:gitaly_revlist_for_repo_size) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Runtime::Feature.enable(:gitaly_revlist_for_repo_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
Runtime::Feature.set({ gitaly_revlist_for_repo_size: flag_enabled })
|
||||||
|
end
|
||||||
|
|
||||||
|
# Previously, GitLab could report a size many times larger than a cloned copy. For example, 37Gb reported for a
|
||||||
|
# repo that is 2Gb when cloned.
|
||||||
|
#
|
||||||
|
# After changing Gitaly to use `git rev-list` to determine the size of a repo, the reported size is much more
|
||||||
|
# accurate. Nonetheless, the size of a clone is still not necessarily the same as the original. We can't do a
|
||||||
|
# precise comparison because of the non-deterministic nature of how git packs files. Depending on the history of
|
||||||
|
# the repository the sizes can vary considerably. For example, at the time of writing this a clone of
|
||||||
|
# www-gitlab-com was 5.27Gb, about 5% smaller than the size GitLab reported, 5.51Gb.
|
||||||
|
#
|
||||||
|
# There are unit tests to verify the accuracy of GitLab's determination of repo size, so for this test we
|
||||||
|
# attempt to detect large differences that could indicate a regression to previous behavior.
|
||||||
|
it 'matches cloned repo usage to reported usage',
|
||||||
|
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/365196' do
|
||||||
|
project = Resource::Project.fabricate_via_api! do |project|
|
||||||
|
project.name = project_name
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_data = SecureRandom.random_bytes(500000)
|
||||||
|
|
||||||
|
Resource::Repository::ProjectPush.fabricate! do |push|
|
||||||
|
push.project = project
|
||||||
|
push.file_name = 'data.dat'
|
||||||
|
push.file_content = SecureRandom.random_bytes(500000) + shared_data
|
||||||
|
push.commit_message = 'Add file'
|
||||||
|
end
|
||||||
|
|
||||||
|
local_size = Git::Repository.perform do |repository|
|
||||||
|
repository.uri = project.repository_http_location.uri
|
||||||
|
repository.use_default_credentials
|
||||||
|
repository.clone
|
||||||
|
repository.configure_identity('GitLab QA', 'root@gitlab.com')
|
||||||
|
# These two commits add a total of 1mb, but half of that is the same as content that has already been added to
|
||||||
|
# the repository, so garbage collection will deduplicate it.
|
||||||
|
repository.commit_file("new-data", SecureRandom.random_bytes(500000), "Add file")
|
||||||
|
repository.commit_file("redudant-data", shared_data, "Add file")
|
||||||
|
repository.run_gc
|
||||||
|
repository.push_changes
|
||||||
|
repository.local_size
|
||||||
|
end
|
||||||
|
|
||||||
|
# The size of the remote repository after all content has been added.
|
||||||
|
initial_size = project.statistics[:repository_size].to_i
|
||||||
|
|
||||||
|
# This is an async process and as a user we have no way to know when it's complete unless the statistics are
|
||||||
|
# updated
|
||||||
|
Support::Retrier.retry_until(max_duration: 60, sleep_interval: 5) do
|
||||||
|
# This should perform the same deduplication as in the local repo
|
||||||
|
project.perform_housekeeping
|
||||||
|
|
||||||
|
project.statistics[:repository_size].to_i != initial_size
|
||||||
|
end
|
||||||
|
|
||||||
|
twentyfive_percent = local_size.to_i * 0.25
|
||||||
|
expect(project.statistics[:repository_size].to_i).to be_within(twentyfive_percent).of(local_size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,6 +15,10 @@ module QA
|
||||||
def success?
|
def success?
|
||||||
exitstatus == 0 && !response.include?('Error encountered')
|
exitstatus == 0 && !response.include?('Error encountered')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_i
|
||||||
|
response.to_i
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(command_str, env: [], max_attempts: 1, log_prefix: '')
|
def run(command_str, env: [], max_attempts: 1, log_prefix: '')
|
||||||
|
|
|
@ -231,7 +231,7 @@ module Glfm
|
||||||
name = example.fetch(:name)
|
name = example.fetch(:name)
|
||||||
|
|
||||||
json = if glfm_examples_statuses.dig(name, 'skip_update_example_snapshot_prosemirror_json')
|
json = if glfm_examples_statuses.dig(name, 'skip_update_example_snapshot_prosemirror_json')
|
||||||
existing_hash.dig(name)
|
existing_hash[name]
|
||||||
else
|
else
|
||||||
wysiwyg_html_and_json_hash.dig(name, 'json')
|
wysiwyg_html_and_json_hash.dig(name, 'json')
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ RSpec.describe 'Admin Groups' do
|
||||||
|
|
||||||
let_it_be(:user) { create :user }
|
let_it_be(:user) { create :user }
|
||||||
let_it_be(:group) { create :group }
|
let_it_be(:group) { create :group }
|
||||||
let_it_be(:current_user) { create(:admin) }
|
let_it_be_with_reload(:current_user) { create(:admin) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(current_user)
|
sign_in(current_user)
|
||||||
|
@ -231,6 +231,28 @@ RSpec.describe 'Admin Groups' do
|
||||||
it_behaves_like 'adds user into a group' do
|
it_behaves_like 'adds user into a group' do
|
||||||
let(:user_selector) { user.email }
|
let(:user_selector) { user.email }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when membership is set to expire' do
|
||||||
|
it 'renders relative time' do
|
||||||
|
expire_time = Time.current + 2.days
|
||||||
|
current_user.update!(time_display_relative: true)
|
||||||
|
group.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||||
|
|
||||||
|
visit admin_group_path(group)
|
||||||
|
|
||||||
|
expect(page).to have_content(/Expires in \d day/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders absolute time' do
|
||||||
|
expire_time = Time.current.tomorrow.middle_of_day
|
||||||
|
current_user.update!(time_display_relative: false)
|
||||||
|
group.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||||
|
|
||||||
|
visit admin_group_path(group)
|
||||||
|
|
||||||
|
expect(page).to have_content("Expires on #{expire_time.strftime('%b %-d')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'add admin himself to a group' do
|
describe 'add admin himself to a group' do
|
||||||
|
|
|
@ -7,15 +7,37 @@ RSpec.describe "Admin::Projects" do
|
||||||
include Spec::Support::Helpers::Features::InviteMembersModalHelper
|
include Spec::Support::Helpers::Features::InviteMembersModalHelper
|
||||||
include Spec::Support::Helpers::ModalHelpers
|
include Spec::Support::Helpers::ModalHelpers
|
||||||
|
|
||||||
let(:user) { create :user }
|
let_it_be_with_reload(:user) { create :user }
|
||||||
let(:project) { create(:project, :with_namespace_settings) }
|
let_it_be_with_reload(:project) { create(:project, :with_namespace_settings) }
|
||||||
let(:current_user) { create(:admin) }
|
let_it_be_with_reload(:current_user) { create(:admin) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(current_user)
|
sign_in(current_user)
|
||||||
gitlab_enable_admin_mode_sign_in(current_user)
|
gitlab_enable_admin_mode_sign_in(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'when membership is set to expire', :js do
|
||||||
|
it 'renders relative time' do
|
||||||
|
expire_time = Time.current + 2.days
|
||||||
|
current_user.update!(time_display_relative: true)
|
||||||
|
project.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||||
|
|
||||||
|
visit admin_project_path(project)
|
||||||
|
|
||||||
|
expect(page).to have_content(/Expires in \d day/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders absolute time' do
|
||||||
|
expire_time = Time.current.tomorrow.middle_of_day
|
||||||
|
current_user.update!(time_display_relative: false)
|
||||||
|
project.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||||
|
|
||||||
|
visit admin_project_path(project)
|
||||||
|
|
||||||
|
expect(page).to have_content("Expires on #{expire_time.strftime('%b %-d')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET /admin/projects" do
|
describe "GET /admin/projects" do
|
||||||
let!(:archived_project) { create :project, :public, :archived }
|
let!(:archived_project) { create :project, :public, :archived }
|
||||||
|
|
||||||
|
|
|
@ -1,262 +1,71 @@
|
||||||
import { GlButton, GlTooltip, GlModal, GlFormInput, GlFormTextarea, GlAlert } from '@gitlab/ui';
|
import { GlButton, GlTooltip } from '@gitlab/ui';
|
||||||
import Vue from 'vue';
|
|
||||||
import VueApollo from 'vue-apollo';
|
|
||||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import { mockTracking } from 'helpers/tracking_helper';
|
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||||
import {
|
|
||||||
EVENT_LABEL_MODAL,
|
|
||||||
EVENT_ACTIONS_OPEN,
|
|
||||||
TOKEN_NAME_LIMIT,
|
|
||||||
TOKEN_STATUS_ACTIVE,
|
|
||||||
MAX_LIST_COUNT,
|
|
||||||
CREATE_TOKEN_MODAL,
|
|
||||||
} from '~/clusters/agents/constants';
|
|
||||||
import createNewAgentToken from '~/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql';
|
|
||||||
import getClusterAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql';
|
|
||||||
import AgentToken from '~/clusters_list/components/agent_token.vue';
|
|
||||||
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
|
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
|
||||||
import {
|
import { CREATE_TOKEN_MODAL } from '~/clusters/agents/constants';
|
||||||
clusterAgentToken,
|
|
||||||
getTokenResponse,
|
|
||||||
createAgentTokenErrorResponse,
|
|
||||||
} from '../../mock_data';
|
|
||||||
|
|
||||||
Vue.use(VueApollo);
|
|
||||||
|
|
||||||
describe('CreateTokenButton', () => {
|
describe('CreateTokenButton', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let apolloProvider;
|
|
||||||
let trackingSpy;
|
|
||||||
let createResponse;
|
|
||||||
|
|
||||||
const clusterAgentId = 'cluster-agent-id';
|
|
||||||
const cursor = {
|
|
||||||
first: MAX_LIST_COUNT,
|
|
||||||
last: null,
|
|
||||||
};
|
|
||||||
const agentName = 'cluster-agent';
|
|
||||||
const projectPath = 'path/to/project';
|
|
||||||
|
|
||||||
const defaultProvide = {
|
const defaultProvide = {
|
||||||
agentName,
|
|
||||||
projectPath,
|
|
||||||
canAdminCluster: true,
|
canAdminCluster: true,
|
||||||
};
|
};
|
||||||
const propsData = {
|
|
||||||
clusterAgentId,
|
|
||||||
cursor,
|
|
||||||
};
|
|
||||||
|
|
||||||
const findModal = () => wrapper.findComponent(GlModal);
|
const findButton = () => wrapper.findComponent(GlButton);
|
||||||
const findBtn = () => wrapper.findComponent(GlButton);
|
|
||||||
const findInput = () => wrapper.findComponent(GlFormInput);
|
|
||||||
const findTextarea = () => wrapper.findComponent(GlFormTextarea);
|
|
||||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
|
||||||
const findTooltip = () => wrapper.findComponent(GlTooltip);
|
const findTooltip = () => wrapper.findComponent(GlTooltip);
|
||||||
const findAgentInstructions = () => findModal().findComponent(AgentToken);
|
|
||||||
const findButtonByVariant = (variant) =>
|
|
||||||
findModal()
|
|
||||||
.findAll(GlButton)
|
|
||||||
.wrappers.find((button) => button.props('variant') === variant);
|
|
||||||
const findActionButton = () => findButtonByVariant('confirm');
|
|
||||||
const findCancelButton = () => wrapper.findByTestId('agent-token-close-button');
|
|
||||||
|
|
||||||
const expectDisabledAttribute = (element, disabled) => {
|
const createWrapper = ({ provideData = {} } = {}) => {
|
||||||
if (disabled) {
|
|
||||||
expect(element.attributes('disabled')).toBe('true');
|
|
||||||
} else {
|
|
||||||
expect(element.attributes('disabled')).toBeUndefined();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createMockApolloProvider = ({ mutationResponse }) => {
|
|
||||||
createResponse = jest.fn().mockResolvedValue(mutationResponse);
|
|
||||||
|
|
||||||
return createMockApollo([[createNewAgentToken, createResponse]]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeQuery = () => {
|
|
||||||
apolloProvider.clients.defaultClient.cache.writeQuery({
|
|
||||||
query: getClusterAgentQuery,
|
|
||||||
data: getTokenResponse.data,
|
|
||||||
variables: {
|
|
||||||
agentName,
|
|
||||||
projectPath,
|
|
||||||
tokenStatus: TOKEN_STATUS_ACTIVE,
|
|
||||||
...cursor,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createWrapper = async ({ provideData = {} } = {}) => {
|
|
||||||
wrapper = shallowMountExtended(CreateTokenButton, {
|
wrapper = shallowMountExtended(CreateTokenButton, {
|
||||||
apolloProvider,
|
|
||||||
provide: {
|
provide: {
|
||||||
...defaultProvide,
|
...defaultProvide,
|
||||||
...provideData,
|
...provideData,
|
||||||
},
|
},
|
||||||
propsData,
|
directives: {
|
||||||
|
GlModalDirective: createMockDirective(),
|
||||||
|
},
|
||||||
stubs: {
|
stubs: {
|
||||||
GlModal,
|
|
||||||
GlTooltip,
|
GlTooltip,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
wrapper.vm.$refs.modal.hide = jest.fn();
|
|
||||||
|
|
||||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockCreatedResponse = (mutationResponse) => {
|
|
||||||
apolloProvider = createMockApolloProvider({ mutationResponse });
|
|
||||||
writeQuery();
|
|
||||||
|
|
||||||
createWrapper();
|
|
||||||
|
|
||||||
findInput().vm.$emit('input', 'new-token');
|
|
||||||
findTextarea().vm.$emit('input', 'new-token-description');
|
|
||||||
findActionButton().vm.$emit('click');
|
|
||||||
|
|
||||||
return waitForPromises();
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
createWrapper();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
apolloProvider = null;
|
|
||||||
createResponse = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create agent token action', () => {
|
describe('when user can create token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createWrapper();
|
||||||
|
});
|
||||||
|
|
||||||
it('displays create agent token button', () => {
|
it('displays create agent token button', () => {
|
||||||
expect(findBtn().text()).toBe('Create token');
|
expect(findButton().text()).toBe('Create token');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when user cannot create token', () => {
|
it('displays create agent token button as not disabled', () => {
|
||||||
beforeEach(() => {
|
expect(findButton().attributes('disabled')).toBeUndefined();
|
||||||
createWrapper({ provideData: { canAdminCluster: false } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('disabled the button', () => {
|
|
||||||
expect(findBtn().attributes('disabled')).toBe('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a disabled tooltip', () => {
|
|
||||||
expect(findTooltip().attributes('title')).toBe(
|
|
||||||
'Requires a Maintainer or greater role to perform these actions',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when user can create a token and clicks the button', () => {
|
it('triggers the modal', () => {
|
||||||
beforeEach(() => {
|
const binding = getBinding(findButton().element, 'gl-modal-directive');
|
||||||
findBtn().vm.$emit('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays a token creation modal', () => {
|
expect(binding.value).toBe(CREATE_TOKEN_MODAL);
|
||||||
expect(findModal().isVisible()).toBe(true);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initial state', () => {
|
describe('when user cannot create token', () => {
|
||||||
it('renders an input for the token name', () => {
|
beforeEach(() => {
|
||||||
expect(findInput().exists()).toBe(true);
|
createWrapper({ provideData: { canAdminCluster: false } });
|
||||||
expectDisabledAttribute(findInput(), false);
|
});
|
||||||
expect(findInput().attributes('max-length')).toBe(TOKEN_NAME_LIMIT.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a textarea for the token description', () => {
|
it('disabled the button', () => {
|
||||||
expect(findTextarea().exists()).toBe(true);
|
expect(findButton().attributes('disabled')).toBe('true');
|
||||||
expectDisabledAttribute(findTextarea(), false);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a cancel button', () => {
|
it('shows a disabled tooltip', () => {
|
||||||
expect(findCancelButton().isVisible()).toBe(true);
|
expect(findTooltip().attributes('title')).toBe(
|
||||||
expectDisabledAttribute(findCancelButton(), false);
|
'Requires a Maintainer or greater role to perform these actions',
|
||||||
});
|
);
|
||||||
|
|
||||||
it('renders a disabled next button', () => {
|
|
||||||
expect(findActionButton().text()).toBe('Create token');
|
|
||||||
expectDisabledAttribute(findActionButton(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sends tracking event for modal shown', () => {
|
|
||||||
findModal().vm.$emit('show');
|
|
||||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
|
|
||||||
label: EVENT_LABEL_MODAL,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when user inputs the token name', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
expectDisabledAttribute(findActionButton(), true);
|
|
||||||
findInput().vm.$emit('input', 'new-token');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('enables the next button', () => {
|
|
||||||
expectDisabledAttribute(findActionButton(), false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when user clicks the create-token button', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const loadingResponse = new Promise(() => {});
|
|
||||||
await mockCreatedResponse(loadingResponse);
|
|
||||||
|
|
||||||
findInput().vm.$emit('input', 'new-token');
|
|
||||||
findActionButton().vm.$emit('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('disables the create-token button', () => {
|
|
||||||
expectDisabledAttribute(findActionButton(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('hides the cancel button', () => {
|
|
||||||
expect(findCancelButton().exists()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('creating a new token', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await mockCreatedResponse(clusterAgentToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates a token', () => {
|
|
||||||
expect(createResponse).toHaveBeenCalledWith({
|
|
||||||
input: { clusterAgentId, name: 'new-token', description: 'new-token-description' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows agent instructions', () => {
|
|
||||||
expect(findAgentInstructions().props()).toMatchObject({
|
|
||||||
agentName,
|
|
||||||
agentToken: 'token-secret',
|
|
||||||
modalId: CREATE_TOKEN_MODAL,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a close button', () => {
|
|
||||||
expect(findActionButton().isVisible()).toBe(true);
|
|
||||||
expect(findActionButton().text()).toBe('Close');
|
|
||||||
expectDisabledAttribute(findActionButton(), false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('error creating a new token', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await mockCreatedResponse(createAgentTokenErrorResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays the error message', async () => {
|
|
||||||
expect(findAlert().text()).toBe(
|
|
||||||
createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
import { GlButton, GlModal, GlFormInput, GlFormTextarea, GlAlert } from '@gitlab/ui';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
import { mockTracking } from 'helpers/tracking_helper';
|
||||||
|
import {
|
||||||
|
EVENT_LABEL_MODAL,
|
||||||
|
EVENT_ACTIONS_OPEN,
|
||||||
|
TOKEN_NAME_LIMIT,
|
||||||
|
TOKEN_STATUS_ACTIVE,
|
||||||
|
MAX_LIST_COUNT,
|
||||||
|
CREATE_TOKEN_MODAL,
|
||||||
|
} from '~/clusters/agents/constants';
|
||||||
|
import createNewAgentToken from '~/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql';
|
||||||
|
import getClusterAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql';
|
||||||
|
import AgentToken from '~/clusters_list/components/agent_token.vue';
|
||||||
|
import CreateTokenModal from '~/clusters/agents/components/create_token_modal.vue';
|
||||||
|
import {
|
||||||
|
clusterAgentToken,
|
||||||
|
getTokenResponse,
|
||||||
|
createAgentTokenErrorResponse,
|
||||||
|
} from '../../mock_data';
|
||||||
|
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
describe('CreateTokenModal', () => {
|
||||||
|
let wrapper;
|
||||||
|
let apolloProvider;
|
||||||
|
let trackingSpy;
|
||||||
|
let createResponse;
|
||||||
|
|
||||||
|
const clusterAgentId = 'cluster-agent-id';
|
||||||
|
const cursor = {
|
||||||
|
first: MAX_LIST_COUNT,
|
||||||
|
last: null,
|
||||||
|
};
|
||||||
|
const agentName = 'cluster-agent';
|
||||||
|
const projectPath = 'path/to/project';
|
||||||
|
|
||||||
|
const provide = {
|
||||||
|
agentName,
|
||||||
|
projectPath,
|
||||||
|
};
|
||||||
|
const propsData = {
|
||||||
|
clusterAgentId,
|
||||||
|
cursor,
|
||||||
|
};
|
||||||
|
|
||||||
|
const findModal = () => wrapper.findComponent(GlModal);
|
||||||
|
const findInput = () => wrapper.findComponent(GlFormInput);
|
||||||
|
const findTextarea = () => wrapper.findComponent(GlFormTextarea);
|
||||||
|
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||||
|
const findAgentInstructions = () => findModal().findComponent(AgentToken);
|
||||||
|
const findButtonByVariant = (variant) =>
|
||||||
|
findModal()
|
||||||
|
.findAll(GlButton)
|
||||||
|
.wrappers.find((button) => button.props('variant') === variant);
|
||||||
|
const findActionButton = () => findButtonByVariant('confirm');
|
||||||
|
const findCancelButton = () => wrapper.findByTestId('agent-token-close-button');
|
||||||
|
|
||||||
|
const expectDisabledAttribute = (element, disabled) => {
|
||||||
|
if (disabled) {
|
||||||
|
expect(element.attributes('disabled')).toBe('true');
|
||||||
|
} else {
|
||||||
|
expect(element.attributes('disabled')).toBeUndefined();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMockApolloProvider = ({ mutationResponse }) => {
|
||||||
|
createResponse = jest.fn().mockResolvedValue(mutationResponse);
|
||||||
|
|
||||||
|
return createMockApollo([[createNewAgentToken, createResponse]]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeQuery = () => {
|
||||||
|
apolloProvider.clients.defaultClient.cache.writeQuery({
|
||||||
|
query: getClusterAgentQuery,
|
||||||
|
data: getTokenResponse.data,
|
||||||
|
variables: {
|
||||||
|
agentName,
|
||||||
|
projectPath,
|
||||||
|
tokenStatus: TOKEN_STATUS_ACTIVE,
|
||||||
|
...cursor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createWrapper = () => {
|
||||||
|
wrapper = shallowMountExtended(CreateTokenModal, {
|
||||||
|
apolloProvider,
|
||||||
|
provide,
|
||||||
|
propsData,
|
||||||
|
stubs: {
|
||||||
|
GlModal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wrapper.vm.$refs.modal.hide = jest.fn();
|
||||||
|
|
||||||
|
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCreatedResponse = (mutationResponse) => {
|
||||||
|
apolloProvider = createMockApolloProvider({ mutationResponse });
|
||||||
|
writeQuery();
|
||||||
|
|
||||||
|
createWrapper();
|
||||||
|
|
||||||
|
findInput().vm.$emit('input', 'new-token');
|
||||||
|
findTextarea().vm.$emit('input', 'new-token-description');
|
||||||
|
findActionButton().vm.$emit('click');
|
||||||
|
|
||||||
|
return waitForPromises();
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
createWrapper();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
apolloProvider = null;
|
||||||
|
createResponse = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initial state', () => {
|
||||||
|
it('renders an input for the token name', () => {
|
||||||
|
expect(findInput().exists()).toBe(true);
|
||||||
|
expectDisabledAttribute(findInput(), false);
|
||||||
|
expect(findInput().attributes('max-length')).toBe(TOKEN_NAME_LIMIT.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a textarea for the token description', () => {
|
||||||
|
expect(findTextarea().exists()).toBe(true);
|
||||||
|
expectDisabledAttribute(findTextarea(), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a cancel button', () => {
|
||||||
|
expect(findCancelButton().isVisible()).toBe(true);
|
||||||
|
expectDisabledAttribute(findCancelButton(), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a disabled next button', () => {
|
||||||
|
expect(findActionButton().text()).toBe('Create token');
|
||||||
|
expectDisabledAttribute(findActionButton(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends tracking event for modal shown', () => {
|
||||||
|
findModal().vm.$emit('show');
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
|
||||||
|
label: EVENT_LABEL_MODAL,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when user inputs the token name', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
expectDisabledAttribute(findActionButton(), true);
|
||||||
|
findInput().vm.$emit('input', 'new-token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables the next button', () => {
|
||||||
|
expectDisabledAttribute(findActionButton(), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when user clicks the create-token button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const loadingResponse = new Promise(() => {});
|
||||||
|
await mockCreatedResponse(loadingResponse);
|
||||||
|
|
||||||
|
findInput().vm.$emit('input', 'new-token');
|
||||||
|
findActionButton().vm.$emit('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables the create-token button', () => {
|
||||||
|
expectDisabledAttribute(findActionButton(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides the cancel button', () => {
|
||||||
|
expect(findCancelButton().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('creating a new token', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await mockCreatedResponse(clusterAgentToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a token', () => {
|
||||||
|
expect(createResponse).toHaveBeenCalledWith({
|
||||||
|
input: { clusterAgentId, name: 'new-token', description: 'new-token-description' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows agent instructions', () => {
|
||||||
|
expect(findAgentInstructions().props()).toMatchObject({
|
||||||
|
agentName,
|
||||||
|
agentToken: 'token-secret',
|
||||||
|
modalId: CREATE_TOKEN_MODAL,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a close button', () => {
|
||||||
|
expect(findActionButton().isVisible()).toBe(true);
|
||||||
|
expect(findActionButton().text()).toBe('Close');
|
||||||
|
expectDisabledAttribute(findActionButton(), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('error creating a new token', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await mockCreatedResponse(createAgentTokenErrorResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays the error message', async () => {
|
||||||
|
expect(findAlert().text()).toBe(
|
||||||
|
createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import { GlEmptyState, GlTooltip, GlTruncate } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import TokenTable from '~/clusters/agents/components/token_table.vue';
|
import TokenTable from '~/clusters/agents/components/token_table.vue';
|
||||||
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
|
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
|
||||||
|
import CreateTokenModal from '~/clusters/agents/components/create_token_modal.vue';
|
||||||
import { useFakeDate } from 'helpers/fake_date';
|
import { useFakeDate } from 'helpers/fake_date';
|
||||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||||
import { MAX_LIST_COUNT } from '~/clusters/agents/constants';
|
import { MAX_LIST_COUNT } from '~/clusters/agents/constants';
|
||||||
|
@ -50,6 +51,7 @@ describe('ClusterAgentTokenTable', () => {
|
||||||
|
|
||||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||||
const findCreateTokenBtn = () => wrapper.findComponent(CreateTokenButton);
|
const findCreateTokenBtn = () => wrapper.findComponent(CreateTokenButton);
|
||||||
|
const findCreateModal = () => wrapper.findComponent(CreateTokenModal);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
return createComponent(defaultTokens);
|
return createComponent(defaultTokens);
|
||||||
|
@ -63,8 +65,8 @@ describe('ClusterAgentTokenTable', () => {
|
||||||
expect(findCreateTokenBtn().exists()).toBe(true);
|
expect(findCreateTokenBtn().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes the correct params to the create token component', () => {
|
it('passes the correct params to the create token modal component', () => {
|
||||||
expect(findCreateTokenBtn().props()).toMatchObject({
|
expect(findCreateModal().props()).toMatchObject({
|
||||||
clusterAgentId,
|
clusterAgentId,
|
||||||
cursor,
|
cursor,
|
||||||
});
|
});
|
||||||
|
|
|
@ -127,8 +127,8 @@ describe('Client side Markdown processing', () => {
|
||||||
pristineDoc: document,
|
pristineDoc: document,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sourceAttrs = (sourceMapKey, sourceMarkdown) => ({
|
const source = (sourceMarkdown) => ({
|
||||||
sourceMapKey,
|
sourceMapKey: expect.any(String),
|
||||||
sourceMarkdown,
|
sourceMarkdown,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -136,63 +136,48 @@ describe('Client side Markdown processing', () => {
|
||||||
{
|
{
|
||||||
markdown: '__bold text__',
|
markdown: '__bold text__',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(source('__bold text__'), bold(source('__bold text__'), 'bold text')),
|
||||||
sourceAttrs('0:13', '__bold text__'),
|
|
||||||
bold(sourceAttrs('0:13', '__bold text__'), 'bold text'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '**bold text**',
|
markdown: '**bold text**',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(source('**bold text**'), bold(source('**bold text**'), 'bold text')),
|
||||||
sourceAttrs('0:13', '**bold text**'),
|
|
||||||
bold(sourceAttrs('0:13', '**bold text**'), 'bold text'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '<strong>bold text</strong>',
|
markdown: '<strong>bold text</strong>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:26', '<strong>bold text</strong>'),
|
source('<strong>bold text</strong>'),
|
||||||
bold(sourceAttrs('0:26', '<strong>bold text</strong>'), 'bold text'),
|
bold(source('<strong>bold text</strong>'), 'bold text'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '<b>bold text</b>',
|
markdown: '<b>bold text</b>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(source('<b>bold text</b>'), bold(source('<b>bold text</b>'), 'bold text')),
|
||||||
sourceAttrs('0:16', '<b>bold text</b>'),
|
|
||||||
bold(sourceAttrs('0:16', '<b>bold text</b>'), 'bold text'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '_italic text_',
|
markdown: '_italic text_',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(source('_italic text_'), italic(source('_italic text_'), 'italic text')),
|
||||||
sourceAttrs('0:13', '_italic text_'),
|
|
||||||
italic(sourceAttrs('0:13', '_italic text_'), 'italic text'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '*italic text*',
|
markdown: '*italic text*',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(source('*italic text*'), italic(source('*italic text*'), 'italic text')),
|
||||||
sourceAttrs('0:13', '*italic text*'),
|
|
||||||
italic(sourceAttrs('0:13', '*italic text*'), 'italic text'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '<em>italic text</em>',
|
markdown: '<em>italic text</em>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:20', '<em>italic text</em>'),
|
source('<em>italic text</em>'),
|
||||||
italic(sourceAttrs('0:20', '<em>italic text</em>'), 'italic text'),
|
italic(source('<em>italic text</em>'), 'italic text'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -200,28 +185,25 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: '<i>italic text</i>',
|
markdown: '<i>italic text</i>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:18', '<i>italic text</i>'),
|
source('<i>italic text</i>'),
|
||||||
italic(sourceAttrs('0:18', '<i>italic text</i>'), 'italic text'),
|
italic(source('<i>italic text</i>'), 'italic text'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '`inline code`',
|
markdown: '`inline code`',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(source('`inline code`'), code(source('`inline code`'), 'inline code')),
|
||||||
sourceAttrs('0:13', '`inline code`'),
|
|
||||||
code(sourceAttrs('0:13', '`inline code`'), 'inline code'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '**`inline code bold`**',
|
markdown: '**`inline code bold`**',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:22', '**`inline code bold`**'),
|
source('**`inline code bold`**'),
|
||||||
bold(
|
bold(
|
||||||
sourceAttrs('0:22', '**`inline code bold`**'),
|
source('**`inline code bold`**'),
|
||||||
code(sourceAttrs('2:20', '`inline code bold`'), 'inline code bold'),
|
code(source('`inline code bold`'), 'inline code bold'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -230,10 +212,10 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: '_`inline code italics`_',
|
markdown: '_`inline code italics`_',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:23', '_`inline code italics`_'),
|
source('_`inline code italics`_'),
|
||||||
italic(
|
italic(
|
||||||
sourceAttrs('0:23', '_`inline code italics`_'),
|
source('_`inline code italics`_'),
|
||||||
code(sourceAttrs('1:22', '`inline code italics`'), 'inline code italics'),
|
code(source('`inline code italics`'), 'inline code italics'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -246,8 +228,8 @@ describe('Client side Markdown processing', () => {
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:28', '<i class="foo">\n *bar*\n</i>'),
|
source('<i class="foo">\n *bar*\n</i>'),
|
||||||
italic(sourceAttrs('0:28', '<i class="foo">\n *bar*\n</i>'), '\n *bar*\n'),
|
italic(source('<i class="foo">\n *bar*\n</i>'), '\n *bar*\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -259,8 +241,8 @@ describe('Client side Markdown processing', () => {
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:27', '<img src="bar" alt="foo" />'),
|
source('<img src="bar" alt="foo" />'),
|
||||||
image({ ...sourceAttrs('0:27', '<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
image({ ...source('<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -273,15 +255,12 @@ describe('Client side Markdown processing', () => {
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('0:13', '- List item 1'),
|
source('- List item 1'),
|
||||||
listItem(
|
listItem(source('- List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:13', '- List item 1'),
|
|
||||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('15:42', '<img src="bar" alt="foo" />'),
|
source('<img src="bar" alt="foo" />'),
|
||||||
image({ ...sourceAttrs('15:42', '<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
image({ ...source('<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -289,10 +268,10 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: '[GitLab](https://gitlab.com "Go to GitLab")',
|
markdown: '[GitLab](https://gitlab.com "Go to GitLab")',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:43', '[GitLab](https://gitlab.com "Go to GitLab")'),
|
source('[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||||
link(
|
link(
|
||||||
{
|
{
|
||||||
...sourceAttrs('0:43', '[GitLab](https://gitlab.com "Go to GitLab")'),
|
...source('[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||||
href: 'https://gitlab.com',
|
href: 'https://gitlab.com',
|
||||||
title: 'Go to GitLab',
|
title: 'Go to GitLab',
|
||||||
},
|
},
|
||||||
|
@ -305,12 +284,12 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: '**[GitLab](https://gitlab.com "Go to GitLab")**',
|
markdown: '**[GitLab](https://gitlab.com "Go to GitLab")**',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:47', '**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
source('**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
||||||
bold(
|
bold(
|
||||||
sourceAttrs('0:47', '**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
source('**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
||||||
link(
|
link(
|
||||||
{
|
{
|
||||||
...sourceAttrs('2:45', '[GitLab](https://gitlab.com "Go to GitLab")'),
|
...source('[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||||
href: 'https://gitlab.com',
|
href: 'https://gitlab.com',
|
||||||
title: 'Go to GitLab',
|
title: 'Go to GitLab',
|
||||||
},
|
},
|
||||||
|
@ -324,10 +303,10 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: 'www.commonmark.org',
|
markdown: 'www.commonmark.org',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:18', 'www.commonmark.org'),
|
source('www.commonmark.org'),
|
||||||
link(
|
link(
|
||||||
{
|
{
|
||||||
...sourceAttrs('0:18', 'www.commonmark.org'),
|
...source('www.commonmark.org'),
|
||||||
href: 'http://www.commonmark.org',
|
href: 'http://www.commonmark.org',
|
||||||
},
|
},
|
||||||
'www.commonmark.org',
|
'www.commonmark.org',
|
||||||
|
@ -339,11 +318,11 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: 'Visit www.commonmark.org/help for more information.',
|
markdown: 'Visit www.commonmark.org/help for more information.',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:51', 'Visit www.commonmark.org/help for more information.'),
|
source('Visit www.commonmark.org/help for more information.'),
|
||||||
'Visit ',
|
'Visit ',
|
||||||
link(
|
link(
|
||||||
{
|
{
|
||||||
...sourceAttrs('6:29', 'www.commonmark.org/help'),
|
...source('www.commonmark.org/help'),
|
||||||
href: 'http://www.commonmark.org/help',
|
href: 'http://www.commonmark.org/help',
|
||||||
},
|
},
|
||||||
'www.commonmark.org/help',
|
'www.commonmark.org/help',
|
||||||
|
@ -356,11 +335,11 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: 'hello@mail+xyz.example isn’t valid, but hello+xyz@mail.example is.',
|
markdown: 'hello@mail+xyz.example isn’t valid, but hello+xyz@mail.example is.',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:66', 'hello@mail+xyz.example isn’t valid, but hello+xyz@mail.example is.'),
|
source('hello@mail+xyz.example isn’t valid, but hello+xyz@mail.example is.'),
|
||||||
'hello@mail+xyz.example isn’t valid, but ',
|
'hello@mail+xyz.example isn’t valid, but ',
|
||||||
link(
|
link(
|
||||||
{
|
{
|
||||||
...sourceAttrs('40:62', 'hello+xyz@mail.example'),
|
...source('hello+xyz@mail.example'),
|
||||||
href: 'mailto:hello+xyz@mail.example',
|
href: 'mailto:hello+xyz@mail.example',
|
||||||
},
|
},
|
||||||
'hello+xyz@mail.example',
|
'hello+xyz@mail.example',
|
||||||
|
@ -373,11 +352,12 @@ describe('Client side Markdown processing', () => {
|
||||||
markdown: '[https://gitlab.com>',
|
markdown: '[https://gitlab.com>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:20', '[https://gitlab.com>'),
|
source('[https://gitlab.com>'),
|
||||||
'[',
|
'[',
|
||||||
link(
|
link(
|
||||||
{
|
{
|
||||||
...sourceAttrs(),
|
sourceMapKey: null,
|
||||||
|
sourceMarkdown: null,
|
||||||
href: 'https://gitlab.com',
|
href: 'https://gitlab.com',
|
||||||
},
|
},
|
||||||
'https://gitlab.com',
|
'https://gitlab.com',
|
||||||
|
@ -392,9 +372,9 @@ This is a paragraph with a\\
|
||||||
hard line break`,
|
hard line break`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:43', 'This is a paragraph with a\\\nhard line break'),
|
source('This is a paragraph with a\\\nhard line break'),
|
||||||
'This is a paragraph with a',
|
'This is a paragraph with a',
|
||||||
hardBreak(sourceAttrs('26:28', '\\\n')),
|
hardBreak(source('\\\n')),
|
||||||
'\nhard line break',
|
'\nhard line break',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -403,9 +383,9 @@ hard line break`,
|
||||||
markdown: '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")',
|
markdown: '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:57', '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
|
source('![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
|
||||||
image({
|
image({
|
||||||
...sourceAttrs('0:57', '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
|
...source('![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
|
||||||
alt: 'GitLab Logo',
|
alt: 'GitLab Logo',
|
||||||
src: 'https://gitlab.com/logo.png',
|
src: 'https://gitlab.com/logo.png',
|
||||||
title: 'GitLab Logo',
|
title: 'GitLab Logo',
|
||||||
|
@ -415,49 +395,43 @@ hard line break`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '---',
|
markdown: '---',
|
||||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:3', '---'))),
|
expectedDoc: doc(horizontalRule(source('---'))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '***',
|
markdown: '***',
|
||||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:3', '***'))),
|
expectedDoc: doc(horizontalRule(source('***'))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '___',
|
markdown: '___',
|
||||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:3', '___'))),
|
expectedDoc: doc(horizontalRule(source('___'))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '<hr>',
|
markdown: '<hr>',
|
||||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:4', '<hr>'))),
|
expectedDoc: doc(horizontalRule(source('<hr>'))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '# Heading 1',
|
markdown: '# Heading 1',
|
||||||
expectedDoc: doc(heading({ ...sourceAttrs('0:11', '# Heading 1'), level: 1 }, 'Heading 1')),
|
expectedDoc: doc(heading({ ...source('# Heading 1'), level: 1 }, 'Heading 1')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '## Heading 2',
|
markdown: '## Heading 2',
|
||||||
expectedDoc: doc(heading({ ...sourceAttrs('0:12', '## Heading 2'), level: 2 }, 'Heading 2')),
|
expectedDoc: doc(heading({ ...source('## Heading 2'), level: 2 }, 'Heading 2')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '### Heading 3',
|
markdown: '### Heading 3',
|
||||||
expectedDoc: doc(heading({ ...sourceAttrs('0:13', '### Heading 3'), level: 3 }, 'Heading 3')),
|
expectedDoc: doc(heading({ ...source('### Heading 3'), level: 3 }, 'Heading 3')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '#### Heading 4',
|
markdown: '#### Heading 4',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(heading({ ...source('#### Heading 4'), level: 4 }, 'Heading 4')),
|
||||||
heading({ ...sourceAttrs('0:14', '#### Heading 4'), level: 4 }, 'Heading 4'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '##### Heading 5',
|
markdown: '##### Heading 5',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(heading({ ...source('##### Heading 5'), level: 5 }, 'Heading 5')),
|
||||||
heading({ ...sourceAttrs('0:15', '##### Heading 5'), level: 5 }, 'Heading 5'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: '###### Heading 6',
|
markdown: '###### Heading 6',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(heading({ ...source('###### Heading 6'), level: 6 }, 'Heading 6')),
|
||||||
heading({ ...sourceAttrs('0:16', '###### Heading 6'), level: 6 }, 'Heading 6'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: `
|
markdown: `
|
||||||
|
@ -465,9 +439,7 @@ Heading
|
||||||
one
|
one
|
||||||
======
|
======
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(heading({ ...source('Heading\none\n======'), level: 1 }, 'Heading\none')),
|
||||||
heading({ ...sourceAttrs('0:18', 'Heading\none\n======'), level: 1 }, 'Heading\none'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: `
|
markdown: `
|
||||||
|
@ -475,9 +447,7 @@ Heading
|
||||||
two
|
two
|
||||||
-------
|
-------
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(heading({ ...source('Heading\ntwo\n-------'), level: 2 }, 'Heading\ntwo')),
|
||||||
heading({ ...sourceAttrs('0:19', 'Heading\ntwo\n-------'), level: 2 }, 'Heading\ntwo'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
markdown: `
|
markdown: `
|
||||||
|
@ -486,15 +456,9 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('0:27', '- List item 1\n- List item 2'),
|
source('- List item 1\n- List item 2'),
|
||||||
listItem(
|
listItem(source('- List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:13', '- List item 1'),
|
listItem(source('- List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('14:27', '- List item 2'),
|
|
||||||
paragraph(sourceAttrs('16:27', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -505,15 +469,9 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('0:27', '* List item 1\n* List item 2'),
|
source('* List item 1\n* List item 2'),
|
||||||
listItem(
|
listItem(source('* List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:13', '* List item 1'),
|
listItem(source('* List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('14:27', '* List item 2'),
|
|
||||||
paragraph(sourceAttrs('16:27', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -524,15 +482,9 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('0:27', '+ List item 1\n+ List item 2'),
|
source('+ List item 1\n+ List item 2'),
|
||||||
listItem(
|
listItem(source('+ List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:13', '+ List item 1'),
|
listItem(source('+ List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('14:27', '+ List item 2'),
|
|
||||||
paragraph(sourceAttrs('16:27', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -543,15 +495,9 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
orderedList(
|
orderedList(
|
||||||
sourceAttrs('0:29', '1. List item 1\n1. List item 2'),
|
source('1. List item 1\n1. List item 2'),
|
||||||
listItem(
|
listItem(source('1. List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:14', '1. List item 1'),
|
listItem(source('1. List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('3:14', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('15:29', '1. List item 2'),
|
|
||||||
paragraph(sourceAttrs('18:29', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -562,15 +508,9 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
orderedList(
|
orderedList(
|
||||||
sourceAttrs('0:29', '1. List item 1\n2. List item 2'),
|
source('1. List item 1\n2. List item 2'),
|
||||||
listItem(
|
listItem(source('1. List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:14', '1. List item 1'),
|
listItem(source('2. List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('3:14', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('15:29', '2. List item 2'),
|
|
||||||
paragraph(sourceAttrs('18:29', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -581,15 +521,9 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
orderedList(
|
orderedList(
|
||||||
sourceAttrs('0:29', '1) List item 1\n2) List item 2'),
|
source('1) List item 1\n2) List item 2'),
|
||||||
listItem(
|
listItem(source('1) List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('0:14', '1) List item 1'),
|
listItem(source('2) List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('3:14', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('15:29', '2) List item 2'),
|
|
||||||
paragraph(sourceAttrs('18:29', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -600,15 +534,15 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('0:33', '- List item 1\n - Sub list item 1'),
|
source('- List item 1\n - Sub list item 1'),
|
||||||
listItem(
|
listItem(
|
||||||
sourceAttrs('0:33', '- List item 1\n - Sub list item 1'),
|
source('- List item 1\n - Sub list item 1'),
|
||||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
paragraph(source('List item 1'), 'List item 1'),
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('16:33', '- Sub list item 1'),
|
source('- Sub list item 1'),
|
||||||
listItem(
|
listItem(
|
||||||
sourceAttrs('16:33', '- Sub list item 1'),
|
source('- Sub list item 1'),
|
||||||
paragraph(sourceAttrs('18:33', 'Sub list item 1'), 'Sub list item 1'),
|
paragraph(source('Sub list item 1'), 'Sub list item 1'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -624,19 +558,13 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs(
|
source('- List item 1 paragraph 1\n\n List item 1 paragraph 2\n- List item 2'),
|
||||||
'0:66',
|
|
||||||
'- List item 1 paragraph 1\n\n List item 1 paragraph 2\n- List item 2',
|
|
||||||
),
|
|
||||||
listItem(
|
listItem(
|
||||||
sourceAttrs('0:52', '- List item 1 paragraph 1\n\n List item 1 paragraph 2'),
|
source('- List item 1 paragraph 1\n\n List item 1 paragraph 2'),
|
||||||
paragraph(sourceAttrs('2:25', 'List item 1 paragraph 1'), 'List item 1 paragraph 1'),
|
paragraph(source('List item 1 paragraph 1'), 'List item 1 paragraph 1'),
|
||||||
paragraph(sourceAttrs('29:52', 'List item 1 paragraph 2'), 'List item 1 paragraph 2'),
|
paragraph(source('List item 1 paragraph 2'), 'List item 1 paragraph 2'),
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('53:66', '- List item 2'),
|
|
||||||
paragraph(sourceAttrs('55:66', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
),
|
||||||
|
listItem(source('- List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -646,13 +574,13 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('0:41', '- List item with an image ![bar](foo.png)'),
|
source('- List item with an image ![bar](foo.png)'),
|
||||||
listItem(
|
listItem(
|
||||||
sourceAttrs('0:41', '- List item with an image ![bar](foo.png)'),
|
source('- List item with an image ![bar](foo.png)'),
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('2:41', 'List item with an image ![bar](foo.png)'),
|
source('List item with an image ![bar](foo.png)'),
|
||||||
'List item with an image',
|
'List item with an image',
|
||||||
image({ ...sourceAttrs('26:41', '![bar](foo.png)'), alt: 'bar', src: 'foo.png' }),
|
image({ ...source('![bar](foo.png)'), alt: 'bar', src: 'foo.png' }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -664,8 +592,8 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
blockquote(
|
blockquote(
|
||||||
sourceAttrs('0:22', '> This is a blockquote'),
|
source('> This is a blockquote'),
|
||||||
paragraph(sourceAttrs('2:22', 'This is a blockquote'), 'This is a blockquote'),
|
paragraph(source('This is a blockquote'), 'This is a blockquote'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -676,17 +604,11 @@ two
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
blockquote(
|
blockquote(
|
||||||
sourceAttrs('0:31', '> - List item 1\n> - List item 2'),
|
source('> - List item 1\n> - List item 2'),
|
||||||
bulletList(
|
bulletList(
|
||||||
sourceAttrs('2:31', '- List item 1\n> - List item 2'),
|
source('- List item 1\n> - List item 2'),
|
||||||
listItem(
|
listItem(source('- List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||||
sourceAttrs('2:15', '- List item 1'),
|
listItem(source('- List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||||
paragraph(sourceAttrs('4:15', 'List item 1'), 'List item 1'),
|
|
||||||
),
|
|
||||||
listItem(
|
|
||||||
sourceAttrs('18:31', '- List item 2'),
|
|
||||||
paragraph(sourceAttrs('20:31', 'List item 2'), 'List item 2'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -699,10 +621,10 @@ code block
|
||||||
|
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(sourceAttrs('0:10', 'code block'), 'code block'),
|
paragraph(source('code block'), 'code block'),
|
||||||
codeBlock(
|
codeBlock(
|
||||||
{
|
{
|
||||||
...sourceAttrs('12:42', " const fn = () => 'GitLab';"),
|
...source(" const fn = () => 'GitLab';"),
|
||||||
class: 'code highlight',
|
class: 'code highlight',
|
||||||
language: null,
|
language: null,
|
||||||
},
|
},
|
||||||
|
@ -719,7 +641,7 @@ const fn = () => 'GitLab';
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
codeBlock(
|
codeBlock(
|
||||||
{
|
{
|
||||||
...sourceAttrs('0:44', "```javascript\nconst fn = () => 'GitLab';\n```"),
|
...source("```javascript\nconst fn = () => 'GitLab';\n```"),
|
||||||
class: 'code highlight',
|
class: 'code highlight',
|
||||||
language: 'javascript',
|
language: 'javascript',
|
||||||
},
|
},
|
||||||
|
@ -736,7 +658,7 @@ const fn = () => 'GitLab';
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
codeBlock(
|
codeBlock(
|
||||||
{
|
{
|
||||||
...sourceAttrs('0:44', "~~~javascript\nconst fn = () => 'GitLab';\n~~~"),
|
...source("~~~javascript\nconst fn = () => 'GitLab';\n~~~"),
|
||||||
class: 'code highlight',
|
class: 'code highlight',
|
||||||
language: 'javascript',
|
language: 'javascript',
|
||||||
},
|
},
|
||||||
|
@ -752,7 +674,7 @@ const fn = () => 'GitLab';
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
codeBlock(
|
codeBlock(
|
||||||
{
|
{
|
||||||
...sourceAttrs('0:7', '```\n```'),
|
...source('```\n```'),
|
||||||
class: 'code highlight',
|
class: 'code highlight',
|
||||||
language: null,
|
language: null,
|
||||||
},
|
},
|
||||||
|
@ -770,7 +692,7 @@ const fn = () => 'GitLab';
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
codeBlock(
|
codeBlock(
|
||||||
{
|
{
|
||||||
...sourceAttrs('0:45', "```javascript\nconst fn = () => 'GitLab';\n\n```"),
|
...source("```javascript\nconst fn = () => 'GitLab';\n\n```"),
|
||||||
class: 'code highlight',
|
class: 'code highlight',
|
||||||
language: 'javascript',
|
language: 'javascript',
|
||||||
},
|
},
|
||||||
|
@ -782,8 +704,8 @@ const fn = () => 'GitLab';
|
||||||
markdown: '~~Strikedthrough text~~',
|
markdown: '~~Strikedthrough text~~',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:23', '~~Strikedthrough text~~'),
|
source('~~Strikedthrough text~~'),
|
||||||
strike(sourceAttrs('0:23', '~~Strikedthrough text~~'), 'Strikedthrough text'),
|
strike(source('~~Strikedthrough text~~'), 'Strikedthrough text'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -791,8 +713,8 @@ const fn = () => 'GitLab';
|
||||||
markdown: '<del>Strikedthrough text</del>',
|
markdown: '<del>Strikedthrough text</del>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:30', '<del>Strikedthrough text</del>'),
|
source('<del>Strikedthrough text</del>'),
|
||||||
strike(sourceAttrs('0:30', '<del>Strikedthrough text</del>'), 'Strikedthrough text'),
|
strike(source('<del>Strikedthrough text</del>'), 'Strikedthrough text'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -800,11 +722,8 @@ const fn = () => 'GitLab';
|
||||||
markdown: '<strike>Strikedthrough text</strike>',
|
markdown: '<strike>Strikedthrough text</strike>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:36', '<strike>Strikedthrough text</strike>'),
|
source('<strike>Strikedthrough text</strike>'),
|
||||||
strike(
|
strike(source('<strike>Strikedthrough text</strike>'), 'Strikedthrough text'),
|
||||||
sourceAttrs('0:36', '<strike>Strikedthrough text</strike>'),
|
|
||||||
'Strikedthrough text',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -812,8 +731,8 @@ const fn = () => 'GitLab';
|
||||||
markdown: '<s>Strikedthrough text</s>',
|
markdown: '<s>Strikedthrough text</s>',
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:26', '<s>Strikedthrough text</s>'),
|
source('<s>Strikedthrough text</s>'),
|
||||||
strike(sourceAttrs('0:26', '<s>Strikedthrough text</s>'), 'Strikedthrough text'),
|
strike(source('<s>Strikedthrough text</s>'), 'Strikedthrough text'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -826,21 +745,21 @@ const fn = () => 'GitLab';
|
||||||
taskList(
|
taskList(
|
||||||
{
|
{
|
||||||
numeric: false,
|
numeric: false,
|
||||||
...sourceAttrs('0:45', '- [ ] task list item 1\n- [ ] task list item 2'),
|
...source('- [ ] task list item 1\n- [ ] task list item 2'),
|
||||||
},
|
},
|
||||||
taskItem(
|
taskItem(
|
||||||
{
|
{
|
||||||
checked: false,
|
checked: false,
|
||||||
...sourceAttrs('0:22', '- [ ] task list item 1'),
|
...source('- [ ] task list item 1'),
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('6:22', 'task list item 1'), 'task list item 1'),
|
paragraph(source('task list item 1'), 'task list item 1'),
|
||||||
),
|
),
|
||||||
taskItem(
|
taskItem(
|
||||||
{
|
{
|
||||||
checked: false,
|
checked: false,
|
||||||
...sourceAttrs('23:45', '- [ ] task list item 2'),
|
...source('- [ ] task list item 2'),
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('29:45', 'task list item 2'), 'task list item 2'),
|
paragraph(source('task list item 2'), 'task list item 2'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -854,21 +773,21 @@ const fn = () => 'GitLab';
|
||||||
taskList(
|
taskList(
|
||||||
{
|
{
|
||||||
numeric: false,
|
numeric: false,
|
||||||
...sourceAttrs('0:45', '- [x] task list item 1\n- [x] task list item 2'),
|
...source('- [x] task list item 1\n- [x] task list item 2'),
|
||||||
},
|
},
|
||||||
taskItem(
|
taskItem(
|
||||||
{
|
{
|
||||||
checked: true,
|
checked: true,
|
||||||
...sourceAttrs('0:22', '- [x] task list item 1'),
|
...source('- [x] task list item 1'),
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('6:22', 'task list item 1'), 'task list item 1'),
|
paragraph(source('task list item 1'), 'task list item 1'),
|
||||||
),
|
),
|
||||||
taskItem(
|
taskItem(
|
||||||
{
|
{
|
||||||
checked: true,
|
checked: true,
|
||||||
...sourceAttrs('23:45', '- [x] task list item 2'),
|
...source('- [x] task list item 2'),
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('29:45', 'task list item 2'), 'task list item 2'),
|
paragraph(source('task list item 2'), 'task list item 2'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -882,21 +801,21 @@ const fn = () => 'GitLab';
|
||||||
taskList(
|
taskList(
|
||||||
{
|
{
|
||||||
numeric: true,
|
numeric: true,
|
||||||
...sourceAttrs('0:47', '1. [ ] task list item 1\n2. [ ] task list item 2'),
|
...source('1. [ ] task list item 1\n2. [ ] task list item 2'),
|
||||||
},
|
},
|
||||||
taskItem(
|
taskItem(
|
||||||
{
|
{
|
||||||
checked: false,
|
checked: false,
|
||||||
...sourceAttrs('0:23', '1. [ ] task list item 1'),
|
...source('1. [ ] task list item 1'),
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('7:23', 'task list item 1'), 'task list item 1'),
|
paragraph(source('task list item 1'), 'task list item 1'),
|
||||||
),
|
),
|
||||||
taskItem(
|
taskItem(
|
||||||
{
|
{
|
||||||
checked: false,
|
checked: false,
|
||||||
...sourceAttrs('24:47', '2. [ ] task list item 2'),
|
...source('2. [ ] task list item 2'),
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('31:47', 'task list item 2'), 'task list item 2'),
|
paragraph(source('task list item 2'), 'task list item 2'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -909,16 +828,16 @@ const fn = () => 'GitLab';
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
table(
|
table(
|
||||||
sourceAttrs('0:29', '| a | b |\n|---|---|\n| c | d |'),
|
source('| a | b |\n|---|---|\n| c | d |'),
|
||||||
tableRow(
|
tableRow(
|
||||||
sourceAttrs('0:9', '| a | b |'),
|
source('| a | b |'),
|
||||||
tableHeader(sourceAttrs('0:5', '| a |'), paragraph(sourceAttrs('2:3', 'a'), 'a')),
|
tableHeader(source('| a |'), paragraph(source('a'), 'a')),
|
||||||
tableHeader(sourceAttrs('5:9', ' b |'), paragraph(sourceAttrs('6:7', 'b'), 'b')),
|
tableHeader(source(' b |'), paragraph(source('b'), 'b')),
|
||||||
),
|
),
|
||||||
tableRow(
|
tableRow(
|
||||||
sourceAttrs('20:29', '| c | d |'),
|
source('| c | d |'),
|
||||||
tableCell(sourceAttrs('20:25', '| c |'), paragraph(sourceAttrs('22:23', 'c'), 'c')),
|
tableCell(source('| c |'), paragraph(source('c'), 'c')),
|
||||||
tableCell(sourceAttrs('25:29', ' d |'), paragraph(sourceAttrs('26:27', 'd'), 'd')),
|
tableCell(source(' d |'), paragraph(source('d'), 'd')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -936,30 +855,29 @@ const fn = () => 'GitLab';
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
table(
|
table(
|
||||||
sourceAttrs(
|
source(
|
||||||
'0:132',
|
|
||||||
'<table>\n <tr>\n <th colspan="2" rowspan="5">Header</th>\n </tr>\n <tr>\n <td colspan="2" rowspan="5">Body</td>\n </tr>\n</table>',
|
'<table>\n <tr>\n <th colspan="2" rowspan="5">Header</th>\n </tr>\n <tr>\n <td colspan="2" rowspan="5">Body</td>\n </tr>\n</table>',
|
||||||
),
|
),
|
||||||
tableRow(
|
tableRow(
|
||||||
sourceAttrs('10:66', '<tr>\n <th colspan="2" rowspan="5">Header</th>\n </tr>'),
|
source('<tr>\n <th colspan="2" rowspan="5">Header</th>\n </tr>'),
|
||||||
tableHeader(
|
tableHeader(
|
||||||
{
|
{
|
||||||
...sourceAttrs('19:58', '<th colspan="2" rowspan="5">Header</th>'),
|
...source('<th colspan="2" rowspan="5">Header</th>'),
|
||||||
colspan: 2,
|
colspan: 2,
|
||||||
rowspan: 5,
|
rowspan: 5,
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('47:53', 'Header'), 'Header'),
|
paragraph(source('Header'), 'Header'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tableRow(
|
tableRow(
|
||||||
sourceAttrs('69:123', '<tr>\n <td colspan="2" rowspan="5">Body</td>\n </tr>'),
|
source('<tr>\n <td colspan="2" rowspan="5">Body</td>\n </tr>'),
|
||||||
tableCell(
|
tableCell(
|
||||||
{
|
{
|
||||||
...sourceAttrs('78:115', '<td colspan="2" rowspan="5">Body</td>'),
|
...source('<td colspan="2" rowspan="5">Body</td>'),
|
||||||
colspan: 2,
|
colspan: 2,
|
||||||
rowspan: 5,
|
rowspan: 5,
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('106:110', 'Body'), 'Body'),
|
paragraph(source('Body'), 'Body'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -977,24 +895,24 @@ Paragraph
|
||||||
`,
|
`,
|
||||||
expectedDoc: doc(
|
expectedDoc: doc(
|
||||||
paragraph(
|
paragraph(
|
||||||
sourceAttrs('0:30', 'This is a footnote [^footnote]'),
|
source('This is a footnote [^footnote]'),
|
||||||
'This is a footnote ',
|
'This is a footnote ',
|
||||||
footnoteReference({
|
footnoteReference({
|
||||||
...sourceAttrs('19:30', '[^footnote]'),
|
...source('[^footnote]'),
|
||||||
identifier: 'footnote',
|
identifier: 'footnote',
|
||||||
label: 'footnote',
|
label: 'footnote',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
paragraph(sourceAttrs('32:41', 'Paragraph'), 'Paragraph'),
|
paragraph(source('Paragraph'), 'Paragraph'),
|
||||||
footnoteDefinition(
|
footnoteDefinition(
|
||||||
{
|
{
|
||||||
...sourceAttrs('43:75', '[^footnote]: Footnote definition'),
|
...source('[^footnote]: Footnote definition'),
|
||||||
identifier: 'footnote',
|
identifier: 'footnote',
|
||||||
label: 'footnote',
|
label: 'footnote',
|
||||||
},
|
},
|
||||||
paragraph(sourceAttrs('56:75', 'Footnote definition'), 'Footnote definition'),
|
paragraph(source('Footnote definition'), 'Footnote definition'),
|
||||||
),
|
),
|
||||||
paragraph(sourceAttrs('77:86', 'Paragraph'), 'Paragraph'),
|
paragraph(source('Paragraph'), 'Paragraph'),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
||||||
it 'returns the object kind for a deployment' do
|
it 'returns the object kind for a deployment' do
|
||||||
deployment = build(:deployment, deployable: nil, environment: create(:environment))
|
deployment = build(:deployment, deployable: nil, environment: create(:environment))
|
||||||
|
|
||||||
data = described_class.build(deployment, Time.current)
|
data = described_class.build(deployment, 'success', Time.current)
|
||||||
|
|
||||||
expect(data[:object_kind]).to eq('deployment')
|
expect(data[:object_kind]).to eq('deployment')
|
||||||
end
|
end
|
||||||
|
@ -23,7 +23,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
||||||
expected_commit_url = Gitlab::UrlBuilder.build(commit)
|
expected_commit_url = Gitlab::UrlBuilder.build(commit)
|
||||||
status_changed_at = Time.current
|
status_changed_at = Time.current
|
||||||
|
|
||||||
data = described_class.build(deployment, status_changed_at)
|
data = described_class.build(deployment, 'failed', status_changed_at)
|
||||||
|
|
||||||
expect(data[:status]).to eq('failed')
|
expect(data[:status]).to eq('failed')
|
||||||
expect(data[:status_changed_at]).to eq(status_changed_at)
|
expect(data[:status_changed_at]).to eq(status_changed_at)
|
||||||
|
@ -42,7 +42,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
||||||
|
|
||||||
it 'does not include the deployable URL when there is no deployable' do
|
it 'does not include the deployable URL when there is no deployable' do
|
||||||
deployment = create(:deployment, status: :failed, deployable: nil)
|
deployment = create(:deployment, status: :failed, deployable: nil)
|
||||||
data = described_class.build(deployment, Time.current)
|
data = described_class.build(deployment, 'failed', Time.current)
|
||||||
|
|
||||||
expect(data[:deployable_url]).to be_nil
|
expect(data[:deployable_url]).to be_nil
|
||||||
end
|
end
|
||||||
|
@ -51,7 +51,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
||||||
let_it_be(:project) { create(:project, :repository) }
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
let_it_be(:deployment) { create(:deployment, project: project) }
|
let_it_be(:deployment) { create(:deployment, project: project) }
|
||||||
|
|
||||||
subject(:data) { described_class.build(deployment, Time.current) }
|
subject(:data) { described_class.build(deployment, 'created', Time.current) }
|
||||||
|
|
||||||
before(:all) do
|
before(:all) do
|
||||||
project.repository.remove
|
project.repository.remove
|
||||||
|
@ -69,7 +69,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
||||||
context 'when deployed_by is nil' do
|
context 'when deployed_by is nil' do
|
||||||
let_it_be(:deployment) { create(:deployment, user: nil, deployable: nil) }
|
let_it_be(:deployment) { create(:deployment, user: nil, deployable: nil) }
|
||||||
|
|
||||||
subject(:data) { described_class.build(deployment, Time.current) }
|
subject(:data) { described_class.build(deployment, 'created', Time.current) }
|
||||||
|
|
||||||
before(:all) do
|
before(:all) do
|
||||||
deployment.user = nil
|
deployment.user = nil
|
||||||
|
|
|
@ -1364,7 +1364,7 @@ RSpec.describe Ci::Build do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
||||||
allow(deployment).to receive(:execute_hooks)
|
allow(Deployments::HooksWorker).to receive(:perform_async)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has deployments record with created status' do
|
it 'has deployments record with created status' do
|
||||||
|
@ -1420,7 +1420,7 @@ RSpec.describe Ci::Build do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
allow(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
||||||
allow(deployment).to receive(:execute_hooks)
|
allow(Deployments::HooksWorker).to receive(:perform_async)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'avoid deadlock'
|
it_behaves_like 'avoid deadlock'
|
||||||
|
@ -1506,28 +1506,14 @@ RSpec.describe Ci::Build do
|
||||||
|
|
||||||
it 'transitions to running and calls webhook' do
|
it 'transitions to running and calls webhook' do
|
||||||
freeze_time do
|
freeze_time do
|
||||||
expect(deployment).to receive(:execute_hooks).with(Time.current)
|
expect(Deployments::HooksWorker)
|
||||||
|
.to receive(:perform_async).with(deployment_id: deployment.id, status: 'running', status_changed_at: Time.current)
|
||||||
|
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(deployment).to be_running
|
expect(deployment).to be_running
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::HooksWorker asynchronously' do
|
|
||||||
freeze_time do
|
|
||||||
expect(Deployments::HooksWorker)
|
|
||||||
.to receive(:perform_async).with(deployment_id: deployment.id, status_changed_at: Time.current)
|
|
||||||
|
|
||||||
subject
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -139,29 +139,16 @@ RSpec.describe Deployment do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'executes deployment hooks' do
|
it 'executes Deployments::HooksWorker asynchronously' do
|
||||||
freeze_time do
|
freeze_time do
|
||||||
expect(deployment).to receive(:execute_hooks).with(Time.current)
|
expect(Deployments::HooksWorker)
|
||||||
|
.to receive(:perform_async).with(deployment_id: deployment.id, status: 'running',
|
||||||
|
status_changed_at: Time.current)
|
||||||
|
|
||||||
deployment.run!
|
deployment.run!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::HooksWorker asynchronously' do
|
|
||||||
freeze_time do
|
|
||||||
expect(Deployments::HooksWorker)
|
|
||||||
.to receive(:perform_async).with(deployment_id: deployment.id, status_changed_at: Time.current)
|
|
||||||
|
|
||||||
deployment.run!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::DropOlderDeploymentsWorker asynchronously' do
|
it 'executes Deployments::DropOlderDeploymentsWorker asynchronously' do
|
||||||
expect(Deployments::DropOlderDeploymentsWorker)
|
expect(Deployments::DropOlderDeploymentsWorker)
|
||||||
.to receive(:perform_async).once.with(deployment.id)
|
.to receive(:perform_async).once.with(deployment.id)
|
||||||
|
@ -189,28 +176,15 @@ RSpec.describe Deployment do
|
||||||
deployment.succeed!
|
deployment.succeed!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'executes deployment hooks' do
|
it 'executes Deployments::HooksWorker asynchronously' do
|
||||||
freeze_time do
|
freeze_time do
|
||||||
expect(deployment).to receive(:execute_hooks).with(Time.current)
|
expect(Deployments::HooksWorker)
|
||||||
|
.to receive(:perform_async).with(deployment_id: deployment.id, status: 'success',
|
||||||
|
status_changed_at: Time.current)
|
||||||
|
|
||||||
deployment.succeed!
|
deployment.succeed!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::HooksWorker asynchronously' do
|
|
||||||
freeze_time do
|
|
||||||
expect(Deployments::HooksWorker)
|
|
||||||
.to receive(:perform_async).with(deployment_id: deployment.id, status_changed_at: Time.current)
|
|
||||||
|
|
||||||
deployment.succeed!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when deployment failed' do
|
context 'when deployment failed' do
|
||||||
|
@ -232,28 +206,15 @@ RSpec.describe Deployment do
|
||||||
deployment.drop!
|
deployment.drop!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'executes deployment hooks' do
|
it 'executes Deployments::HooksWorker asynchronously' do
|
||||||
freeze_time do
|
freeze_time do
|
||||||
expect(deployment).to receive(:execute_hooks).with(Time.current)
|
expect(Deployments::HooksWorker)
|
||||||
|
.to receive(:perform_async).with(deployment_id: deployment.id, status: 'failed',
|
||||||
|
status_changed_at: Time.current)
|
||||||
|
|
||||||
deployment.drop!
|
deployment.drop!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::HooksWorker asynchronously' do
|
|
||||||
freeze_time do
|
|
||||||
expect(Deployments::HooksWorker)
|
|
||||||
.to receive(:perform_async).with(deployment_id: deployment.id, status_changed_at: Time.current)
|
|
||||||
|
|
||||||
deployment.drop!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when deployment was canceled' do
|
context 'when deployment was canceled' do
|
||||||
|
@ -275,28 +236,15 @@ RSpec.describe Deployment do
|
||||||
deployment.cancel!
|
deployment.cancel!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'executes deployment hooks' do
|
it 'executes Deployments::HooksWorker asynchronously' do
|
||||||
freeze_time do
|
freeze_time do
|
||||||
expect(deployment).to receive(:execute_hooks).with(Time.current)
|
expect(Deployments::HooksWorker)
|
||||||
|
.to receive(:perform_async).with(deployment_id: deployment.id, status: 'canceled',
|
||||||
|
status_changed_at: Time.current)
|
||||||
|
|
||||||
deployment.cancel!
|
deployment.cancel!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::HooksWorker asynchronously' do
|
|
||||||
freeze_time do
|
|
||||||
expect(Deployments::HooksWorker)
|
|
||||||
.to receive(:perform_async).with(deployment_id: deployment.id, status_changed_at: Time.current)
|
|
||||||
|
|
||||||
deployment.cancel!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when deployment was skipped' do
|
context 'when deployment was skipped' do
|
||||||
|
@ -324,12 +272,6 @@ RSpec.describe Deployment do
|
||||||
deployment.skip!
|
deployment.skip!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not execute deployment hooks' do
|
|
||||||
expect(deployment).not_to receive(:execute_hooks)
|
|
||||||
|
|
||||||
deployment.skip!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when deployment is blocked' do
|
context 'when deployment is blocked' do
|
||||||
|
@ -353,12 +295,6 @@ RSpec.describe Deployment do
|
||||||
|
|
||||||
deployment.block!
|
deployment.block!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not execute deployment hooks' do
|
|
||||||
expect(deployment).not_to receive(:execute_hooks)
|
|
||||||
|
|
||||||
deployment.block!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'synching status to Jira' do
|
describe 'synching status to Jira' do
|
||||||
|
@ -1052,30 +988,11 @@ RSpec.describe Deployment do
|
||||||
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
||||||
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
||||||
expect(Deployments::ArchiveInProjectWorker).to receive(:perform_async)
|
expect(Deployments::ArchiveInProjectWorker).to receive(:perform_async)
|
||||||
|
expect(Deployments::HooksWorker).to receive(:perform_async)
|
||||||
|
|
||||||
expect(deploy.update_status('success')).to eq(true)
|
expect(deploy.update_status('success')).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'schedules `Deployments::HooksWorker` when finishing a deploy' do
|
|
||||||
expect(Deployments::HooksWorker).to receive(:perform_async)
|
|
||||||
|
|
||||||
deploy.update_status('success')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes deployment hooks when finishing a deploy' do
|
|
||||||
freeze_time do
|
|
||||||
expect(deploy).to receive(:execute_hooks).with(Time.current)
|
|
||||||
|
|
||||||
deploy.update_status('success')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates finished_at when transitioning to a finished status' do
|
it 'updates finished_at when transitioning to a finished status' do
|
||||||
freeze_time do
|
freeze_time do
|
||||||
deploy.update_status('success')
|
deploy.update_status('success')
|
||||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe Integrations::ChatMessage::DeploymentMessage do
|
||||||
let_it_be(:deployment) { create(:deployment, status: :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha) }
|
let_it_be(:deployment) { create(:deployment, status: :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha) }
|
||||||
|
|
||||||
let(:args) do
|
let(:args) do
|
||||||
Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
|
Gitlab::DataBuilder::Deployment.build(deployment, 'success', Time.current)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like Integrations::ChatMessage
|
it_behaves_like Integrations::ChatMessage
|
||||||
|
|
|
@ -59,7 +59,7 @@ RSpec.describe Integrations::Slack do
|
||||||
context 'deployment notification' do
|
context 'deployment notification' do
|
||||||
let_it_be(:deployment) { create(:deployment, user: user) }
|
let_it_be(:deployment) { create(:deployment, user: user) }
|
||||||
|
|
||||||
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) }
|
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) }
|
||||||
|
|
||||||
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification'
|
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification'
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ require 'spec_helper'
|
||||||
# See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
|
# See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
|
||||||
# for documentation on this spec.
|
# for documentation on this spec.
|
||||||
RSpec.describe API::Markdown, 'Snapshot' do
|
RSpec.describe API::Markdown, 'Snapshot' do
|
||||||
|
# noinspection RubyMismatchedArgumentType (ignore RBS type warning: __dir__ can be nil, but 2nd argument can't be nil)
|
||||||
glfm_specification_dir = File.expand_path('../../../glfm_specification', __dir__)
|
glfm_specification_dir = File.expand_path('../../../glfm_specification', __dir__)
|
||||||
glfm_example_snapshots_dir = File.expand_path('../../fixtures/glfm/example_snapshots', __dir__)
|
glfm_example_snapshots_dir = File.expand_path('../../fixtures/glfm/example_snapshots', __dir__)
|
||||||
include_context 'with API::Markdown Snapshot shared context', glfm_specification_dir, glfm_example_snapshots_dir
|
include_context 'with API::Markdown Snapshot shared context', glfm_specification_dir, glfm_example_snapshots_dir
|
||||||
|
|
|
@ -14,7 +14,7 @@ require_relative '../../../../scripts/lib/glfm/update_example_snapshots'
|
||||||
# This is because the invocation of the full script is slow, because it executes
|
# This is because the invocation of the full script is slow, because it executes
|
||||||
# two subshells for processing, one which runs a full Rails environment, and one
|
# two subshells for processing, one which runs a full Rails environment, and one
|
||||||
# which runs a jest test environment. This results in each full run of the script
|
# which runs a jest test environment. This results in each full run of the script
|
||||||
# taking between 30-60 seconds. The majority of this is spent loading the Rails environmnent.
|
# taking between 30-60 seconds. The majority of this is spent loading the Rails environment.
|
||||||
#
|
#
|
||||||
# However, only the `writing html.yml and prosemirror_json.yml` context is used
|
# However, only the `writing html.yml and prosemirror_json.yml` context is used
|
||||||
# to test these slow sub-processes, and it only contains a single example.
|
# to test these slow sub-processes, and it only contains a single example.
|
||||||
|
|
|
@ -21,34 +21,11 @@ RSpec.describe Deployments::CreateService do
|
||||||
|
|
||||||
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
||||||
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
||||||
expect_next_instance_of(Deployment) do |deployment|
|
expect(Deployments::HooksWorker).to receive(:perform_async)
|
||||||
expect(deployment).to receive(:execute_hooks)
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(service.execute).to be_persisted
|
expect(service.execute).to be_persisted
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `deployment_hooks_skip_worker` flag is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(deployment_hooks_skip_worker: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'executes Deployments::HooksWorker asynchronously' do
|
|
||||||
service = described_class.new(
|
|
||||||
environment,
|
|
||||||
user,
|
|
||||||
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
|
|
||||||
ref: 'master',
|
|
||||||
tag: false,
|
|
||||||
status: 'success'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(Deployments::HooksWorker).to receive(:perform_async)
|
|
||||||
|
|
||||||
service.execute
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not change the status if no status is given' do
|
it 'does not change the status if no status is given' do
|
||||||
service = described_class.new(
|
service = described_class.new(
|
||||||
environment,
|
environment,
|
||||||
|
@ -60,9 +37,7 @@ RSpec.describe Deployments::CreateService do
|
||||||
|
|
||||||
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
|
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
|
||||||
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
|
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
|
||||||
expect_next_instance_of(Deployment) do |deployment|
|
expect(Deployments::HooksWorker).not_to receive(:perform_async)
|
||||||
expect(deployment).not_to receive(:execute_hooks)
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(service.execute).to be_persisted
|
expect(service.execute).to be_persisted
|
||||||
end
|
end
|
||||||
|
@ -80,9 +55,11 @@ RSpec.describe Deployments::CreateService do
|
||||||
it 'does not create a new deployment' do
|
it 'does not create a new deployment' do
|
||||||
described_class.new(environment, user, params).execute
|
described_class.new(environment, user, params).execute
|
||||||
|
|
||||||
expect do
|
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
|
||||||
described_class.new(environment.reload, user, params).execute
|
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
|
||||||
end.not_to change { Deployment.count }
|
expect(Deployments::HooksWorker).not_to receive(:perform_async)
|
||||||
|
|
||||||
|
described_class.new(environment.reload, user, params).execute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
||||||
allow(deployment).to receive(:execute_hooks)
|
allow(Deployments::HooksWorker).to receive(:perform_async)
|
||||||
job.success! # Create/Succeed deployment
|
job.success! # Create/Succeed deployment
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -357,7 +357,8 @@ RSpec.shared_examples "chat integration" do |integration_name|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'deployment events' do
|
context 'deployment events' do
|
||||||
let(:sample_data) { Gitlab::DataBuilder::Deployment.build(create(:deployment), Time.now) }
|
let(:deployment) { create(:deployment) }
|
||||||
|
let(:sample_data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
|
||||||
|
|
||||||
it_behaves_like "untriggered #{integration_name} integration"
|
it_behaves_like "untriggered #{integration_name} integration"
|
||||||
end
|
end
|
||||||
|
|
|
@ -230,7 +230,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
|
||||||
context 'deployment events' do
|
context 'deployment events' do
|
||||||
let_it_be(:deployment) { create(:deployment) }
|
let_it_be(:deployment) { create(:deployment) }
|
||||||
|
|
||||||
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) }
|
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, 'created', Time.current) }
|
||||||
|
|
||||||
it_behaves_like 'calls the integration API with the event message', /Deploy to (.*?) created/
|
it_behaves_like 'calls the integration API with the event message', /Deploy to (.*?) created/
|
||||||
end
|
end
|
||||||
|
@ -677,7 +677,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
|
||||||
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
|
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.now) }
|
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(chat_integration).to receive_messages(
|
allow(chat_integration).to receive_messages(
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue