Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cd6e1ccea4
commit
c39912f553
41 changed files with 952 additions and 916 deletions
|
@ -1,154 +1,23 @@
|
|||
<script>
|
||||
import {
|
||||
GlButton,
|
||||
GlModalDirective,
|
||||
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 });
|
||||
import { GlButton, GlModalDirective, GlTooltip } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { CREATE_TOKEN_MODAL } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AgentToken,
|
||||
GlButton,
|
||||
GlTooltip,
|
||||
GlModal,
|
||||
GlFormGroup,
|
||||
GlFormInput,
|
||||
GlFormTextarea,
|
||||
GlAlert,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
mixins: [trackingMixin],
|
||||
inject: ['agentName', 'projectPath', 'canAdminCluster'],
|
||||
props: {
|
||||
clusterAgentId: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
cursor: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
inject: ['canAdminCluster'],
|
||||
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'),
|
||||
dropdownDisabledHint: s__(
|
||||
'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>
|
||||
|
@ -170,82 +39,5 @@ export default {
|
|||
:title="$options.i18n.dropdownDisabledHint"
|
||||
/>
|
||||
</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>
|
||||
</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() {
|
||||
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 TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import CreateTokenButton from './create_token_button.vue';
|
||||
import CreateTokenModal from './create_token_modal.vue';
|
||||
import RevokeTokenButton from './revoke_token_button.vue';
|
||||
|
||||
export default {
|
||||
|
@ -13,6 +14,7 @@ export default {
|
|||
GlTruncate,
|
||||
TimeAgoTooltip,
|
||||
CreateTokenButton,
|
||||
CreateTokenModal,
|
||||
RevokeTokenButton,
|
||||
},
|
||||
i18n: {
|
||||
|
@ -85,57 +87,57 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="tokens.length">
|
||||
<create-token-button
|
||||
class="gl-text-right gl-my-5"
|
||||
:cluster-agent-id="clusterAgentId"
|
||||
:cursor="cursor"
|
||||
/>
|
||||
<div>
|
||||
<div v-if="tokens.length">
|
||||
<create-token-button class="gl-text-right gl-my-5" />
|
||||
|
||||
<gl-table
|
||||
:items="tokens"
|
||||
:fields="fields"
|
||||
fixed
|
||||
stacked="md"
|
||||
head-variant="white"
|
||||
thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
|
||||
>
|
||||
<template #cell(lastUsed)="{ item }">
|
||||
<time-ago-tooltip v-if="item.lastUsedAt" :time="item.lastUsedAt" />
|
||||
<span v-else>{{ $options.i18n.neverUsed }}</span>
|
||||
<gl-table
|
||||
:items="tokens"
|
||||
:fields="fields"
|
||||
fixed
|
||||
stacked="md"
|
||||
head-variant="white"
|
||||
thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
|
||||
>
|
||||
<template #cell(lastUsed)="{ item }">
|
||||
<time-ago-tooltip v-if="item.lastUsedAt" :time="item.lastUsedAt" />
|
||||
<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>
|
||||
</gl-empty-state>
|
||||
|
||||
<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>
|
||||
<create-token-modal :cluster-agent-id="clusterAgentId" :cursor="cursor" />
|
||||
</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>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Clusters
|
||||
module Applications
|
||||
class Runner < ApplicationRecord
|
||||
VERSION = '0.41.0'
|
||||
VERSION = '0.42.0'
|
||||
|
||||
self.table_name = 'clusters_applications_runners'
|
||||
|
||||
|
|
|
@ -108,13 +108,9 @@ class Deployment < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
after_transition any => :running do |deployment|
|
||||
after_transition any => :running do |deployment, transition|
|
||||
deployment.run_after_commit do
|
||||
if Feature.enabled?(:deployment_hooks_skip_worker, deployment.project)
|
||||
deployment.execute_hooks(Time.current)
|
||||
else
|
||||
Deployments::HooksWorker.perform_async(deployment_id: id, status_changed_at: Time.current)
|
||||
end
|
||||
Deployments::HooksWorker.perform_async(deployment_id: id, status: transition.to, status_changed_at: Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,13 +122,9 @@ class Deployment < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
after_transition any => FINISHED_STATUSES do |deployment|
|
||||
after_transition any => FINISHED_STATUSES do |deployment, transition|
|
||||
deployment.run_after_commit do
|
||||
if Feature.enabled?(:deployment_hooks_skip_worker, deployment.project)
|
||||
deployment.execute_hooks(Time.current)
|
||||
else
|
||||
Deployments::HooksWorker.perform_async(deployment_id: id, status_changed_at: Time.current)
|
||||
end
|
||||
Deployments::HooksWorker.perform_async(deployment_id: id, status: transition.to, status_changed_at: Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -269,8 +261,8 @@ class Deployment < ApplicationRecord
|
|||
Commit.truncate_sha(sha)
|
||||
end
|
||||
|
||||
def execute_hooks(status_changed_at)
|
||||
deployment_data = Gitlab::DataBuilder::Deployment.build(self, status_changed_at)
|
||||
def execute_hooks(status, status_changed_at)
|
||||
deployment_data = Gitlab::DataBuilder::Deployment.build(self, status, status_changed_at)
|
||||
project.execute_hooks(deployment_data, :deployment_hooks)
|
||||
project.execute_integrations(deployment_data, :deployment_hooks)
|
||||
end
|
||||
|
|
|
@ -63,7 +63,7 @@ module Integrations
|
|||
|
||||
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
|
||||
|
||||
def releases_events_data
|
||||
|
|
|
@ -11,5 +11,6 @@
|
|||
= form_tag search_path, method: :get, class: 'form-inline-flex' do |f|
|
||||
.field
|
||||
= 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'
|
||||
|
|
|
@ -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?) }
|
||||
- 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
|
||||
= 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_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
|
||||
|
|
|
@ -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 |
|
||||
| ----- | ----------- |
|
||||
| <a id="securitypolicyrelationtypedirect"></a>`DIRECT` | Policies defined for the project only. |
|
||||
| <a id="securitypolicyrelationtypeinherited"></a>`INHERITED` | Policies defined for the project and project's ancestor groups. |
|
||||
| <a id="securitypolicyrelationtypedirect"></a>`DIRECT` | Policies defined for the project/group only. |
|
||||
| <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`
|
||||
|
||||
|
|
|
@ -79,12 +79,10 @@ page, with these behaviors:
|
|||
- **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:`
|
||||
- **At capacity** - 🔴 `:red_circle:`
|
||||
- **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
|
||||
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.
|
||||
- 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
|
||||
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
|
||||
|
|
|
@ -668,6 +668,19 @@ The most popular public email domains cannot be restricted, such as:
|
|||
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
||||
- `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)**
|
||||
|
||||
Use group file templates to share a set of templates for common file
|
||||
|
|
|
@ -5,7 +5,8 @@ module Gitlab
|
|||
module Deployment
|
||||
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.
|
||||
deployable_url =
|
||||
if deployment.deployable
|
||||
|
@ -22,9 +23,13 @@ module Gitlab
|
|||
Gitlab::UrlBuilder.build(deployment.deployed_by)
|
||||
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',
|
||||
status: deployment.status,
|
||||
status: deployment_status,
|
||||
status_changed_at: status_changed_at,
|
||||
deployment_id: deployment.id,
|
||||
deployable_id: deployment.deployable_id,
|
||||
|
|
|
@ -15315,7 +15315,7 @@ msgstr ""
|
|||
msgid "Expires"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expires in %{expires_at}"
|
||||
msgid "Expires %{preposition} %{expires_at}"
|
||||
msgstr ""
|
||||
|
||||
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")
|
||||
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
|
||||
|
||||
attr_reader :uri, :username, :password, :ssh, :use_lfs
|
||||
|
|
|
@ -224,6 +224,10 @@ module QA
|
|||
"#{api_get_path}/releases"
|
||||
end
|
||||
|
||||
def api_housekeeping_path
|
||||
"/projects/#{id}/housekeeping"
|
||||
end
|
||||
|
||||
def api_post_body
|
||||
post_body = {
|
||||
name: name,
|
||||
|
@ -447,6 +451,31 @@ module QA
|
|||
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
|
||||
|
||||
# Return subset of fields for comparing projects
|
||||
|
|
|
@ -10,7 +10,7 @@ module QA
|
|||
let(:differ) { RSpec::Support::Differ.new(color: true) }
|
||||
let(:gitlab_group) { ENV['QA_LARGE_IMPORT_GROUP'] || 'gitlab-migration' }
|
||||
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
|
||||
{
|
||||
|
|
|
@ -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?
|
||||
exitstatus == 0 && !response.include?('Error encountered')
|
||||
end
|
||||
|
||||
def to_i
|
||||
response.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def run(command_str, env: [], max_attempts: 1, log_prefix: '')
|
||||
|
|
|
@ -231,7 +231,7 @@ module Glfm
|
|||
name = example.fetch(:name)
|
||||
|
||||
json = if glfm_examples_statuses.dig(name, 'skip_update_example_snapshot_prosemirror_json')
|
||||
existing_hash.dig(name)
|
||||
existing_hash[name]
|
||||
else
|
||||
wysiwyg_html_and_json_hash.dig(name, 'json')
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ RSpec.describe 'Admin Groups' do
|
|||
|
||||
let_it_be(:user) { create :user }
|
||||
let_it_be(:group) { create :group }
|
||||
let_it_be(:current_user) { create(:admin) }
|
||||
let_it_be_with_reload(:current_user) { create(:admin) }
|
||||
|
||||
before do
|
||||
sign_in(current_user)
|
||||
|
@ -231,6 +231,28 @@ RSpec.describe 'Admin Groups' do
|
|||
it_behaves_like 'adds user into a group' do
|
||||
let(:user_selector) { user.email }
|
||||
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
|
||||
|
||||
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::ModalHelpers
|
||||
|
||||
let(:user) { create :user }
|
||||
let(:project) { create(:project, :with_namespace_settings) }
|
||||
let(:current_user) { create(:admin) }
|
||||
let_it_be_with_reload(:user) { create :user }
|
||||
let_it_be_with_reload(:project) { create(:project, :with_namespace_settings) }
|
||||
let_it_be_with_reload(:current_user) { create(:admin) }
|
||||
|
||||
before do
|
||||
sign_in(current_user)
|
||||
gitlab_enable_admin_mode_sign_in(current_user)
|
||||
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
|
||||
let!(:archived_project) { create :project, :public, :archived }
|
||||
|
||||
|
|
|
@ -1,262 +1,71 @@
|
|||
import { GlButton, GlTooltip, 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 { GlButton, GlTooltip } from '@gitlab/ui';
|
||||
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 { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
|
||||
import {
|
||||
clusterAgentToken,
|
||||
getTokenResponse,
|
||||
createAgentTokenErrorResponse,
|
||||
} from '../../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
import { CREATE_TOKEN_MODAL } from '~/clusters/agents/constants';
|
||||
|
||||
describe('CreateTokenButton', () => {
|
||||
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 = {
|
||||
agentName,
|
||||
projectPath,
|
||||
canAdminCluster: true,
|
||||
};
|
||||
const propsData = {
|
||||
clusterAgentId,
|
||||
cursor,
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findBtn = () => wrapper.findComponent(GlButton);
|
||||
const findInput = () => wrapper.findComponent(GlFormInput);
|
||||
const findTextarea = () => wrapper.findComponent(GlFormTextarea);
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
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) => {
|
||||
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 = {} } = {}) => {
|
||||
const createWrapper = ({ provideData = {} } = {}) => {
|
||||
wrapper = shallowMountExtended(CreateTokenButton, {
|
||||
apolloProvider,
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
...provideData,
|
||||
},
|
||||
propsData,
|
||||
directives: {
|
||||
GlModalDirective: createMockDirective(),
|
||||
},
|
||||
stubs: {
|
||||
GlModal,
|
||||
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(() => {
|
||||
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', () => {
|
||||
expect(findBtn().text()).toBe('Create token');
|
||||
expect(findButton().text()).toBe('Create token');
|
||||
});
|
||||
|
||||
describe('when user cannot create token', () => {
|
||||
beforeEach(() => {
|
||||
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',
|
||||
);
|
||||
});
|
||||
it('displays create agent token button as not disabled', () => {
|
||||
expect(findButton().attributes('disabled')).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('when user can create a token and clicks the button', () => {
|
||||
beforeEach(() => {
|
||||
findBtn().vm.$emit('click');
|
||||
});
|
||||
it('triggers the modal', () => {
|
||||
const binding = getBinding(findButton().element, 'gl-modal-directive');
|
||||
|
||||
it('displays a token creation modal', () => {
|
||||
expect(findModal().isVisible()).toBe(true);
|
||||
});
|
||||
expect(binding.value).toBe(CREATE_TOKEN_MODAL);
|
||||
});
|
||||
});
|
||||
|
||||
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());
|
||||
});
|
||||
describe('when user cannot create token', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({ provideData: { canAdminCluster: false } });
|
||||
});
|
||||
|
||||
it('renders a textarea for the token description', () => {
|
||||
expect(findTextarea().exists()).toBe(true);
|
||||
expectDisabledAttribute(findTextarea(), false);
|
||||
});
|
||||
it('disabled the button', () => {
|
||||
expect(findButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
|
||||
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],
|
||||
);
|
||||
});
|
||||
});
|
||||
it('shows a disabled tooltip', () => {
|
||||
expect(findTooltip().attributes('title')).toBe(
|
||||
'Requires a Maintainer or greater role to perform these actions',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 TokenTable from '~/clusters/agents/components/token_table.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 { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import { MAX_LIST_COUNT } from '~/clusters/agents/constants';
|
||||
|
@ -50,6 +51,7 @@ describe('ClusterAgentTokenTable', () => {
|
|||
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
const findCreateTokenBtn = () => wrapper.findComponent(CreateTokenButton);
|
||||
const findCreateModal = () => wrapper.findComponent(CreateTokenModal);
|
||||
|
||||
beforeEach(() => {
|
||||
return createComponent(defaultTokens);
|
||||
|
@ -63,8 +65,8 @@ describe('ClusterAgentTokenTable', () => {
|
|||
expect(findCreateTokenBtn().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('passes the correct params to the create token component', () => {
|
||||
expect(findCreateTokenBtn().props()).toMatchObject({
|
||||
it('passes the correct params to the create token modal component', () => {
|
||||
expect(findCreateModal().props()).toMatchObject({
|
||||
clusterAgentId,
|
||||
cursor,
|
||||
});
|
||||
|
|
|
@ -127,8 +127,8 @@ describe('Client side Markdown processing', () => {
|
|||
pristineDoc: document,
|
||||
});
|
||||
|
||||
const sourceAttrs = (sourceMapKey, sourceMarkdown) => ({
|
||||
sourceMapKey,
|
||||
const source = (sourceMarkdown) => ({
|
||||
sourceMapKey: expect.any(String),
|
||||
sourceMarkdown,
|
||||
});
|
||||
|
||||
|
@ -136,63 +136,48 @@ describe('Client side Markdown processing', () => {
|
|||
{
|
||||
markdown: '__bold text__',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:13', '__bold text__'),
|
||||
bold(sourceAttrs('0:13', '__bold text__'), 'bold text'),
|
||||
),
|
||||
paragraph(source('__bold text__'), bold(source('__bold text__'), 'bold text')),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '**bold text**',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:13', '**bold text**'),
|
||||
bold(sourceAttrs('0:13', '**bold text**'), 'bold text'),
|
||||
),
|
||||
paragraph(source('**bold text**'), bold(source('**bold text**'), 'bold text')),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '<strong>bold text</strong>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:26', '<strong>bold text</strong>'),
|
||||
bold(sourceAttrs('0:26', '<strong>bold text</strong>'), 'bold text'),
|
||||
source('<strong>bold text</strong>'),
|
||||
bold(source('<strong>bold text</strong>'), 'bold text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '<b>bold text</b>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:16', '<b>bold text</b>'),
|
||||
bold(sourceAttrs('0:16', '<b>bold text</b>'), 'bold text'),
|
||||
),
|
||||
paragraph(source('<b>bold text</b>'), bold(source('<b>bold text</b>'), 'bold text')),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '_italic text_',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:13', '_italic text_'),
|
||||
italic(sourceAttrs('0:13', '_italic text_'), 'italic text'),
|
||||
),
|
||||
paragraph(source('_italic text_'), italic(source('_italic text_'), 'italic text')),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '*italic text*',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:13', '*italic text*'),
|
||||
italic(sourceAttrs('0:13', '*italic text*'), 'italic text'),
|
||||
),
|
||||
paragraph(source('*italic text*'), italic(source('*italic text*'), 'italic text')),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '<em>italic text</em>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:20', '<em>italic text</em>'),
|
||||
italic(sourceAttrs('0:20', '<em>italic text</em>'), 'italic text'),
|
||||
source('<em>italic text</em>'),
|
||||
italic(source('<em>italic text</em>'), 'italic text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -200,28 +185,25 @@ describe('Client side Markdown processing', () => {
|
|||
markdown: '<i>italic text</i>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:18', '<i>italic text</i>'),
|
||||
italic(sourceAttrs('0:18', '<i>italic text</i>'), 'italic text'),
|
||||
source('<i>italic text</i>'),
|
||||
italic(source('<i>italic text</i>'), 'italic text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '`inline code`',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:13', '`inline code`'),
|
||||
code(sourceAttrs('0:13', '`inline code`'), 'inline code'),
|
||||
),
|
||||
paragraph(source('`inline code`'), code(source('`inline code`'), 'inline code')),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '**`inline code bold`**',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:22', '**`inline code bold`**'),
|
||||
source('**`inline code bold`**'),
|
||||
bold(
|
||||
sourceAttrs('0:22', '**`inline code bold`**'),
|
||||
code(sourceAttrs('2:20', '`inline code bold`'), 'inline code bold'),
|
||||
source('**`inline code bold`**'),
|
||||
code(source('`inline code bold`'), 'inline code bold'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -230,10 +212,10 @@ describe('Client side Markdown processing', () => {
|
|||
markdown: '_`inline code italics`_',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:23', '_`inline code italics`_'),
|
||||
source('_`inline code italics`_'),
|
||||
italic(
|
||||
sourceAttrs('0:23', '_`inline code italics`_'),
|
||||
code(sourceAttrs('1:22', '`inline code italics`'), 'inline code italics'),
|
||||
source('_`inline code italics`_'),
|
||||
code(source('`inline code italics`'), 'inline code italics'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -246,8 +228,8 @@ describe('Client side Markdown processing', () => {
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:28', '<i class="foo">\n *bar*\n</i>'),
|
||||
italic(sourceAttrs('0:28', '<i class="foo">\n *bar*\n</i>'), '\n *bar*\n'),
|
||||
source('<i class="foo">\n *bar*\n</i>'),
|
||||
italic(source('<i class="foo">\n *bar*\n</i>'), '\n *bar*\n'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -259,8 +241,8 @@ describe('Client side Markdown processing', () => {
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:27', '<img src="bar" alt="foo" />'),
|
||||
image({ ...sourceAttrs('0:27', '<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||
source('<img src="bar" alt="foo" />'),
|
||||
image({ ...source('<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -273,15 +255,12 @@ describe('Client side Markdown processing', () => {
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:13', '- List item 1'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '- List item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
source('- List item 1'),
|
||||
listItem(source('- List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
),
|
||||
paragraph(
|
||||
sourceAttrs('15:42', '<img src="bar" alt="foo" />'),
|
||||
image({ ...sourceAttrs('15:42', '<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||
source('<img src="bar" alt="foo" />'),
|
||||
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")',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:43', '[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||
source('[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||
link(
|
||||
{
|
||||
...sourceAttrs('0:43', '[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||
...source('[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||
href: 'https://gitlab.com',
|
||||
title: 'Go to GitLab',
|
||||
},
|
||||
|
@ -305,12 +284,12 @@ describe('Client side Markdown processing', () => {
|
|||
markdown: '**[GitLab](https://gitlab.com "Go to GitLab")**',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:47', '**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
||||
source('**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
||||
bold(
|
||||
sourceAttrs('0:47', '**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
||||
source('**[GitLab](https://gitlab.com "Go to GitLab")**'),
|
||||
link(
|
||||
{
|
||||
...sourceAttrs('2:45', '[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||
...source('[GitLab](https://gitlab.com "Go to GitLab")'),
|
||||
href: 'https://gitlab.com',
|
||||
title: 'Go to GitLab',
|
||||
},
|
||||
|
@ -324,10 +303,10 @@ describe('Client side Markdown processing', () => {
|
|||
markdown: 'www.commonmark.org',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:18', 'www.commonmark.org'),
|
||||
source('www.commonmark.org'),
|
||||
link(
|
||||
{
|
||||
...sourceAttrs('0:18', 'www.commonmark.org'),
|
||||
...source('www.commonmark.org'),
|
||||
href: 'http://www.commonmark.org',
|
||||
},
|
||||
'www.commonmark.org',
|
||||
|
@ -339,11 +318,11 @@ describe('Client side Markdown processing', () => {
|
|||
markdown: 'Visit www.commonmark.org/help for more information.',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:51', 'Visit www.commonmark.org/help for more information.'),
|
||||
source('Visit www.commonmark.org/help for more information.'),
|
||||
'Visit ',
|
||||
link(
|
||||
{
|
||||
...sourceAttrs('6:29', 'www.commonmark.org/help'),
|
||||
...source('www.commonmark.org/help'),
|
||||
href: 'http://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.',
|
||||
expectedDoc: doc(
|
||||
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 ',
|
||||
link(
|
||||
{
|
||||
...sourceAttrs('40:62', 'hello+xyz@mail.example'),
|
||||
...source('hello+xyz@mail.example'),
|
||||
href: 'mailto:hello+xyz@mail.example',
|
||||
},
|
||||
'hello+xyz@mail.example',
|
||||
|
@ -373,11 +352,12 @@ describe('Client side Markdown processing', () => {
|
|||
markdown: '[https://gitlab.com>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:20', '[https://gitlab.com>'),
|
||||
source('[https://gitlab.com>'),
|
||||
'[',
|
||||
link(
|
||||
{
|
||||
...sourceAttrs(),
|
||||
sourceMapKey: null,
|
||||
sourceMarkdown: null,
|
||||
href: 'https://gitlab.com',
|
||||
},
|
||||
'https://gitlab.com',
|
||||
|
@ -392,9 +372,9 @@ This is a paragraph with a\\
|
|||
hard line break`,
|
||||
expectedDoc: doc(
|
||||
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',
|
||||
hardBreak(sourceAttrs('26:28', '\\\n')),
|
||||
hardBreak(source('\\\n')),
|
||||
'\nhard line break',
|
||||
),
|
||||
),
|
||||
|
@ -403,9 +383,9 @@ hard line break`,
|
|||
markdown: '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:57', '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
|
||||
source('![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
|
||||
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',
|
||||
src: 'https://gitlab.com/logo.png',
|
||||
title: 'GitLab Logo',
|
||||
|
@ -415,49 +395,43 @@ hard line break`,
|
|||
},
|
||||
{
|
||||
markdown: '---',
|
||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:3', '---'))),
|
||||
expectedDoc: doc(horizontalRule(source('---'))),
|
||||
},
|
||||
{
|
||||
markdown: '***',
|
||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:3', '***'))),
|
||||
expectedDoc: doc(horizontalRule(source('***'))),
|
||||
},
|
||||
{
|
||||
markdown: '___',
|
||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:3', '___'))),
|
||||
expectedDoc: doc(horizontalRule(source('___'))),
|
||||
},
|
||||
{
|
||||
markdown: '<hr>',
|
||||
expectedDoc: doc(horizontalRule(sourceAttrs('0:4', '<hr>'))),
|
||||
expectedDoc: doc(horizontalRule(source('<hr>'))),
|
||||
},
|
||||
{
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
expectedDoc: doc(
|
||||
heading({ ...sourceAttrs('0:14', '#### Heading 4'), level: 4 }, 'Heading 4'),
|
||||
),
|
||||
expectedDoc: doc(heading({ ...source('#### Heading 4'), level: 4 }, 'Heading 4')),
|
||||
},
|
||||
{
|
||||
markdown: '##### Heading 5',
|
||||
expectedDoc: doc(
|
||||
heading({ ...sourceAttrs('0:15', '##### Heading 5'), level: 5 }, 'Heading 5'),
|
||||
),
|
||||
expectedDoc: doc(heading({ ...source('##### Heading 5'), level: 5 }, 'Heading 5')),
|
||||
},
|
||||
{
|
||||
markdown: '###### Heading 6',
|
||||
expectedDoc: doc(
|
||||
heading({ ...sourceAttrs('0:16', '###### Heading 6'), level: 6 }, 'Heading 6'),
|
||||
),
|
||||
expectedDoc: doc(heading({ ...source('###### Heading 6'), level: 6 }, 'Heading 6')),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
|
@ -465,9 +439,7 @@ Heading
|
|||
one
|
||||
======
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
heading({ ...sourceAttrs('0:18', 'Heading\none\n======'), level: 1 }, 'Heading\none'),
|
||||
),
|
||||
expectedDoc: doc(heading({ ...source('Heading\none\n======'), level: 1 }, 'Heading\none')),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
|
@ -475,9 +447,7 @@ Heading
|
|||
two
|
||||
-------
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
heading({ ...sourceAttrs('0:19', 'Heading\ntwo\n-------'), level: 2 }, 'Heading\ntwo'),
|
||||
),
|
||||
expectedDoc: doc(heading({ ...source('Heading\ntwo\n-------'), level: 2 }, 'Heading\ntwo')),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
|
@ -486,15 +456,9 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:27', '- List item 1\n- List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '- List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('- List item 1\n- List item 2'),
|
||||
listItem(source('- List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('- List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -505,15 +469,9 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:27', '* List item 1\n* List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '* List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('* List item 1\n* List item 2'),
|
||||
listItem(source('* List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('* List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -524,15 +482,9 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:27', '+ List item 1\n+ List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '+ List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('+ List item 1\n+ List item 2'),
|
||||
listItem(source('+ List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('+ List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -543,15 +495,9 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
orderedList(
|
||||
sourceAttrs('0:29', '1. List item 1\n1. List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:14', '1. List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('1. List item 1\n1. List item 2'),
|
||||
listItem(source('1. List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('1. List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -562,15 +508,9 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
orderedList(
|
||||
sourceAttrs('0:29', '1. List item 1\n2. List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:14', '1. List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('1. List item 1\n2. List item 2'),
|
||||
listItem(source('1. List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('2. List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -581,15 +521,9 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
orderedList(
|
||||
sourceAttrs('0:29', '1) List item 1\n2) List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:14', '1) List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('1) List item 1\n2) List item 2'),
|
||||
listItem(source('1) List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('2) List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -600,15 +534,15 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:33', '- List item 1\n - Sub list item 1'),
|
||||
source('- List item 1\n - Sub list item 1'),
|
||||
listItem(
|
||||
sourceAttrs('0:33', '- List item 1\n - Sub list item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
source('- List item 1\n - Sub list item 1'),
|
||||
paragraph(source('List item 1'), 'List item 1'),
|
||||
bulletList(
|
||||
sourceAttrs('16:33', '- Sub list item 1'),
|
||||
source('- Sub list item 1'),
|
||||
listItem(
|
||||
sourceAttrs('16:33', '- Sub list item 1'),
|
||||
paragraph(sourceAttrs('18:33', 'Sub list item 1'), 'Sub list item 1'),
|
||||
source('- Sub list item 1'),
|
||||
paragraph(source('Sub list item 1'), 'Sub list item 1'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -624,19 +558,13 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs(
|
||||
'0:66',
|
||||
'- List item 1 paragraph 1\n\n List item 1 paragraph 2\n- List item 2',
|
||||
),
|
||||
source('- List item 1 paragraph 1\n\n List item 1 paragraph 2\n- List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:52', '- 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(sourceAttrs('29:52', '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'),
|
||||
source('- List item 1 paragraph 1\n\n List item 1 paragraph 2'),
|
||||
paragraph(source('List item 1 paragraph 1'), 'List item 1 paragraph 1'),
|
||||
paragraph(source('List item 1 paragraph 2'), 'List item 1 paragraph 2'),
|
||||
),
|
||||
listItem(source('- List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -646,13 +574,13 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:41', '- List item with an image ![bar](foo.png)'),
|
||||
source('- List item with an image ![bar](foo.png)'),
|
||||
listItem(
|
||||
sourceAttrs('0:41', '- List item with an image ![bar](foo.png)'),
|
||||
source('- List item with an image ![bar](foo.png)'),
|
||||
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',
|
||||
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(
|
||||
blockquote(
|
||||
sourceAttrs('0:22', '> This is a blockquote'),
|
||||
paragraph(sourceAttrs('2:22', 'This is a blockquote'), 'This is a blockquote'),
|
||||
source('> This is a blockquote'),
|
||||
paragraph(source('This is a blockquote'), 'This is a blockquote'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -676,17 +604,11 @@ two
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
blockquote(
|
||||
sourceAttrs('0:31', '> - List item 1\n> - List item 2'),
|
||||
source('> - List item 1\n> - List item 2'),
|
||||
bulletList(
|
||||
sourceAttrs('2:31', '- List item 1\n> - List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('2:15', '- List item 1'),
|
||||
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'),
|
||||
),
|
||||
source('- List item 1\n> - List item 2'),
|
||||
listItem(source('- List item 1'), paragraph(source('List item 1'), 'List item 1')),
|
||||
listItem(source('- List item 2'), paragraph(source('List item 2'), 'List item 2')),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -699,10 +621,10 @@ code block
|
|||
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(sourceAttrs('0:10', 'code block'), 'code block'),
|
||||
paragraph(source('code block'), 'code block'),
|
||||
codeBlock(
|
||||
{
|
||||
...sourceAttrs('12:42', " const fn = () => 'GitLab';"),
|
||||
...source(" const fn = () => 'GitLab';"),
|
||||
class: 'code highlight',
|
||||
language: null,
|
||||
},
|
||||
|
@ -719,7 +641,7 @@ const fn = () => 'GitLab';
|
|||
expectedDoc: doc(
|
||||
codeBlock(
|
||||
{
|
||||
...sourceAttrs('0:44', "```javascript\nconst fn = () => 'GitLab';\n```"),
|
||||
...source("```javascript\nconst fn = () => 'GitLab';\n```"),
|
||||
class: 'code highlight',
|
||||
language: 'javascript',
|
||||
},
|
||||
|
@ -736,7 +658,7 @@ const fn = () => 'GitLab';
|
|||
expectedDoc: doc(
|
||||
codeBlock(
|
||||
{
|
||||
...sourceAttrs('0:44', "~~~javascript\nconst fn = () => 'GitLab';\n~~~"),
|
||||
...source("~~~javascript\nconst fn = () => 'GitLab';\n~~~"),
|
||||
class: 'code highlight',
|
||||
language: 'javascript',
|
||||
},
|
||||
|
@ -752,7 +674,7 @@ const fn = () => 'GitLab';
|
|||
expectedDoc: doc(
|
||||
codeBlock(
|
||||
{
|
||||
...sourceAttrs('0:7', '```\n```'),
|
||||
...source('```\n```'),
|
||||
class: 'code highlight',
|
||||
language: null,
|
||||
},
|
||||
|
@ -770,7 +692,7 @@ const fn = () => 'GitLab';
|
|||
expectedDoc: doc(
|
||||
codeBlock(
|
||||
{
|
||||
...sourceAttrs('0:45', "```javascript\nconst fn = () => 'GitLab';\n\n```"),
|
||||
...source("```javascript\nconst fn = () => 'GitLab';\n\n```"),
|
||||
class: 'code highlight',
|
||||
language: 'javascript',
|
||||
},
|
||||
|
@ -782,8 +704,8 @@ const fn = () => 'GitLab';
|
|||
markdown: '~~Strikedthrough text~~',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:23', '~~Strikedthrough text~~'),
|
||||
strike(sourceAttrs('0:23', '~~Strikedthrough text~~'), 'Strikedthrough text'),
|
||||
source('~~Strikedthrough text~~'),
|
||||
strike(source('~~Strikedthrough text~~'), 'Strikedthrough text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -791,8 +713,8 @@ const fn = () => 'GitLab';
|
|||
markdown: '<del>Strikedthrough text</del>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:30', '<del>Strikedthrough text</del>'),
|
||||
strike(sourceAttrs('0:30', '<del>Strikedthrough text</del>'), 'Strikedthrough text'),
|
||||
source('<del>Strikedthrough text</del>'),
|
||||
strike(source('<del>Strikedthrough text</del>'), 'Strikedthrough text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -800,11 +722,8 @@ const fn = () => 'GitLab';
|
|||
markdown: '<strike>Strikedthrough text</strike>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:36', '<strike>Strikedthrough text</strike>'),
|
||||
strike(
|
||||
sourceAttrs('0:36', '<strike>Strikedthrough text</strike>'),
|
||||
'Strikedthrough text',
|
||||
),
|
||||
source('<strike>Strikedthrough text</strike>'),
|
||||
strike(source('<strike>Strikedthrough text</strike>'), 'Strikedthrough text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -812,8 +731,8 @@ const fn = () => 'GitLab';
|
|||
markdown: '<s>Strikedthrough text</s>',
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:26', '<s>Strikedthrough text</s>'),
|
||||
strike(sourceAttrs('0:26', '<s>Strikedthrough text</s>'), 'Strikedthrough text'),
|
||||
source('<s>Strikedthrough text</s>'),
|
||||
strike(source('<s>Strikedthrough text</s>'), 'Strikedthrough text'),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
@ -826,21 +745,21 @@ const fn = () => 'GitLab';
|
|||
taskList(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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(
|
||||
table(
|
||||
sourceAttrs('0:29', '| a | b |\n|---|---|\n| c | d |'),
|
||||
source('| a | b |\n|---|---|\n| c | d |'),
|
||||
tableRow(
|
||||
sourceAttrs('0:9', '| a | b |'),
|
||||
tableHeader(sourceAttrs('0:5', '| a |'), paragraph(sourceAttrs('2:3', 'a'), 'a')),
|
||||
tableHeader(sourceAttrs('5:9', ' b |'), paragraph(sourceAttrs('6:7', 'b'), 'b')),
|
||||
source('| a | b |'),
|
||||
tableHeader(source('| a |'), paragraph(source('a'), 'a')),
|
||||
tableHeader(source(' b |'), paragraph(source('b'), 'b')),
|
||||
),
|
||||
tableRow(
|
||||
sourceAttrs('20:29', '| c | d |'),
|
||||
tableCell(sourceAttrs('20:25', '| c |'), paragraph(sourceAttrs('22:23', 'c'), 'c')),
|
||||
tableCell(sourceAttrs('25:29', ' d |'), paragraph(sourceAttrs('26:27', 'd'), 'd')),
|
||||
source('| c | d |'),
|
||||
tableCell(source('| c |'), paragraph(source('c'), 'c')),
|
||||
tableCell(source(' d |'), paragraph(source('d'), 'd')),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -936,30 +855,29 @@ const fn = () => 'GitLab';
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
table(
|
||||
sourceAttrs(
|
||||
'0:132',
|
||||
source(
|
||||
'<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(
|
||||
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(
|
||||
{
|
||||
...sourceAttrs('19:58', '<th colspan="2" rowspan="5">Header</th>'),
|
||||
...source('<th colspan="2" rowspan="5">Header</th>'),
|
||||
colspan: 2,
|
||||
rowspan: 5,
|
||||
},
|
||||
paragraph(sourceAttrs('47:53', 'Header'), 'Header'),
|
||||
paragraph(source('Header'), 'Header'),
|
||||
),
|
||||
),
|
||||
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(
|
||||
{
|
||||
...sourceAttrs('78:115', '<td colspan="2" rowspan="5">Body</td>'),
|
||||
...source('<td colspan="2" rowspan="5">Body</td>'),
|
||||
colspan: 2,
|
||||
rowspan: 5,
|
||||
},
|
||||
paragraph(sourceAttrs('106:110', 'Body'), 'Body'),
|
||||
paragraph(source('Body'), 'Body'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -977,24 +895,24 @@ Paragraph
|
|||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:30', 'This is a footnote [^footnote]'),
|
||||
source('This is a footnote [^footnote]'),
|
||||
'This is a footnote ',
|
||||
footnoteReference({
|
||||
...sourceAttrs('19:30', '[^footnote]'),
|
||||
...source('[^footnote]'),
|
||||
identifier: 'footnote',
|
||||
label: 'footnote',
|
||||
}),
|
||||
),
|
||||
paragraph(sourceAttrs('32:41', 'Paragraph'), 'Paragraph'),
|
||||
paragraph(source('Paragraph'), 'Paragraph'),
|
||||
footnoteDefinition(
|
||||
{
|
||||
...sourceAttrs('43:75', '[^footnote]: Footnote definition'),
|
||||
...source('[^footnote]: Footnote definition'),
|
||||
identifier: '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
|
||||
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')
|
||||
end
|
||||
|
@ -23,7 +23,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
|||
expected_commit_url = Gitlab::UrlBuilder.build(commit)
|
||||
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_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
|
||||
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
|
||||
end
|
||||
|
@ -51,7 +51,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
|||
let_it_be(:project) { create(:project, :repository) }
|
||||
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
|
||||
project.repository.remove
|
||||
|
@ -69,7 +69,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
|
|||
context 'when deployed_by is nil' do
|
||||
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
|
||||
deployment.user = nil
|
||||
|
|
|
@ -1364,7 +1364,7 @@ RSpec.describe Ci::Build do
|
|||
|
||||
before do
|
||||
allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
||||
allow(deployment).to receive(:execute_hooks)
|
||||
allow(Deployments::HooksWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it 'has deployments record with created status' do
|
||||
|
@ -1420,7 +1420,7 @@ RSpec.describe Ci::Build do
|
|||
|
||||
before do
|
||||
allow(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
||||
allow(deployment).to receive(:execute_hooks)
|
||||
allow(Deployments::HooksWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it_behaves_like 'avoid deadlock'
|
||||
|
@ -1506,28 +1506,14 @@ RSpec.describe Ci::Build do
|
|||
|
||||
it 'transitions to running and calls webhook' 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
|
||||
end
|
||||
|
||||
expect(deployment).to be_running
|
||||
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
|
||||
|
|
|
@ -139,29 +139,16 @@ RSpec.describe Deployment do
|
|||
end
|
||||
end
|
||||
|
||||
it 'executes deployment hooks' do
|
||||
it 'executes Deployments::HooksWorker asynchronously' 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!
|
||||
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
|
||||
expect(Deployments::DropOlderDeploymentsWorker)
|
||||
.to receive(:perform_async).once.with(deployment.id)
|
||||
|
@ -189,28 +176,15 @@ RSpec.describe Deployment do
|
|||
deployment.succeed!
|
||||
end
|
||||
|
||||
it 'executes deployment hooks' do
|
||||
it 'executes Deployments::HooksWorker asynchronously' 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!
|
||||
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
|
||||
|
||||
context 'when deployment failed' do
|
||||
|
@ -232,28 +206,15 @@ RSpec.describe Deployment do
|
|||
deployment.drop!
|
||||
end
|
||||
|
||||
it 'executes deployment hooks' do
|
||||
it 'executes Deployments::HooksWorker asynchronously' 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!
|
||||
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
|
||||
|
||||
context 'when deployment was canceled' do
|
||||
|
@ -275,28 +236,15 @@ RSpec.describe Deployment do
|
|||
deployment.cancel!
|
||||
end
|
||||
|
||||
it 'executes deployment hooks' do
|
||||
it 'executes Deployments::HooksWorker asynchronously' 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!
|
||||
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
|
||||
|
||||
context 'when deployment was skipped' do
|
||||
|
@ -324,12 +272,6 @@ RSpec.describe Deployment do
|
|||
deployment.skip!
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not execute deployment hooks' do
|
||||
expect(deployment).not_to receive(:execute_hooks)
|
||||
|
||||
deployment.skip!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deployment is blocked' do
|
||||
|
@ -353,12 +295,6 @@ RSpec.describe Deployment do
|
|||
|
||||
deployment.block!
|
||||
end
|
||||
|
||||
it 'does not execute deployment hooks' do
|
||||
expect(deployment).not_to receive(:execute_hooks)
|
||||
|
||||
deployment.block!
|
||||
end
|
||||
end
|
||||
|
||||
describe 'synching status to Jira' do
|
||||
|
@ -1052,30 +988,11 @@ RSpec.describe Deployment do
|
|||
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
||||
expect(Deployments::LinkMergeRequestWorker).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)
|
||||
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
|
||||
freeze_time do
|
||||
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(:args) do
|
||||
Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
|
||||
Gitlab::DataBuilder::Deployment.build(deployment, 'success', Time.current)
|
||||
end
|
||||
|
||||
it_behaves_like Integrations::ChatMessage
|
||||
|
|
|
@ -59,7 +59,7 @@ RSpec.describe Integrations::Slack do
|
|||
context 'deployment notification' do
|
||||
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'
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'spec_helper'
|
|||
# See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
|
||||
# for documentation on this spec.
|
||||
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_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
|
||||
|
|
|
@ -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
|
||||
# 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
|
||||
# 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
|
||||
# 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::LinkMergeRequestWorker).to receive(:perform_async)
|
||||
expect_next_instance_of(Deployment) do |deployment|
|
||||
expect(deployment).to receive(:execute_hooks)
|
||||
end
|
||||
expect(Deployments::HooksWorker).to receive(:perform_async)
|
||||
|
||||
expect(service.execute).to be_persisted
|
||||
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
|
||||
service = described_class.new(
|
||||
environment,
|
||||
|
@ -60,9 +37,7 @@ RSpec.describe Deployments::CreateService do
|
|||
|
||||
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
|
||||
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
|
||||
expect_next_instance_of(Deployment) do |deployment|
|
||||
expect(deployment).not_to receive(:execute_hooks)
|
||||
end
|
||||
expect(Deployments::HooksWorker).not_to receive(:perform_async)
|
||||
|
||||
expect(service.execute).to be_persisted
|
||||
end
|
||||
|
@ -80,9 +55,11 @@ RSpec.describe Deployments::CreateService do
|
|||
it 'does not create a new deployment' do
|
||||
described_class.new(environment, user, params).execute
|
||||
|
||||
expect do
|
||||
described_class.new(environment.reload, user, params).execute
|
||||
end.not_to change { Deployment.count }
|
||||
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
|
||||
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
|
||||
expect(Deployments::HooksWorker).not_to receive(:perform_async)
|
||||
|
||||
described_class.new(environment.reload, user, params).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
|
|||
|
||||
before do
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -357,7 +357,8 @@ RSpec.shared_examples "chat integration" do |integration_name|
|
|||
end
|
||||
|
||||
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"
|
||||
end
|
||||
|
|
|
@ -230,7 +230,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
|
|||
context 'deployment events' do
|
||||
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/
|
||||
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)
|
||||
end
|
||||
|
||||
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.now) }
|
||||
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
|
||||
|
||||
before do
|
||||
allow(chat_integration).to receive_messages(
|
||||
|
|
BIN
vendor/project_templates/rails.tar.gz
vendored
BIN
vendor/project_templates/rails.tar.gz
vendored
Binary file not shown.
Loading…
Reference in a new issue