Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-21 18:09:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-21 18:09:33 +0300
commitc39912f5538caca913374a8983adffc234b3f1c9 (patch)
tree8dfffd81aff7ba03c152991a0c825d9076dcf624
parentcd6e1ccea48e770d74296f4fdb8957d3cfb073f3 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/clusters/agents/components/create_token_button.vue216
-rw-r--r--app/assets/javascripts/clusters/agents/components/create_token_modal.vue218
-rw-r--r--app/assets/javascripts/clusters/agents/components/revoke_token_button.vue2
-rw-r--r--app/assets/javascripts/clusters/agents/components/token_table.vue92
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/deployment.rb20
-rw-r--r--app/services/concerns/integrations/project_test_data.rb2
-rw-r--r--app/views/errors/not_found.html.haml3
-rw-r--r--app/views/shared/members/_member.html.haml3
-rw-r--r--app/workers/deployments/hooks_worker.rb2
-rw-r--r--config/feature_flags/development/deployment_hooks_skip_worker.yml8
-rw-r--r--config/feature_flags/undefined/gitaly_revlist_for_repo_size.yml8
-rw-r--r--doc/api/graphql/reference/index.md5
-rw-r--r--doc/development/code_review.md4
-rw-r--r--doc/user/group/index.md13
-rw-r--r--lib/gitlab/data_builder/deployment.rb9
-rw-r--r--locale/gitlab.pot2
-rw-r--r--qa/qa/git/repository.rb25
-rw-r--r--qa/qa/resource/project.rb29
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb77
-rw-r--r--qa/qa/support/run.rb4
-rw-r--r--scripts/lib/glfm/update_example_snapshots.rb2
-rw-r--r--spec/features/admin/admin_groups_spec.rb24
-rw-r--r--spec/features/admin/admin_projects_spec.rb28
-rw-r--r--spec/frontend/clusters/agents/components/create_token_button_spec.js255
-rw-r--r--spec/frontend/clusters/agents/components/create_token_modal_spec.js223
-rw-r--r--spec/frontend/clusters/agents/components/token_table_spec.js6
-rw-r--r--spec/frontend/content_editor/remark_markdown_processing_spec.js374
-rw-r--r--spec/lib/gitlab/data_builder/deployment_spec.rb10
-rw-r--r--spec/models/ci/build_spec.rb22
-rw-r--r--spec/models/deployment_spec.rb117
-rw-r--r--spec/models/integrations/chat_message/deployment_message_spec.rb2
-rw-r--r--spec/models/integrations/slack_spec.rb2
-rw-r--r--spec/requests/api/markdown_snapshot_spec.rb1
-rw-r--r--spec/scripts/lib/glfm/update_example_snapshots_spec.rb2
-rw-r--r--spec/services/deployments/create_service_spec.rb37
-rw-r--r--spec/services/deployments/update_environment_service_spec.rb2
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb4
-rw-r--r--vendor/project_templates/rails.tar.gzbin148742 -> 150831 bytes
41 files changed, 948 insertions, 912 deletions
diff --git a/app/assets/javascripts/clusters/agents/components/create_token_button.vue b/app/assets/javascripts/clusters/agents/components/create_token_button.vue
index 74155d7819a..67a178b5f98 100644
--- a/app/assets/javascripts/clusters/agents/components/create_token_button.vue
+++ b/app/assets/javascripts/clusters/agents/components/create_token_button.vue
@@ -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>
diff --git a/app/assets/javascripts/clusters/agents/components/create_token_modal.vue b/app/assets/javascripts/clusters/agents/components/create_token_modal.vue
new file mode 100644
index 00000000000..451e1ee1d67
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/components/create_token_modal.vue
@@ -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>
diff --git a/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue b/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue
index 7d36cbb170d..f0af0da4bb4 100644
--- a/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue
+++ b/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue
@@ -148,7 +148,7 @@ export default {
},
hideModal() {
this.resetModal();
- this.$refs.modal.hide();
+ this.$refs.modal?.hide();
},
},
};
diff --git a/app/assets/javascripts/clusters/agents/components/token_table.vue b/app/assets/javascripts/clusters/agents/components/token_table.vue
index 9e64c9da712..f74d66f6b8f 100644
--- a/app/assets/javascripts/clusters/agents/components/token_table.vue
+++ b/app/assets/javascripts/clusters/agents/components/token_table.vue
@@ -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>
- </template>
+ <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(createdAt)="{ item }">
+ <time-ago-tooltip :time="item.createdAt" />
+ </template>
- <template #cell(createdBy)="{ item }">
- <span>{{ createdByName(item) }}</span>
- </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" />
+ <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>
+ <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 #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-table>
- </div>
+ </gl-empty-state>
- <gl-empty-state v-else :title="$options.i18n.noTokens">
- <template #actions>
- <create-token-button :cluster-agent-id="clusterAgentId" :cursor="cursor" />
- </template>
- </gl-empty-state>
+ <create-token-modal :cluster-agent-id="clusterAgentId" :cursor="cursor" />
+ </div>
</template>
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index bed0eab5a58..98f5b343ecf 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -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'
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index fc0dd7e00c7..64f284af43c 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -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
diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb
index acaa773fd49..ae1e1d1e66c 100644
--- a/app/services/concerns/integrations/project_test_data.rb
+++ b/app/services/concerns/integrations/project_test_data.rb
@@ -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
diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml
index 291adbc0ae8..54291cd9abc 100644
--- a/app/views/errors/not_found.html.haml
+++ b/app/views/errors/not_found.html.haml
@@ -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'
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index ba0e5e492f4..23f78f4be45 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -45,7 +45,8 @@
&middot;
%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: ''
diff --git a/app/workers/deployments/hooks_worker.rb b/app/workers/deployments/hooks_worker.rb
index 608601b4eb9..62e75638c7d 100644
--- a/app/workers/deployments/hooks_worker.rb
+++ b/app/workers/deployments/hooks_worker.rb
@@ -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
diff --git a/config/feature_flags/development/deployment_hooks_skip_worker.yml b/config/feature_flags/development/deployment_hooks_skip_worker.yml
deleted file mode 100644
index d7d35912e2d..00000000000
--- a/config/feature_flags/development/deployment_hooks_skip_worker.yml
+++ /dev/null
@@ -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
diff --git a/config/feature_flags/undefined/gitaly_revlist_for_repo_size.yml b/config/feature_flags/undefined/gitaly_revlist_for_repo_size.yml
new file mode 100644
index 00000000000..376874b2c83
--- /dev/null
+++ b/config/feature_flags/undefined/gitaly_revlist_for_repo_size.yml
@@ -0,0 +1,8 @@
+---
+name: gitaly_revlist_for_repo_size
+introduced_by_url:
+rollout_issue_url:
+milestone:
+type: undefined
+group:
+default_enabled: false
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 805f6a506b7..cc302d51e00 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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`
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 5e576acc15d..d864f369945 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -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
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index c0ae721e3b4..18b4a7e1bcb 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -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
diff --git a/lib/gitlab/data_builder/deployment.rb b/lib/gitlab/data_builder/deployment.rb
index 0e6841e10a7..a9c69e3f997 100644
--- a/lib/gitlab/data_builder/deployment.rb
+++ b/lib/gitlab/data_builder/deployment.rb
@@ -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,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d6ae2c1fc13..7933a206091 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15315,7 +15315,7 @@ msgstr ""
msgid "Expires"
msgstr ""
-msgid "Expires in %{expires_at}"
+msgid "Expires %{preposition} %{expires_at}"
msgstr ""
msgid "Expires on"
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 2244a78b9e5..01faa41a2ff 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -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
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 59964c5833d..825041cbead 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -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
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
index 440e8e1238c..6ed3befa3ab 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
@@ -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
{
diff --git a/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb b/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb
new file mode 100644
index 00000000000..406ff191f95
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb
@@ -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
diff --git a/qa/qa/support/run.rb b/qa/qa/support/run.rb
index a91e7dfd2cb..242293f9eef 100644
--- a/qa/qa/support/run.rb
+++ b/qa/qa/support/run.rb
@@ -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: '')
diff --git a/scripts/lib/glfm/update_example_snapshots.rb b/scripts/lib/glfm/update_example_snapshots.rb
index 9ffa54cd5d4..10529b1f974 100644
--- a/scripts/lib/glfm/update_example_snapshots.rb
+++ b/scripts/lib/glfm/update_example_snapshots.rb
@@ -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
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 2d541a34f62..f903d7c2db2 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -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
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 2166edf65ff..866368af40a 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -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 }
diff --git a/spec/frontend/clusters/agents/components/create_token_button_spec.js b/spec/frontend/clusters/agents/components/create_token_button_spec.js
index fb1a3aa2963..73856b74a8d 100644
--- a/spec/frontend/clusters/agents/components/create_token_button_spec.js
+++ b/spec/frontend/clusters/agents/components/create_token_button_spec.js
@@ -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', () => {
- it('displays create agent token button', () => {
- expect(findBtn().text()).toBe('Create token');
+ describe('when user can create token', () => {
+ beforeEach(() => {
+ createWrapper();
});
- 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', () => {
+ expect(findButton().text()).toBe('Create token');
});
- describe('when user can create a token and clicks the button', () => {
- beforeEach(() => {
- findBtn().vm.$emit('click');
- });
-
- it('displays a token creation modal', () => {
- expect(findModal().isVisible()).toBe(true);
- });
-
- 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('displays create agent token button as not disabled', () => {
+ expect(findButton().attributes('disabled')).toBeUndefined();
+ });
- it('creates a token', () => {
- expect(createResponse).toHaveBeenCalledWith({
- input: { clusterAgentId, name: 'new-token', description: 'new-token-description' },
- });
- });
+ it('triggers the modal', () => {
+ const binding = getBinding(findButton().element, 'gl-modal-directive');
- it('shows agent instructions', () => {
- expect(findAgentInstructions().props()).toMatchObject({
- agentName,
- agentToken: 'token-secret',
- modalId: CREATE_TOKEN_MODAL,
- });
- });
+ expect(binding.value).toBe(CREATE_TOKEN_MODAL);
+ });
+ });
- it('renders a close button', () => {
- expect(findActionButton().isVisible()).toBe(true);
- expect(findActionButton().text()).toBe('Close');
- expectDisabledAttribute(findActionButton(), false);
- });
- });
+ describe('when user cannot create token', () => {
+ beforeEach(() => {
+ createWrapper({ provideData: { canAdminCluster: false } });
+ });
- describe('error creating a new token', () => {
- beforeEach(async () => {
- await mockCreatedResponse(createAgentTokenErrorResponse);
- });
+ it('disabled the button', () => {
+ expect(findButton().attributes('disabled')).toBe('true');
+ });
- 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',
+ );
});
});
});
diff --git a/spec/frontend/clusters/agents/components/create_token_modal_spec.js b/spec/frontend/clusters/agents/components/create_token_modal_spec.js
new file mode 100644
index 00000000000..ad48afe10b6
--- /dev/null
+++ b/spec/frontend/clusters/agents/components/create_token_modal_spec.js
@@ -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],
+ );
+ });
+ });
+});
diff --git a/spec/frontend/clusters/agents/components/token_table_spec.js b/spec/frontend/clusters/agents/components/token_table_spec.js
index f6baaf87fa4..6caeaf5c192 100644
--- a/spec/frontend/clusters/agents/components/token_table_spec.js
+++ b/spec/frontend/clusters/agents/components/token_table_spec.js
@@ -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,
});
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index 60dc540e192..e969e3627ca 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -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',
- ),
- 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'),
- ),
+ source('- List item 1 paragraph 1\n\n List item 1 paragraph 2\n- List item 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'),
),
},
];
diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb
index e8fe80f75cb..8ee57542d43 100644
--- a/spec/lib/gitlab/data_builder/deployment_spec.rb
+++ b/spec/lib/gitlab/data_builder/deployment_spec.rb
@@ -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
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6ad6bb16eb5..eb7b742f0b1 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -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
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index a58d32dfe5d..a3a4f0a5d38 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -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')
diff --git a/spec/models/integrations/chat_message/deployment_message_spec.rb b/spec/models/integrations/chat_message/deployment_message_spec.rb
index 6bcd29c0a00..8da27ef5aa0 100644
--- a/spec/models/integrations/chat_message/deployment_message_spec.rb
+++ b/spec/models/integrations/chat_message/deployment_message_spec.rb
@@ -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
diff --git a/spec/models/integrations/slack_spec.rb b/spec/models/integrations/slack_spec.rb
index 3997d69f947..5801a4c3749 100644
--- a/spec/models/integrations/slack_spec.rb
+++ b/spec/models/integrations/slack_spec.rb
@@ -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
diff --git a/spec/requests/api/markdown_snapshot_spec.rb b/spec/requests/api/markdown_snapshot_spec.rb
index 37607a4e866..2341e1566d2 100644
--- a/spec/requests/api/markdown_snapshot_spec.rb
+++ b/spec/requests/api/markdown_snapshot_spec.rb
@@ -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
diff --git a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
index 82ed8563c3a..1f032e1e40a 100644
--- a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
+++ b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
@@ -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.
diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb
index f6f4c68a6f1..0f2a6ce32e1 100644
--- a/spec/services/deployments/create_service_spec.rb
+++ b/spec/services/deployments/create_service_spec.rb
@@ -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
diff --git a/spec/services/deployments/update_environment_service_spec.rb b/spec/services/deployments/update_environment_service_spec.rb
index e2d7a80fde3..8ab53a37a33 100644
--- a/spec/services/deployments/update_environment_service_spec.rb
+++ b/spec/services/deployments/update_environment_service_spec.rb
@@ -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
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index fa10b03fa90..d189e91effd 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -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
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index 2e062cda4e9..d80be5be3b3 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -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(
diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz
index 357a049da44..17706a67dd0 100644
--- a/vendor/project_templates/rails.tar.gz
+++ b/vendor/project_templates/rails.tar.gz
Binary files differ