summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_empty_state.vue23
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_empty_state.vue89
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_view_all.vue33
-rw-r--r--app/assets/javascripts/clusters_list/components/install_agent_modal.vue78
-rw-r--r--app/assets/javascripts/clusters_list/constants.js90
-rw-r--r--app/assets/javascripts/ide/services/index.js6
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue5
-rw-r--r--app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql1
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js2
-rw-r--r--app/assets/javascripts/projects/new/components/new_project_url_select.vue34
-rw-r--r--app/controllers/users/terms_controller.rb1
-rw-r--r--app/finders/environments/environments_by_deployments_finder.rb22
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/member.rb2
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/issues/create_service.rb2
-rw-r--r--app/views/users/terms/index.html.haml4
-rw-r--r--app/workers/issue_placement_worker.rb7
-rw-r--r--app/workers/issue_rebalancing_worker.rb3
-rw-r--r--app/workers/issues/rebalancing_worker.rb1
-rw-r--r--app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb4
-rw-r--r--config/feature_flags/development/environments_by_deployments_finder_exists_optimization.yml8
-rw-r--r--config/initializers/active_record_transaction_observer.rb4
-rw-r--r--db/migrate/20211117174209_create_vulnerability_reads.rb24
-rw-r--r--db/migrate/20211118124537_add_foreign_key_to_vulnerability_reads_on_vulnerability.rb15
-rw-r--r--db/migrate/20211118124628_add_foreign_key_to_vulnerability_reads_on_project.rb15
-rw-r--r--db/migrate/20211118124650_add_foreign_key_to_vulnerability_reads_on_scanner.rb15
-rw-r--r--db/schema_migrations/202111171742091
-rw-r--r--db/schema_migrations/202111181245371
-rw-r--r--db/schema_migrations/202111181246281
-rw-r--r--db/schema_migrations/202111181246501
-rw-r--r--db/structure.sql48
-rw-r--r--doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md22
-rw-r--r--doc/development/api_graphql_styleguide.md9
-rw-r--r--doc/development/documentation/styleguide/index.md9
-rw-r--r--doc/development/documentation/styleguide/word_list.md16
-rw-r--r--doc/development/graphql_guide/index.md1
-rw-r--r--doc/development/graphql_guide/monitoring.md89
-rw-r--r--doc/user/admin_area/index.md1
-rw-r--r--doc/user/profile/active_sessions.md2
-rw-r--r--doc/user/profile/preferences.md6
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/median.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb8
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml1
-rw-r--r--lib/gitlab/redis/multi_store.rb8
-rw-r--r--locale/gitlab.pot115
-rw-r--r--spec/features/clusters/create_agent_spec.rb8
-rw-r--r--spec/features/projects/cluster_agents_spec.rb2
-rw-r--r--spec/features/projects/clusters/eks_spec.rb2
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb12
-rw-r--r--spec/features/projects/clusters/user_spec.rb4
-rw-r--r--spec/features/projects/clusters_spec.rb22
-rw-r--r--spec/finders/environments/environments_by_deployments_finder_spec.rb14
-rw-r--r--spec/frontend/clusters_list/components/agent_empty_state_spec.js13
-rw-r--r--spec/frontend/clusters_list/components/clusters_empty_state_spec.js54
-rw-r--r--spec/frontend/clusters_list/components/clusters_main_view_spec.js8
-rw-r--r--spec/frontend/ide/services/index_spec.js8
-rw-r--r--spec/frontend/jobs/components/table/cells/actions_cell_spec.js20
-rw-r--r--spec/frontend/jobs/mock_data.js37
-rw-r--r--spec/frontend/projects/new/components/new_project_url_select_spec.js81
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb13
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb8
-rw-r--r--spec/workers/issue_placement_worker_spec.rb6
-rw-r--r--spec/workers/issues/placement_worker_spec.rb2
-rw-r--r--spec/workers/issues/rebalancing_worker_spec.rb14
-rw-r--r--spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb6
69 files changed, 735 insertions, 438 deletions
diff --git a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
index 37cec0b2891..f54f7b11414 100644
--- a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
@@ -6,8 +6,7 @@ import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants';
export default {
i18n: I18N_AGENTS_EMPTY_STATE,
modalId: INSTALL_AGENT_MODAL_ID,
- multipleClustersDocsUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
- installDocsUrl: helpPagePath('administration/clusters/kas'),
+ agentDocsUrl: helpPagePath('user/clusters/agent/index'),
components: {
GlButton,
GlEmptyState,
@@ -32,38 +31,24 @@ export default {
<gl-empty-state :svg-path="emptyStateImage" title="" class="agents-empty-state">
<template #description>
<p class="gl-text-left">
- {{ $options.i18n.introText }}
- </p>
- <p class="gl-text-left">
- <gl-sprintf :message="$options.i18n.multipleClustersText">
+ <gl-sprintf :message="$options.i18n.introText">
<template #link="{ content }">
- <gl-link
- :href="$options.multipleClustersDocsUrl"
- target="_blank"
- data-testid="multiple-clusters-docs-link"
- >
+ <gl-link :href="$options.agentDocsUrl">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
-
- <p>
- <gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link">
- {{ $options.i18n.learnMoreText }}
- </gl-link>
- </p>
</template>
<template #actions>
<gl-button
v-if="!isChildComponent"
v-gl-modal-directive="$options.modalId"
- data-testid="integration-primary-button"
category="primary"
variant="confirm"
>
- {{ $options.i18n.primaryButtonText }}
+ {{ $options.i18n.buttonText }}
</gl-button>
</template>
</gl-empty-state>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
index 3879af6e9cb..ce601de57bd 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
@@ -1,5 +1,5 @@
<script>
-import { GlEmptyState, GlButton, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlEmptyState, GlButton, GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
import { mapState } from 'vuex';
import { helpPagePath } from '~/helpers/help_page_helper';
import { I18N_CLUSTERS_EMPTY_STATE } from '../constants';
@@ -11,6 +11,7 @@ export default {
GlButton,
GlLink,
GlSprintf,
+ GlAlert,
},
inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'newClusterPath'],
props: {
@@ -20,8 +21,11 @@ export default {
type: Boolean,
},
},
- learnMoreHelpUrl: helpPagePath('user/project/clusters/index'),
- multipleClustersHelpUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
+ clustersHelpUrl: helpPagePath('user/infrastructure/clusters/index', {
+ anchor: 'certificate-based-kubernetes-integration-deprecated',
+ }),
+ blogPostUrl:
+ 'https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/',
computed: {
...mapState(['canAddCluster']),
},
@@ -29,48 +33,45 @@ export default {
</script>
<template>
- <gl-empty-state :svg-path="clustersEmptyStateImage" title="">
- <template #description>
- <p class="gl-text-left">
- {{ $options.i18n.description }}
- </p>
- <p class="gl-text-left">
- <gl-sprintf :message="$options.i18n.multipleClustersText">
- <template #link="{ content }">
- <gl-link
- :href="$options.multipleClustersHelpUrl"
- target="_blank"
- data-testid="multiple-clusters-docs-link"
- >
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
+ <div>
+ <gl-empty-state :svg-path="clustersEmptyStateImage" title="">
+ <template #description>
+ <p class="gl-text-left">
+ <gl-sprintf :message="$options.i18n.introText">
+ <template #link="{ content }">
+ <gl-link :href="$options.clustersHelpUrl">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
- <p v-if="emptyStateHelpText" data-testid="clusters-empty-state-text">
- {{ emptyStateHelpText }}
- </p>
+ <p v-if="emptyStateHelpText" data-testid="clusters-empty-state-text">
+ {{ emptyStateHelpText }}
+ </p>
+ </template>
- <p>
- <gl-link :href="$options.learnMoreHelpUrl" target="_blank" data-testid="clusters-docs-link">
- {{ $options.i18n.learnMoreLinkText }}
- </gl-link>
- </p>
- </template>
+ <template #actions>
+ <gl-button
+ v-if="!isChildComponent"
+ data-testid="integration-primary-button"
+ data-qa-selector="add_kubernetes_cluster_link"
+ category="primary"
+ variant="confirm"
+ :disabled="!canAddCluster"
+ :href="newClusterPath"
+ >
+ {{ $options.i18n.buttonText }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
- <template #actions>
- <gl-button
- v-if="!isChildComponent"
- data-testid="integration-primary-button"
- data-qa-selector="add_kubernetes_cluster_link"
- category="primary"
- variant="confirm"
- :disabled="!canAddCluster"
- :href="newClusterPath"
- >
- {{ $options.i18n.buttonText }}
- </gl-button>
- </template>
- </gl-empty-state>
+ <gl-alert variant="warning" :dismissible="false">
+ <gl-sprintf :message="$options.i18n.alertText">
+ <template #link="{ content }">
+ <gl-link :href="$options.blogPostUrl" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+ </div>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
index 285876e57d8..0e312d21e4e 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
@@ -34,10 +34,12 @@ export default {
directives: {
GlModalDirective,
},
- AGENT_CARD_INFO,
- CERTIFICATE_BASED_CARD_INFO,
MAX_CLUSTERS_LIST,
INSTALL_AGENT_MODAL_ID,
+ i18n: {
+ agent: AGENT_CARD_INFO,
+ certificate: CERTIFICATE_BASED_CARD_INFO,
+ },
inject: ['addClusterPath'],
props: {
defaultBranchName: {
@@ -122,21 +124,21 @@ export default {
</gl-sprintf>
</h3>
- <gl-badge id="clusters-recommended-badge" size="md" variant="info">{{
- $options.AGENT_CARD_INFO.tooltip.label
+ <gl-badge id="clusters-recommended-badge" variant="info">{{
+ $options.i18n.agent.tooltip.label
}}</gl-badge>
<gl-popover
target="clusters-recommended-badge"
container="viewport"
placement="bottom"
- :title="$options.AGENT_CARD_INFO.tooltip.title"
+ :title="$options.i18n.agent.tooltip.title"
>
<p class="gl-mb-0">
- <gl-sprintf :message="$options.AGENT_CARD_INFO.tooltip.text">
+ <gl-sprintf :message="$options.i18n.agent.tooltip.text">
<template #link="{ content }">
<gl-link
- :href="$options.AGENT_CARD_INFO.tooltip.link"
+ :href="$options.i18n.agent.tooltip.link"
target="_blank"
class="gl-font-sm"
>
@@ -159,9 +161,9 @@ export default {
<gl-link
v-if="totalAgents"
data-testid="agents-tab-footer-link"
- :href="`?tab=${$options.AGENT_CARD_INFO.tabName}`"
- @click="changeTab($event, $options.AGENT_CARD_INFO.tabName)"
- ><gl-sprintf :message="$options.AGENT_CARD_INFO.footerText"
+ :href="`?tab=${$options.i18n.agent.tabName}`"
+ @click="changeTab($event, $options.i18n.agent.tabName)"
+ ><gl-sprintf :message="$options.i18n.agent.footerText"
><template #number>{{ cardFooterNumber(totalAgents) }}</template></gl-sprintf
></gl-link
><gl-button
@@ -169,7 +171,7 @@ export default {
class="gl-ml-4"
category="secondary"
variant="confirm"
- >{{ $options.AGENT_CARD_INFO.actionText }}</gl-button
+ >{{ $options.i18n.agent.actionText }}</gl-button
>
</template>
</gl-card>
@@ -190,6 +192,7 @@ export default {
<template #total>{{ clustersCardTitle.total }}</template>
</gl-sprintf>
</h3>
+ <gl-badge variant="warning">{{ $options.i18n.certificate.badgeText }}</gl-badge>
</template>
<clusters :limit="$options.MAX_CLUSTERS_LIST" :is-child-component="true" />
@@ -198,9 +201,9 @@ export default {
<gl-link
v-if="totalClusters"
data-testid="clusters-tab-footer-link"
- :href="`?tab=${$options.CERTIFICATE_BASED_CARD_INFO.tabName}`"
- @click="changeTab($event, $options.CERTIFICATE_BASED_CARD_INFO.tabName)"
- ><gl-sprintf :message="$options.CERTIFICATE_BASED_CARD_INFO.footerText"
+ :href="`?tab=${$options.i18n.certificate.tabName}`"
+ @click="changeTab($event, $options.i18n.certificate.tabName)"
+ ><gl-sprintf :message="$options.i18n.certificate.footerText"
><template #number>{{ cardFooterNumber(totalClusters) }}</template></gl-sprintf
></gl-link
><gl-button
@@ -209,7 +212,7 @@ export default {
variant="confirm"
class="gl-ml-4"
:href="addClusterPath"
- >{{ $options.CERTIFICATE_BASED_CARD_INFO.actionText }}</gl-button
+ >{{ $options.i18n.certificate.actionText }}</gl-button
>
</template>
</gl-card>
diff --git a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
index 2c5bbe4a757..a2f3c3579ab 100644
--- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -9,7 +9,7 @@ import {
GlSprintf,
} from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import Tracking from '~/tracking';
import { generateAgentRegistrationCommand } from '../clusters_util';
@@ -38,9 +38,19 @@ export default {
EVENT_ACTIONS_OPEN,
EVENT_ACTIONS_CLICK,
EVENT_LABEL_MODAL,
+ basicInstallPath: helpPagePath('user/clusters/agent/install/index', {
+ anchor: 'install-the-agent-into-the-cluster',
+ }),
+ advancedInstallPath: helpPagePath('user/clusters/agent/install/index', {
+ anchor: 'advanced-installation',
+ }),
+ enableKasPath: helpPagePath('administration/clusters/kas'),
+ installAgentPath: helpPagePath('user/clusters/agent/install/index'),
+ registerAgentPath: helpPagePath('user/clusters/agent/install/index', {
+ anchor: 'create-an-agent-record-in-gitlab',
+ }),
components: {
AvailableAgentsDropdown,
- ClipboardButton,
CodeBlock,
GlAlert,
GlButton,
@@ -49,6 +59,7 @@ export default {
GlLink,
GlModal,
GlSprintf,
+ ModalCopyButton,
},
mixins: [trackingMixin],
inject: ['projectPath', 'kasAddress', 'emptyStateImage'],
@@ -103,17 +114,6 @@ export default {
agentRegistrationCommand() {
return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
},
- basicInstallPath() {
- return helpPagePath('user/clusters/agent/install/index', {
- anchor: 'install-the-agent-into-the-cluster',
- });
- },
- advancedInstallPath() {
- return helpPagePath('user/clusters/agent/install/index', { anchor: 'advanced-installation' });
- },
- enableKasPath() {
- return helpPagePath('administration/clusters/kas');
- },
getAgentsQueryVariables() {
return {
defaultBranchName: this.defaultBranchName,
@@ -122,11 +122,6 @@ export default {
projectPath: this.projectPath,
};
},
- installAgentPath() {
- return helpPagePath('user/clusters/agent/index', {
- anchor: 'define-a-configuration-repository',
- });
- },
i18n() {
return I18N_AGENT_MODAL[this.modalType];
},
@@ -272,7 +267,7 @@ export default {
<p class="gl-mb-0">{{ i18n.selectAgentBody }}</p>
<p>
- <gl-link :href="basicInstallPath" target="_blank"> {{ i18n.learnMoreLink }}</gl-link>
+ <gl-link :href="$options.registerAgentPath"> {{ i18n.learnMoreLink }}</gl-link>
</p>
<form>
@@ -301,7 +296,7 @@ export default {
<p>
<gl-sprintf :message="i18n.tokenBody">
<template #link="{ content }">
- <gl-link :href="basicInstallPath" target="_blank"> {{ content }}</gl-link>
+ <gl-link :href="$options.basicInstallPath" target="_blank"> {{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
@@ -315,7 +310,11 @@ export default {
<p>
<gl-form-input-group readonly :value="agentToken" :select-on-click="true">
<template #append>
- <clipboard-button :text="agentToken" :title="i18n.copyToken" />
+ <modal-copy-button
+ :text="agentToken"
+ :title="i18n.copyToken"
+ :modal-id="$options.modalId"
+ />
</template>
</gl-form-input-group>
</p>
@@ -339,7 +338,7 @@ export default {
<p>
<gl-sprintf :message="i18n.advancedInstallBody">
<template #link="{ content }">
- <gl-link :href="advancedInstallPath" target="_blank"> {{ content }}</gl-link>
+ <gl-link :href="$options.advancedInstallPath" target="_blank"> {{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
@@ -350,34 +349,26 @@ export default {
<div class="gl-text-center gl-mb-5">
<img :alt="i18n.altText" :src="emptyStateImage" height="100" />
</div>
- <p>{{ i18n.modalBody }}</p>
- <p v-if="kasDisabled">
- <gl-sprintf :message="i18n.enableKasText">
+ <p>
+ <gl-sprintf :message="i18n.modalBody">
<template #link="{ content }">
- <gl-link :href="enableKasPath"> {{ content }}</gl-link>
+ <gl-link :href="$options.installAgentPath"> {{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
- <p class="gl-mb-0">
- <gl-link :href="installAgentPath">
- {{ i18n.docsLinkText }}
- </gl-link>
+ <p v-if="kasDisabled">
+ <gl-sprintf :message="i18n.enableKasText">
+ <template #link="{ content }">
+ <gl-link :href="$options.enableKasPath"> {{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
</p>
</template>
<template #modal-footer>
<gl-button
- v-if="canCancel"
- :data-track-action="$options.EVENT_ACTIONS_CLICK"
- :data-track-label="$options.EVENT_LABEL_MODAL"
- data-track-property="cancel"
- @click="closeModal"
- >{{ i18n.cancel }}
- </gl-button>
-
- <gl-button
v-if="registered"
variant="confirm"
category="primary"
@@ -401,6 +392,15 @@ export default {
</gl-button>
<gl-button
+ v-if="canCancel"
+ :data-track-action="$options.EVENT_ACTIONS_CLICK"
+ :data-track-label="$options.EVENT_LABEL_MODAL"
+ data-track-property="cancel"
+ @click="closeModal"
+ >{{ i18n.cancel }}
+ </gl-button>
+
+ <gl-button
v-if="isEmptyStateModal"
:href="repositoryPath"
variant="confirm"
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index 960c9701d81..9b52df74fc5 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -66,63 +66,61 @@ export const STATUSES = {
export const I18N_AGENT_MODAL = {
agent_registration: {
- registerAgentButton: s__('ClusterAgents|Register Agent'),
+ registerAgentButton: s__('ClusterAgents|Register'),
close: __('Close'),
cancel: __('Cancel'),
- modalTitle: s__('ClusterAgents|Connect with Agent'),
-
- selectAgentTitle: s__('ClusterAgents|Select which Agent you want to install'),
+ modalTitle: s__('ClusterAgents|Connect a cluster through the Agent'),
+ selectAgentTitle: s__('ClusterAgents|Select an agent to register with GitLab'),
selectAgentBody: s__(
- 'ClusterAgents|Select an Agent to register with GitLab and install on your cluster.',
+ 'ClusterAgents|Register an agent to generate a token that will be used to install the agent on your cluster in the next step.',
),
- learnMoreLink: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent registration.'),
+ learnMoreLink: s__('ClusterAgents|How to register an agent?'),
copyToken: s__('ClusterAgents|Copy token'),
tokenTitle: s__('ClusterAgents|Registration token'),
tokenBody: s__(
- `ClusterAgents|The registration token will be used to connect the Agent on your cluster to GitLab. To learn more about the registration tokens and how they are used %{linkStart}go to the documentation%{linkEnd}.`,
+ `ClusterAgents|The registration token will be used to connect the agent on your cluster to GitLab. %{linkStart}What are registration tokens?%{linkEnd}`,
),
tokenSingleUseWarningTitle: s__(
- 'ClusterAgents|The token value will not be shown again after you close this window.',
+ 'ClusterAgents|You cannot see this token again after you close this window.',
),
tokenSingleUseWarningBody: s__(
- `ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window.`,
+ `ClusterAgents|The recommended installation method includes the token. If you want to follow the advanced installation method provided in the docs, make sure you save the token value before you close this window.`,
),
basicInstallTitle: s__('ClusterAgents|Recommended installation method'),
basicInstallBody: __(
- `Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command.`,
+ `Open a CLI and connect to the cluster you want to install the agent in. Use this installation method to minimize any manual steps. The token is already included in the command.`,
),
- advancedInstallTitle: s__('ClusterAgents|Alternative installation methods'),
+ advancedInstallTitle: s__('ClusterAgents|Advanced installation methods'),
advancedInstallBody: s__(
- 'ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}.',
+ 'ClusterAgents|For the advanced installation method %{linkStart}see the documentation%{linkEnd}.',
),
- registrationErrorTitle: __('Failed to register Agent'),
+ registrationErrorTitle: s__('ClusterAgents|Failed to register an agent'),
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
},
empty_state: {
- modalTitle: s__('ClusterAgents|Install new Agent'),
+ modalTitle: s__('ClusterAgents|Connect your cluster through the Agent'),
modalBody: s__(
- 'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
+ "ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}What's the agent's configuration file?%{linkEnd}",
),
- docsLinkText: s__('ClusterAgents|Learn more about installing a GitLab Kubernetes Agent'),
enableKasText: s__(
- 'ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}',
+ "ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it.",
),
- altText: s__('ClusterAgents|GitLab Kubernetes Agent'),
- secondaryButton: s__('ClusterAgents|Go to the repository'),
- done: __('Done'),
+ altText: s__('ClusterAgents|GitLab Agent for Kubernetes'),
+ secondaryButton: s__('ClusterAgents|Go to the repository files'),
+ done: __('Cancel'),
},
};
export const KAS_DISABLED_ERROR = 'Gitlab::Kas::Client::ConfigurationError';
export const I18N_AVAILABLE_AGENTS_DROPDOWN = {
- selectAgent: s__('ClusterAgents|Select an Agent'),
+ selectAgent: s__('ClusterAgents|Select an agent'),
registeringAgent: s__('ClusterAgents|Registering Agent'),
};
@@ -143,7 +141,7 @@ export const AGENT_STATUSES = {
title: s__('ClusterAgents|Agent might not be connected to GitLab'),
body: sprintf(
s__(
- 'ClusterAgents|The Agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}.',
+ 'ClusterAgents|The agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}.',
),
),
},
@@ -161,50 +159,48 @@ export const AGENT_STATUSES = {
export const I18N_AGENTS_EMPTY_STATE = {
introText: s__(
- 'ClusterAgents|Use GitLab Agents to more securely integrate with your clusters to deploy your applications, run your pipelines, use review apps and much more.',
- ),
- multipleClustersText: s__(
- 'ClusterAgents|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}',
+ 'ClusterIntegration|Use the %{linkStart}GitLab Agent%{linkEnd} to safely connect your Kubernetes clusters to GitLab. You can deploy your applications, run your pipelines, use Review Apps, and much more.',
),
- learnMoreText: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent.'),
- primaryButtonText: s__('ClusterAgents|Connect with a GitLab Agent'),
+ buttonText: s__('ClusterAgents|Connect with the GitLab Agent'),
};
export const I18N_CLUSTERS_EMPTY_STATE = {
- description: s__(
- 'ClusterIntegration|Use certificates to integrate with your clusters to deploy your applications, run your pipelines, use review apps and much more in an easy way.',
- ),
- multipleClustersText: s__(
- 'ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}',
+ introText: s__(
+ 'ClusterIntegration|Connect your cluster to GitLab through %{linkStart}cluster certificates%{linkEnd}.',
),
- learnMoreLinkText: s__('ClusterIntegration|Learn more about the GitLab managed clusters'),
buttonText: s__('ClusterIntegration|Connect with a certificate'),
+ alertText: s__(
+ 'ClusterIntegration|The certificate-based method to connect clusters to GitLab was %{linkStart}deprecated%{linkEnd} in GitLab 14.5.',
+ ),
};
export const AGENT_CARD_INFO = {
tabName: 'agent',
- title: sprintf(s__('ClusterAgents|%{number} of %{total} Agent based integrations')),
- emptyTitle: s__('ClusterAgents|No Agent based integrations'),
+ title: sprintf(s__('ClusterAgents|%{number} of %{total} agents')),
+ emptyTitle: s__('ClusterAgents|No agents'),
tooltip: {
label: s__('ClusterAgents|Recommended'),
- title: s__('ClusterAgents|GitLab Agents'),
+ title: s__('ClusterAgents|GitLab Agent'),
text: sprintf(
s__(
- 'ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}',
+ 'ClusterAgents|The GitLab Agent provides an increased level of security when connecting Kubernetes clusters to GitLab. %{linkStart}Learn more about the GitLab Agent.%{linkEnd}',
),
),
link: helpPagePath('user/clusters/agent/index'),
},
- actionText: s__('ClusterAgents|Install new Agent'),
- footerText: sprintf(s__('ClusterAgents|View all %{number} Agent based integrations')),
+ actionText: s__('ClusterAgents|Install a new agent'),
+ footerText: sprintf(s__('ClusterAgents|View all %{number} agents')),
};
export const CERTIFICATE_BASED_CARD_INFO = {
tabName: 'certificate_based',
- title: sprintf(s__('ClusterAgents|%{number} of %{total} Certificate based integrations')),
- emptyTitle: s__('ClusterAgents|No Certificate based integrations'),
+ title: sprintf(
+ s__('ClusterAgents|%{number} of %{total} clusters connected through cluster certificates'),
+ ),
+ emptyTitle: s__('ClusterAgents|No clusters connected through cluster certificates'),
actionText: s__('ClusterAgents|Connect existing cluster'),
- footerText: sprintf(s__('ClusterAgents|View all %{number} Certificate based integrations')),
+ footerText: sprintf(s__('ClusterAgents|View all %{number} clusters')),
+ badgeText: s__('ClusterAgents|Deprecated'),
};
export const MAX_CLUSTERS_LIST = 6;
@@ -221,7 +217,7 @@ export const CLUSTERS_TABS = [
queryParamValue: 'agent',
},
{
- title: s__('ClusterAgents|Certificate based'),
+ title: s__('ClusterAgents|Certificate'),
component: 'clusters',
queryParamValue: 'certificate_based',
},
@@ -229,9 +225,9 @@ export const CLUSTERS_TABS = [
export const CLUSTERS_ACTIONS = {
actionsButton: s__('ClusterAgents|Actions'),
- createNewCluster: s__('ClusterAgents|Create new cluster'),
- connectWithAgent: s__('ClusterAgents|Connect with Agent'),
- connectExistingCluster: s__('ClusterAgents|Connect with certificate'),
+ createNewCluster: s__('ClusterAgents|Create a new cluster'),
+ connectWithAgent: s__('ClusterAgents|Connect with the Agent'),
+ connectExistingCluster: s__('ClusterAgents|Connect with a certificate'),
};
export const AGENT = 'agent';
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index ef4f47f226a..fd2b064160b 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -4,6 +4,7 @@ import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.
import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { query, mutate } from './gql';
const fetchApiProjectData = (projectPath) => Api.project(projectPath).then(({ data }) => data);
@@ -12,7 +13,10 @@ const fetchGqlProjectData = (projectPath) =>
query({
query: getIdeProject,
variables: { projectPath },
- }).then(({ data }) => data.project);
+ }).then(({ data }) => ({
+ ...data.project,
+ id: getIdFromGraphQLId(data.project.id),
+ }));
export default {
getFileData(endpoint) {
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
index 51251c0cacc..9a8ec30bb2d 100644
--- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -64,6 +64,9 @@ export default {
canReadJob() {
return this.job.userPermissions?.readBuild;
},
+ canUpdateJob() {
+ return this.job.userPermissions?.updateBuild;
+ },
isActive() {
return this.job.active;
},
@@ -139,7 +142,7 @@ export default {
<template>
<gl-button-group>
- <template v-if="canReadJob">
+ <template v-if="canReadJob && canUpdateJob">
<gl-button
v-if="isActive"
data-testid="cancel-button"
diff --git a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
index e6a26675773..df37e084408 100644
--- a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
+++ b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
@@ -75,6 +75,7 @@ query getJobs(
userPermissions {
readBuild
readJobArtifacts
+ updateBuild
}
}
}
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index e53a39cde06..12462a2575e 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,6 +1,6 @@
export const DASH_SCOPE = '-';
-const PATH_SEPARATOR = '/';
+export const PATH_SEPARATOR = '/';
const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`);
const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`);
const SHA_REGEX = /[\da-f]{40}/gi;
diff --git a/app/assets/javascripts/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
index e0ba60074af..f4a21c6057c 100644
--- a/app/assets/javascripts/projects/new/components/new_project_url_select.vue
+++ b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
@@ -8,7 +8,7 @@ import {
GlDropdownSectionHeader,
GlSearchBoxByType,
} from '@gitlab/ui';
-import { joinPaths } from '~/lib/utils/url_utility';
+import { joinPaths, PATH_SEPARATOR } from '~/lib/utils/url_utility';
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
@@ -36,7 +36,9 @@ export default {
};
},
skip() {
- return this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH;
+ const hasNotEnoughSearchCharacters =
+ this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH;
+ return this.shouldSkipQuery || hasNotEnoughSearchCharacters;
},
debounce: DEBOUNCE_DELAY,
},
@@ -52,7 +54,7 @@ export default {
data() {
return {
currentUser: {},
- groupToFilterBy: undefined,
+ groupPathToFilterBy: undefined,
search: '',
selectedNamespace: this.namespaceId
? {
@@ -63,6 +65,7 @@ export default {
id: this.userNamespaceId,
fullPath: this.userNamespaceFullPath,
},
+ shouldSkipQuery: true,
};
},
computed: {
@@ -73,10 +76,8 @@ export default {
return this.currentUser.namespace || {};
},
filteredGroups() {
- return this.groupToFilterBy
- ? this.userGroups.filter((group) =>
- group.fullPath.startsWith(this.groupToFilterBy.fullPath),
- )
+ return this.groupPathToFilterBy
+ ? this.userGroups.filter((group) => group.fullPath.startsWith(this.groupPathToFilterBy))
: this.userGroups;
},
hasGroupMatches() {
@@ -85,7 +86,7 @@ export default {
hasNamespaceMatches() {
return (
this.userNamespace.fullPath?.toLowerCase().includes(this.search.toLowerCase()) &&
- !this.groupToFilterBy
+ !this.groupPathToFilterBy
);
},
hasNoMatches() {
@@ -99,7 +100,10 @@ export default {
eventHub.$off('select-template', this.handleSelectTemplate);
},
methods: {
- focusInput() {
+ handleDropdownShown() {
+ if (this.shouldSkipQuery) {
+ this.shouldSkipQuery = false;
+ }
this.$refs.search.focusInput();
},
handleDropdownItemClick(namespace) {
@@ -111,13 +115,9 @@ export default {
});
this.setNamespace(namespace);
},
- handleSelectTemplate(groupId) {
- this.groupToFilterBy = this.userGroups.find(
- (group) => getIdFromGraphQLId(group.id) === groupId,
- );
- if (this.groupToFilterBy) {
- this.setNamespace(this.groupToFilterBy);
- }
+ handleSelectTemplate(id, fullPath) {
+ this.groupPathToFilterBy = fullPath.split(PATH_SEPARATOR).shift();
+ this.setNamespace({ id, fullPath });
},
setNamespace({ id, fullPath }) {
this.selectedNamespace = {
@@ -137,7 +137,7 @@ export default {
toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20"
data-qa-selector="select_namespace_dropdown"
@show="track('activate_form_input', { label: trackLabel, property: 'project_path' })"
- @shown="focusInput"
+ @shown="handleDropdownShown"
>
<gl-search-box-by-type
ref="search"
diff --git a/app/controllers/users/terms_controller.rb b/app/controllers/users/terms_controller.rb
index 7fbf0faa68b..f0d95b56d33 100644
--- a/app/controllers/users/terms_controller.rb
+++ b/app/controllers/users/terms_controller.rb
@@ -3,6 +3,7 @@
module Users
class TermsController < ApplicationController
include InternalRedirect
+ include OneTrustCSP
skip_before_action :authenticate_user!, only: [:index]
skip_before_action :enforce_terms!
diff --git a/app/finders/environments/environments_by_deployments_finder.rb b/app/finders/environments/environments_by_deployments_finder.rb
index 435ad1f3471..2716c80ea6e 100644
--- a/app/finders/environments/environments_by_deployments_finder.rb
+++ b/app/finders/environments/environments_by_deployments_finder.rb
@@ -12,30 +12,18 @@ module Environments
# rubocop: disable CodeReuse/ActiveRecord
def execute
- deployments = project.deployments
deployments =
if ref
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
- deployments.where(deployments_query, ref: ref.to_s)
+ Deployment.where(deployments_query, ref: ref.to_s)
elsif commit
- deployments.where(sha: commit.sha)
+ Deployment.where(sha: commit.sha)
else
- deployments.none
+ Deployment.none
end
- environments =
- if Feature.enabled?(:environments_by_deployments_finder_exists_optimization, project, default_enabled: :yaml)
- # TODO: replace unscope with deployments = Deployment on top of the method https://gitlab.com/gitlab-org/gitlab/-/issues/343544
- project.environments.available
- .where('EXISTS (?)', deployments.unscope(where: :project_id).where('environment_id = environments.id'))
- else
- environment_ids = deployments
- .group(:environment_id)
- .select(:environment_id)
-
- project.environments.available
- .where(id: environment_ids)
- end
+ environments = project.environments.available
+ .where('EXISTS (?)', deployments.where('environment_id = environments.id'))
if params[:find_latest]
find_one(environments.order_by_last_deployed_at_desc)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 581f7c18277..dcba6cb3536 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -605,7 +605,7 @@ class Issue < ApplicationRecord
def could_not_move(exception)
# Symptom of running out of space - schedule rebalancing
- IssueRebalancingWorker.perform_async(nil, *project.self_or_root_group_ids)
+ Issues::RebalancingWorker.perform_async(nil, *project.self_or_root_group_ids)
end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 45c2edf1f9a..a2d53f006d4 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -25,7 +25,7 @@ class Member < ApplicationRecord
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :member_task
- delegate :name, :username, :email, to: :user, prefix: true
+ delegate :name, :username, :email, :last_activity_on, to: :user, prefix: true
delegate :tasks_to_be_done, to: :member_task, allow_nil: true
validates :expires_at, allow_blank: true, future_date: true
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index efb5de5b17c..577f7dd1e3a 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -30,7 +30,7 @@ module Issues
gates = [issue.project, issue.project.group].compact
return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) }
- IssueRebalancingWorker.perform_async(nil, *issue.project.self_or_root_group_ids)
+ Issues::RebalancingWorker.perform_async(nil, *issue.project.self_or_root_group_ids)
end
private
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index fa8d380404b..1296b6c392a 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -41,7 +41,7 @@ module Issues
user = current_user
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id)
- IssuePlacementWorker.perform_async(nil, issue.project_id)
+ Issues::PlacementWorker.perform_async(nil, issue.project_id)
Namespaces::OnboardingIssueCreatedWorker.perform_async(issue.namespace.id)
end
end
diff --git a/app/views/users/terms/index.html.haml b/app/views/users/terms/index.html.haml
index 92095e78f69..8ad3be19c1c 100644
--- a/app/views/users/terms/index.html.haml
+++ b/app/views/users/terms/index.html.haml
@@ -1,5 +1,9 @@
- redirect_params = { redirect: @redirect } if @redirect
- accept_term_link = accept_term_path(@term, redirect_params)
+- content_for :page_specific_javascripts do
+ = render "layouts/google_tag_manager_head"
+ = render "layouts/one_trust"
+= render "layouts/google_tag_manager_body"
- if Feature.enabled?(:terms_of_service_vue, current_user, default_enabled: :yaml)
#js-terms-of-service{ data: { terms_data: terms_data(@term, @redirect) } }
diff --git a/app/workers/issue_placement_worker.rb b/app/workers/issue_placement_worker.rb
index cfd72b90a42..26dec221f45 100644
--- a/app/workers/issue_placement_worker.rb
+++ b/app/workers/issue_placement_worker.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
+# DEPRECATED. Will be removed in 14.7 https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72803
+# Please use Issues::PlacementWorker instead
+#
# todo: remove this worker and it's queue definition from all_queues after Issues::PlacementWorker is deployed
# We want to keep it for one release in case some jobs are already scheduled in the old queue so we need the worker
# to be available to finish those. All new jobs will be queued into the new queue.
@@ -43,10 +46,10 @@ class IssuePlacementWorker
Issue.move_nulls_to_end(to_place)
Issues::BaseService.new(project: nil).rebalance_if_needed(to_place.max_by(&:relative_position))
- IssuePlacementWorker.perform_async(nil, leftover.project_id) if leftover.present?
+ Issues::PlacementWorker.perform_async(nil, leftover.project_id) if leftover.present?
rescue RelativePositioning::NoSpaceLeft => e
Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id, project_id: project_id)
- IssueRebalancingWorker.perform_async(nil, *root_namespace_id_to_rebalance(issue, project_id))
+ Issues::RebalancingWorker.perform_async(nil, *root_namespace_id_to_rebalance(issue, project_id))
end
def find_issue(issue_id, project_id)
diff --git a/app/workers/issue_rebalancing_worker.rb b/app/workers/issue_rebalancing_worker.rb
index a43e76feae4..73edb2eb653 100644
--- a/app/workers/issue_rebalancing_worker.rb
+++ b/app/workers/issue_rebalancing_worker.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
+# DEPRECATED. Will be removed in 14.7 https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72803
+# Please use Issues::RebalancingWorker instead
+#
# todo: remove this worker and it's queue definition from all_queues after Issue::RebalancingWorker is released.
# We want to keep it for one release in case some jobs are already scheduled in the old queue so we need the worker
# to be available to finish those. All new jobs will be queued into the new queue.
diff --git a/app/workers/issues/rebalancing_worker.rb b/app/workers/issues/rebalancing_worker.rb
index 466617d9fa1..8de0588a2a1 100644
--- a/app/workers/issues/rebalancing_worker.rb
+++ b/app/workers/issues/rebalancing_worker.rb
@@ -17,6 +17,7 @@ module Issues
# we need to have exactly one of the project_id and root_namespace_id params be non-nil
raise ArgumentError, "Expected only one of the params project_id: #{project_id} and root_namespace_id: #{root_namespace_id}" if project_id && root_namespace_id
return if project_id.nil? && root_namespace_id.nil?
+ return if ::Gitlab::Issues::Rebalancing::State.rebalance_recently_finished?(project_id, root_namespace_id)
# pull the projects collection to be rebalanced either the project if namespace is not a group(i.e. user namesapce)
# or the root namespace, this also makes the worker backward compatible with previous version where a project_id was
diff --git a/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb b/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb
index d1759589cc0..77cedae558b 100644
--- a/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb
+++ b/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb
@@ -20,13 +20,13 @@ module Issues
namespaces = Namespace.id_in(namespace_ids)
projects = Project.id_in(project_ids)
- IssueRebalancingWorker.bulk_perform_async_with_contexts(
+ Issues::RebalancingWorker.bulk_perform_async_with_contexts(
namespaces,
arguments_proc: -> (namespace) { [nil, nil, namespace.id] },
context_proc: -> (namespace) { { namespace: namespace } }
)
- IssueRebalancingWorker.bulk_perform_async_with_contexts(
+ Issues::RebalancingWorker.bulk_perform_async_with_contexts(
projects,
arguments_proc: -> (project) { [nil, project.id, nil] },
context_proc: -> (project) { { project: project } }
diff --git a/config/feature_flags/development/environments_by_deployments_finder_exists_optimization.yml b/config/feature_flags/development/environments_by_deployments_finder_exists_optimization.yml
deleted file mode 100644
index a265d9528f7..00000000000
--- a/config/feature_flags/development/environments_by_deployments_finder_exists_optimization.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: environments_by_deployments_finder_exists_optimization
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72781/
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343544
-milestone: '14.5'
-type: development
-group: group::release
-default_enabled: false
diff --git a/config/initializers/active_record_transaction_observer.rb b/config/initializers/active_record_transaction_observer.rb
index fc9b73d656e..a1d4b13344e 100644
--- a/config/initializers/active_record_transaction_observer.rb
+++ b/config/initializers/active_record_transaction_observer.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-return unless Gitlab.com? || Gitlab.dev_or_test_env?
-
def feature_flags_available?
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
active_db_connection = ActiveRecord::Base.connection.active? rescue false
@@ -11,6 +9,8 @@ rescue ActiveRecord::NoDatabaseError
false
end
+return unless Gitlab.com? || Gitlab.dev_or_test_env?
+
Gitlab::Application.configure do
if feature_flags_available? && ::Feature.enabled?(:active_record_transactions_tracking, type: :ops, default_enabled: :yaml)
Gitlab::Database::Transaction::Observer.register!
diff --git a/db/migrate/20211117174209_create_vulnerability_reads.rb b/db/migrate/20211117174209_create_vulnerability_reads.rb
new file mode 100644
index 00000000000..b9e32bfd0fa
--- /dev/null
+++ b/db/migrate/20211117174209_create_vulnerability_reads.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateVulnerabilityReads < Gitlab::Database::Migration[1.0]
+ def change
+ create_table :vulnerability_reads do |t|
+ t.bigint :vulnerability_id, null: false
+ t.bigint :project_id, null: false
+ t.bigint :scanner_id, null: false
+ t.integer :report_type, limit: 2, null: false
+ t.integer :severity, limit: 2, null: false
+ t.integer :state, limit: 2, null: false
+ t.boolean :has_issues, default: false, null: false
+ t.boolean :resolved_on_default_branch, default: false, null: false
+ t.uuid :uuid, null: false
+ t.text :location_image, limit: 2048
+
+ t.index :vulnerability_id, unique: true
+ t.index :scanner_id
+ t.index :uuid, unique: true
+ t.index [:project_id, :state, :severity, :vulnerability_id], name: :index_vuln_reads_on_project_id_state_severity_and_vuln_id, order: { vulnerability_id: :desc }
+ t.index :location_image, where: "report_type IN (2, 7)", name: :index_vulnerability_reads_on_location_image
+ end
+ end
+end
diff --git a/db/migrate/20211118124537_add_foreign_key_to_vulnerability_reads_on_vulnerability.rb b/db/migrate/20211118124537_add_foreign_key_to_vulnerability_reads_on_vulnerability.rb
new file mode 100644
index 00000000000..dd5b0bdc028
--- /dev/null
+++ b/db/migrate/20211118124537_add_foreign_key_to_vulnerability_reads_on_vulnerability.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddForeignKeyToVulnerabilityReadsOnVulnerability < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :vulnerability_reads, :vulnerabilities, column: :vulnerability_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :vulnerability_reads, column: :vulnerability_id
+ end
+ end
+end
diff --git a/db/migrate/20211118124628_add_foreign_key_to_vulnerability_reads_on_project.rb b/db/migrate/20211118124628_add_foreign_key_to_vulnerability_reads_on_project.rb
new file mode 100644
index 00000000000..14dde371e3d
--- /dev/null
+++ b/db/migrate/20211118124628_add_foreign_key_to_vulnerability_reads_on_project.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddForeignKeyToVulnerabilityReadsOnProject < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :vulnerability_reads, :projects, column: :project_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :vulnerability_reads, column: :project_id
+ end
+ end
+end
diff --git a/db/migrate/20211118124650_add_foreign_key_to_vulnerability_reads_on_scanner.rb b/db/migrate/20211118124650_add_foreign_key_to_vulnerability_reads_on_scanner.rb
new file mode 100644
index 00000000000..923e62a4beb
--- /dev/null
+++ b/db/migrate/20211118124650_add_foreign_key_to_vulnerability_reads_on_scanner.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddForeignKeyToVulnerabilityReadsOnScanner < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :vulnerability_reads, :vulnerability_scanners, column: :scanner_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :vulnerability_reads, column: :scanner_id
+ end
+ end
+end
diff --git a/db/schema_migrations/20211117174209 b/db/schema_migrations/20211117174209
new file mode 100644
index 00000000000..f5e8cd180c2
--- /dev/null
+++ b/db/schema_migrations/20211117174209
@@ -0,0 +1 @@
+c7c29b136fbe00271807fcd3133baf7a6e9ded40989fc274e941fc99f2c19e4d \ No newline at end of file
diff --git a/db/schema_migrations/20211118124537 b/db/schema_migrations/20211118124537
new file mode 100644
index 00000000000..537c3aa88fa
--- /dev/null
+++ b/db/schema_migrations/20211118124537
@@ -0,0 +1 @@
+d9a0886d95cd54add9e63475a2f1ca0601304bb64ffe6e6d9e62cb8997d5fe40 \ No newline at end of file
diff --git a/db/schema_migrations/20211118124628 b/db/schema_migrations/20211118124628
new file mode 100644
index 00000000000..e6364327ca4
--- /dev/null
+++ b/db/schema_migrations/20211118124628
@@ -0,0 +1 @@
+f25ee0df287f1c44740be143831537bf262d09d7068ceca1c516ee964bc3aa24 \ No newline at end of file
diff --git a/db/schema_migrations/20211118124650 b/db/schema_migrations/20211118124650
new file mode 100644
index 00000000000..739cfeb80b3
--- /dev/null
+++ b/db/schema_migrations/20211118124650
@@ -0,0 +1 @@
+e032fd334d175d803b943c6328048705e81bd70af6ac226a032281304840f1cd \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 79935490be0..c431c35af89 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20843,6 +20843,30 @@ CREATE SEQUENCE vulnerability_occurrences_id_seq
ALTER SEQUENCE vulnerability_occurrences_id_seq OWNED BY vulnerability_occurrences.id;
+CREATE TABLE vulnerability_reads (
+ id bigint NOT NULL,
+ vulnerability_id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ scanner_id bigint NOT NULL,
+ report_type smallint NOT NULL,
+ severity smallint NOT NULL,
+ state smallint NOT NULL,
+ has_issues boolean DEFAULT false NOT NULL,
+ resolved_on_default_branch boolean DEFAULT false NOT NULL,
+ uuid uuid NOT NULL,
+ location_image text,
+ CONSTRAINT check_380451bdbe CHECK ((char_length(location_image) <= 2048))
+);
+
+CREATE SEQUENCE vulnerability_reads_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE vulnerability_reads_id_seq OWNED BY vulnerability_reads.id;
+
CREATE TABLE vulnerability_remediations (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -22093,6 +22117,8 @@ ALTER TABLE ONLY vulnerability_occurrence_pipelines ALTER COLUMN id SET DEFAULT
ALTER TABLE ONLY vulnerability_occurrences ALTER COLUMN id SET DEFAULT nextval('vulnerability_occurrences_id_seq'::regclass);
+ALTER TABLE ONLY vulnerability_reads ALTER COLUMN id SET DEFAULT nextval('vulnerability_reads_id_seq'::regclass);
+
ALTER TABLE ONLY vulnerability_remediations ALTER COLUMN id SET DEFAULT nextval('vulnerability_remediations_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_scanners ALTER COLUMN id SET DEFAULT nextval('vulnerability_scanners_id_seq'::regclass);
@@ -24118,6 +24144,9 @@ ALTER TABLE ONLY vulnerability_occurrence_pipelines
ALTER TABLE ONLY vulnerability_occurrences
ADD CONSTRAINT vulnerability_occurrences_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY vulnerability_reads
+ ADD CONSTRAINT vulnerability_reads_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY vulnerability_remediations
ADD CONSTRAINT vulnerability_remediations_pkey PRIMARY KEY (id);
@@ -27667,6 +27696,8 @@ COMMENT ON INDEX index_verification_codes_on_phone_and_visitor_id_code IS 'JiHu-
CREATE UNIQUE INDEX index_vuln_historical_statistics_on_project_id_and_date ON vulnerability_historical_statistics USING btree (project_id, date);
+CREATE INDEX index_vuln_reads_on_project_id_state_severity_and_vuln_id ON vulnerability_reads USING btree (project_id, state, severity, vulnerability_id DESC);
+
CREATE INDEX index_vulnerabilities_on_author_id ON vulnerabilities USING btree (author_id);
CREATE INDEX index_vulnerabilities_on_confirmed_by_id ON vulnerabilities USING btree (confirmed_by_id);
@@ -27761,6 +27792,14 @@ CREATE UNIQUE INDEX index_vulnerability_occurrences_on_uuid ON vulnerability_occ
CREATE INDEX index_vulnerability_occurrences_on_vulnerability_id ON vulnerability_occurrences USING btree (vulnerability_id);
+CREATE INDEX index_vulnerability_reads_on_location_image ON vulnerability_reads USING btree (location_image) WHERE (report_type = ANY (ARRAY[2, 7]));
+
+CREATE INDEX index_vulnerability_reads_on_scanner_id ON vulnerability_reads USING btree (scanner_id);
+
+CREATE UNIQUE INDEX index_vulnerability_reads_on_uuid ON vulnerability_reads USING btree (uuid);
+
+CREATE UNIQUE INDEX index_vulnerability_reads_on_vulnerability_id ON vulnerability_reads USING btree (vulnerability_id);
+
CREATE UNIQUE INDEX index_vulnerability_remediations_on_project_id_and_checksum ON vulnerability_remediations USING btree (project_id, checksum);
CREATE UNIQUE INDEX index_vulnerability_scanners_on_project_id_and_external_id ON vulnerability_scanners USING btree (project_id, external_id);
@@ -29089,6 +29128,9 @@ ALTER TABLE ONLY releases
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_4a99ebfd60 FOREIGN KEY (repositories_changed_event_id) REFERENCES geo_repositories_changed_events(id) ON DELETE CASCADE;
+ALTER TABLE ONLY vulnerability_reads
+ ADD CONSTRAINT fk_5001652292 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY alert_management_alerts
ADD CONSTRAINT fk_51ab4b6089 FOREIGN KEY (prometheus_alert_id) REFERENCES prometheus_alerts(id) ON DELETE CASCADE;
@@ -29137,6 +29179,9 @@ ALTER TABLE ONLY dast_profile_schedules
ALTER TABLE ONLY events
ADD CONSTRAINT fk_61fbf6ca48 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY vulnerability_reads
+ ADD CONSTRAINT fk_62736f638f FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_641731faff FOREIGN KEY (updated_by_id) REFERENCES users(id) ON DELETE SET NULL;
@@ -29428,6 +29473,9 @@ ALTER TABLE ONLY vulnerabilities
ALTER TABLE ONLY project_access_tokens
ADD CONSTRAINT fk_b27801bfbf FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY vulnerability_reads
+ ADD CONSTRAINT fk_b28c28abf1 FOREIGN KEY (scanner_id) REFERENCES vulnerability_scanners(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_b37be69be6 FOREIGN KEY (work_item_type_id) REFERENCES work_item_types(id);
diff --git a/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md b/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md
index 57d64a2323e..d21918f4cfe 100644
--- a/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md
+++ b/doc/administration/troubleshooting/navigating_gitlab_via_rails_console.md
@@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
At the heart of GitLab is a web application [built using the Ruby on Rails
framework](https://about.gitlab.com/blog/2018/10/29/why-we-use-rails-to-build-gitlab/).
Thanks to this, we also get access to the amazing tools built right into Rails.
-In this guide, we'll introduce the [Rails console](../operations/rails_console.md#starting-a-rails-console-session)
+In this guide, we are introducing the [Rails console](../operations/rails_console.md#starting-a-rails-console-session)
and the basics of interacting with your GitLab instance from the command line.
WARNING:
@@ -19,7 +19,7 @@ or destroying production data. If you would like to explore the Rails console
with no consequences, you are strongly advised to do so in a test environment.
This guide is targeted at GitLab system administrators who are troubleshooting
-a problem or need to retrieve some data that can only be done through direct
+a problem or must retrieve some data that can only be done through direct
access of the GitLab application. Basic knowledge of Ruby is needed (try [this
30-minute tutorial](https://try.ruby-lang.org/) for a quick introduction).
Rails experience is helpful to have but not a must.
@@ -29,7 +29,7 @@ Rails experience is helpful to have but not a must.
Your type of GitLab installation determines how
[to start a rails console](../operations/rails_console.md).
-The following code examples will all take place inside the Rails console and also
+The following code examples take place inside the Rails console and also
assume an Omnibus GitLab installation.
## Active Record objects
@@ -37,7 +37,7 @@ assume an Omnibus GitLab installation.
### Looking up database-persisted objects
Under the hood, Rails uses [Active Record](https://guides.rubyonrails.org/active_record_basics.html),
-an object-relational mapping system, to read, write and map application objects
+an object-relational mapping system, to read, write, and map application objects
to the PostgreSQL database. These mappings are handled by Active Record models,
which are Ruby classes defined in a Rails app. For GitLab, the model classes
can be found at `/opt/gitlab/embedded/service/gitlab-rails/app/models`.
@@ -144,7 +144,7 @@ NoMethodError (undefined method `username' for #<ActiveRecord::Relation [#<User
Did you mean? by_username
```
-We need to retrieve the single object from the collection by using the `.first`
+Let's retrieve the single object from the collection by using the `.first`
method to get the first item in the collection:
```ruby
@@ -164,7 +164,7 @@ Record, please see the [Active Record Query Interface documentation](https://gui
### Modifying Active Record objects
In the previous section, we learned about retrieving database records using
-Active Record. Now, we'll learn how to write changes to the database.
+Active Record. Now, let's learn how to write changes to the database.
First, let's retrieve the `root` user:
@@ -195,7 +195,7 @@ a background job to deliver an email notification. This is an example of an
-- code which is designated to run in response to events in the Active Record
object life cycle. This is also why using the Rails console is preferred when
direct changes to data is necessary as changes made via direct database queries
-will not trigger these callbacks.
+does not trigger these callbacks.
It's also possible to update attributes in a single line:
@@ -265,8 +265,8 @@ user.save!(validate: false)
This is not recommended, as validations are usually put in place to ensure the
integrity and consistency of user-provided data.
-A validation error will prevent the entire object from being saved to
-the database. We'll see a little of this in the next section. If you're getting
+A validation error prevents the entire object from being saved to
+the database. You can see a little of this in the section below. If you're getting
a mysterious red banner in the GitLab UI when submitting a form, this can often
be the fastest way to get to the root of the problem.
@@ -336,7 +336,7 @@ user.activate
user.state
```
-Earlier, we mentioned that a validation error will prevent the entire object
+Earlier, we mentioned that a validation error prevents the entire object
from being saved to the database. Let's see how this can have unexpected
interactions:
@@ -455,7 +455,7 @@ Ci::Build.find(66124)
```
The pipeline and job ID numbers increment globally across your GitLab
-instance, so there's no need to use an internal ID attribute to look them up,
+instance, so there's no requirement to use an internal ID attribute to look them up,
unlike with issues or merge requests.
**Get the current application settings object:**
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 9a17ac4c813..ed893a4aee0 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -698,7 +698,10 @@ aware of the support.
The documentation will mention that the old Global ID style is now deprecated.
-See also [Aliasing and deprecating mutations](#aliasing-and-deprecating-mutations).
+See also:
+
+- [Aliasing and deprecating mutations](#aliasing-and-deprecating-mutations).
+- [How to filter Kibana for queries that used deprecated fields](graphql_guide/monitoring.md#queries-that-used-a-deprecated-field).
## Enums
@@ -2027,3 +2030,7 @@ elimination of laziness, where needed.
For dealing with lazy values without forcing them, use
`Gitlab::Graphql::Lazy.with_value`.
+
+## Monitoring GraphQL
+
+See the [Monitoring GraphQL](graphql_guide/monitoring.md) guide for tips on how to inspect logs of GraphQL requests and monitor the performance of your GraphQL queries.
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 745e8f456ec..255fb3dc786 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -113,8 +113,9 @@ The more we reflexively add information to the documentation, the more
the documentation helps others efficiently accomplish tasks and solve problems.
If you have questions when considering, authoring, or editing documentation, ask
-the Technical Writing team. They're available on Slack in `#docs` or in GitLab by mentioning the
-writer for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+the Technical Writing team. They're available on Slack in `#docs` or in GitLab by
+mentioning [the writer for](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments)
+the applicable [DevOps stage or group](https://about.gitlab.com/handbook/product/categories/#devops-stages).
Otherwise, forge ahead with your best effort. It does not need to be perfect;
the team is happy to review and improve upon your content. Review the
[Documentation guidelines](index.md) before you begin your first documentation MR.
@@ -787,8 +788,8 @@ This is overridden by the [documentation-specific punctuation rules](#punctuatio
- When possible, avoid including words that might change in the future. Changing
a heading changes its anchor URL, which affects other linked pages.
- When introducing a new document, be careful for the headings to be
- grammatically and syntactically correct. Mention an [assigned technical writer (TW)](https://about.gitlab.com/handbook/product/categories/)
- for review.
+ grammatically and syntactically correct. Mention an [assigned technical writer (TW)](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments)
+ for review, based upon the [product category](https://about.gitlab.com/handbook/product/categories/).
This is to ensure that no document with wrong heading is going live without an
audit, thus preventing dead links and redirection issues when corrected.
- Leave exactly one blank line before and after a heading.
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index fad093c48e9..a044e7a3177 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -703,6 +703,14 @@ Do not use **type** if you can avoid it. Use **enter** instead.
Do not use **useful**. If the user doesn't find the process to be useful, we lose their trust. ([Vale](../testing.md#vale) rule: [`Simplicity.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/Simplicity.yml))
+## user, users
+
+When possible, address the reader directly, instead of calling them **users**.
+Use the [second person](#you-your-yours), **you**, instead.
+
+- Do: You can configure a pipeline.
+- Do not: Users can configure a pipeline.
+
## utilize
Do not use **utilize**. Use **use** instead. It's more succinct and easier for non-native English speakers to understand.
@@ -729,5 +737,13 @@ One exception: You can use **we recommend** instead of **it is recommended** or
Do not use **whitelist**. Another option is **allowlist**. ([Vale](../testing.md#vale) rule: [`InclusionCultural.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionCultural.yml))
+## you, your, yours
+
+Use **you**, **your**, and **yours** instead of [**the user** and **the user's**](#user-users).
+Documentation should be from the [point of view](https://design.gitlab.com/content/voice-tone#point-of-view) of the reader.
+
+- Do: You can configure a pipeline.
+- Do not: Users can configure a pipeline.
+
<!-- vale on -->
<!-- markdownlint-enable -->
diff --git a/doc/development/graphql_guide/index.md b/doc/development/graphql_guide/index.md
index cc97e41df05..412825e06d3 100644
--- a/doc/development/graphql_guide/index.md
+++ b/doc/development/graphql_guide/index.md
@@ -21,3 +21,4 @@ feedback, and suggestions.
- [GraphQL BatchLoader](batchloader.md): development documentation on the BatchLoader.
- [GraphQL pagination](pagination.md): development documentation on pagination.
- [GraphQL Pro](graphql_pro.md): information on our GraphQL Pro subscription.
+- [GraphQL monitoring](monitoring.md): tips on how to use our monitoring tools to inspect GraphQL queries.
diff --git a/doc/development/graphql_guide/monitoring.md b/doc/development/graphql_guide/monitoring.md
new file mode 100644
index 00000000000..28d1a4a9046
--- /dev/null
+++ b/doc/development/graphql_guide/monitoring.md
@@ -0,0 +1,89 @@
+---
+stage: Ecosystem
+group: Integrations
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Monitoring GraphQL
+
+This page gives tips on how to analyze GraphQL data in our monitoring tools.
+Please contribute your own tips to improve this document.
+
+## Kibana
+
+We use Kibana to filter GraphQL query logs. Sign in to [Kibana](https://log.gprd.gitlab.net/)
+with a `@gitlab.com` email address.
+
+In Kibana we can inspect two kinds of GraphQL logs:
+
+- Logs of each GraphQL query executed within the request.
+- Logs of the full request, which due to [query multiplexing](https://graphql-ruby.org/queries/multiplex.html)
+ may have executed multiple queries.
+
+### Logs of each GraphQL query
+
+In a [multiplex query](https://graphql-ruby.org/queries/multiplex.html), each individual query
+is logged separately. We can use subcomponent filtering to inspect these logs.
+[Visit Kibana with this filter enabled](https://log.gprd.gitlab.net/goto/a0da8c9a1e9c1f533a058b7d29d13956)
+or set up the subcomponent filter using these steps:
+
+1. Add a filter:
+ 1. Filter: `json.subcomponent`
+ 1. Operator: `is`
+ 1. Value: `graphql_json`
+1. Select **Refresh**.
+
+You can select Kibana fields from the **Available fields** section of the sidebar to
+add columns to the log table, or [visit this view](https://log.gprd.gitlab.net/goto/5826d3d3affb41cac52e637ffc205905),
+which already has a set of Kibana fields selected. Some relevant Kibana fields include:
+
+| Kibana field | Description |
+| --- | --- |
+| `json.operation_name` | The [operation name](https://graphql.org/learn/queries/#operation-name) used by the client. |
+| `json.operation_fingerprint`| The [fingerprint](https://graphql-ruby.org/api-doc/1.12.20/GraphQL/Query#fingerprint-instance_method) of the query, used to recognize repeated queries over time. |
+| `json.meta.caller_id` | Appears as `graphql:<operation_name>` for queries that came from the GitLab frontend, otherwise as `graphql:unknown`. Can be used to identify internal versus external queries. |
+| `json.query_string` | The query string itself. |
+| `json.is_mutation` | `true` when a mutation, `false` when not. |
+| `json.query_analysis.used_fields` | List of GraphQL fields selected by the query. |
+| `json.query_analysis.used_deprecated_fields` | List of deprecated GraphQL fields selected by the query. |
+| `json.query_analysis.duration_s` | Duration of query execution in seconds. |
+| `json.query_analysis.complexity` | The [complexity](../api_graphql_styleguide.md#max-complexity) score of the query. |
+
+#### Useful filters
+
+Combine the [subcomponent filter](#logs-of-each-graphql-query) with the following Kibana filters to further interrogate the query logs.
+
+##### Queries that used a particular field
+
+Filter logs by queries that used a particular field:
+
+1. Add a filter:
+ 1. Filter: `json.query_analysis.used_fields`
+ 1. Operator: `is`
+ 1. Value: `Type.myField`, where `Type.myField` is the type name and field name as it
+ appears in [our GraphQL reference documentation](../../api/graphql/reference/index.md).
+1. Select **Refresh**.
+
+##### Queries that used a deprecated field
+
+Filter logs of queries that used a particular deprecated field by following the
+[steps above](#queries-that-used-a-particular-field) but use the `json.graphql.used_deprecated_fields`
+filter instead.
+
+### Logs of the full request
+
+The full request logs encompass log data for all [multiplexed queries](https://graphql-ruby.org/queries/multiplex.html)
+in the request, as well as data from time spent outside of `GraphQLController#execute`.
+
+To see the full request logs, do **not** apply the `json.subcomponent` [filter](#logs-of-each-graphql-query), and instead:
+
+1. Add a filter:
+ 1. Filter: `json.meta.caller_id`
+ 1. Operator: `is`
+ 1. Value: `GraphqlController#execute`
+1. Select **Refresh**.
+
+Some differences from the [query logs](#logs-of-each-graphql-query) described above:
+
+- Some of the [Kibana fields mentioned above](#logs-of-each-graphql-query) are not available to the full request logs.
+- The names of filters differ. For example, instead of `json.query_analysis.used_fields` you select `json.graphql.used_fields`.
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 1435fe86ad1..35d5d56761d 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -199,6 +199,7 @@ The following data is included in the export:
- Type
- Path
- Access level ([Project](../permissions.md#project-members-permissions) and [Group](../permissions.md#group-members-permissions))
+- Date of last activity ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345388) in GitLab 14.6). For a list of activities that populate this column, see the [Users API documentation](../../api/users.md#get-user-activities-admin-only).
![user permission export button](img/export_permissions_v13_11.png)
diff --git a/doc/user/profile/active_sessions.md b/doc/user/profile/active_sessions.md
index 8bcb9a8845d..b30ee002758 100644
--- a/doc/user/profile/active_sessions.md
+++ b/doc/user/profile/active_sessions.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# Active sessions
+# Active sessions **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17867) in GitLab 10.8.
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index e079e6dcbee..63be88f90d6 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: concepts, howto
---
-# Profile preferences
+# Profile preferences **(FREE)**
A user's profile preferences page allows the user to customize various aspects
of GitLab to their liking.
@@ -120,7 +120,7 @@ You can include the following options for your default dashboard view:
- Your [To-Do List](../todos.md)
- Assigned Issues
- Assigned Merge Requests
-- Operations Dashboard **(PREMIUM)**
+- [Operations Dashboard](../operations_dashboard/index.md)
### Group overview content
@@ -130,7 +130,7 @@ displayed on a group's home page.
You can choose between 2 options:
- Details (default)
-- [Security dashboard](../application_security/security_dashboard/index.md) **(ULTIMATE)**
+- [Security dashboard](../application_security/security_dashboard/index.md)
### Project overview content
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb
index 181ee20948b..bf8f9f4dcc7 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb
@@ -15,7 +15,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def seconds
- @query = @query.select(median_duration_in_seconds.as('median')).reorder(nil)
+ @query = @query.select(duration_in_seconds(percentile_cont).as('median')).reorder(nil)
result = @query.take || {}
result['median'] || nil
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
index 7dce757cdc8..1742d396c10 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
@@ -36,7 +36,7 @@ module Gitlab
def serialized_records
strong_memoize(:serialized_records) do
- records = ordered_and_limited_query.select(stage_event_model.arel_table[Arel.star], duration.as('total_time'))
+ records = ordered_and_limited_query.select(stage_event_model.arel_table[Arel.star], duration_in_seconds.as('total_time'))
yield records if block_given?
issuables_and_records = load_issuables(records)
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb
index f23d1832df9..b00925495f2 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb
@@ -27,13 +27,13 @@ module Gitlab
end
end
- def median_duration_in_seconds
- Arel::Nodes::Extract.new(percentile_cont, :epoch)
- end
-
def in_progress?
params[:end_event_filter] == :in_progress
end
+
+ def duration_in_seconds(duration_expression = duration)
+ Arel::Nodes::Extract.new(duration_expression, :epoch)
+ end
end
end
end
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index 30802f7a153..ee4e5dc0b1c 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -531,6 +531,7 @@ vulnerability_issue_links: :gitlab_main
vulnerability_occurrence_identifiers: :gitlab_main
vulnerability_occurrence_pipelines: :gitlab_main
vulnerability_occurrences: :gitlab_main
+vulnerability_reads: :gitlab_main
vulnerability_remediations: :gitlab_main
vulnerability_scanners: :gitlab_main
vulnerability_statistics: :gitlab_main
diff --git a/lib/gitlab/redis/multi_store.rb b/lib/gitlab/redis/multi_store.rb
index 6616f0d4cec..500b62bf0e8 100644
--- a/lib/gitlab/redis/multi_store.rb
+++ b/lib/gitlab/redis/multi_store.rb
@@ -97,11 +97,15 @@ module Gitlab
end
def use_primary_and_secondary_stores?
- Feature.enabled?("use_primary_and_secondary_stores_for_#{instance_name.underscore}", default_enabled: :yaml) && !same_redis_store?
+ feature_flags_available? &&
+ Feature.enabled?("use_primary_and_secondary_stores_for_#{instance_name.underscore}", default_enabled: :yaml) &&
+ !same_redis_store?
end
def use_primary_store_as_default?
- Feature.enabled?("use_primary_store_as_default_for_#{instance_name.underscore}", default_enabled: :yaml) && !same_redis_store?
+ feature_flags_available? &&
+ Feature.enabled?("use_primary_store_as_default_for_#{instance_name.underscore}", default_enabled: :yaml) &&
+ !same_redis_store?
end
private
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9b5b254b522..218d2d41aa9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7368,10 +7368,10 @@ msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
-msgid "ClusterAgents|%{number} of %{total} Agent based integrations"
+msgid "ClusterAgents|%{number} of %{total} agents"
msgstr ""
-msgid "ClusterAgents|%{number} of %{total} Certificate based integrations"
+msgid "ClusterAgents|%{number} of %{total} clusters connected through cluster certificates"
msgstr ""
msgid "ClusterAgents|Access tokens"
@@ -7380,6 +7380,9 @@ msgstr ""
msgid "ClusterAgents|Actions"
msgstr ""
+msgid "ClusterAgents|Advanced installation methods"
+msgstr ""
+
msgid "ClusterAgents|Agent"
msgstr ""
@@ -7392,9 +7395,6 @@ msgstr ""
msgid "ClusterAgents|All"
msgstr ""
-msgid "ClusterAgents|Alternative installation methods"
-msgstr ""
-
msgid "ClusterAgents|An error occurred while loading your GitLab Agents"
msgstr ""
@@ -7404,22 +7404,28 @@ msgstr ""
msgid "ClusterAgents|An unknown error occurred. Please try again."
msgstr ""
-msgid "ClusterAgents|Certificate based"
+msgid "ClusterAgents|Certificate"
msgstr ""
msgid "ClusterAgents|Configuration"
msgstr ""
+msgid "ClusterAgents|Connect a cluster through the Agent"
+msgstr ""
+
msgid "ClusterAgents|Connect existing cluster"
msgstr ""
-msgid "ClusterAgents|Connect with Agent"
+msgid "ClusterAgents|Connect with a certificate"
+msgstr ""
+
+msgid "ClusterAgents|Connect with the Agent"
msgstr ""
-msgid "ClusterAgents|Connect with a GitLab Agent"
+msgid "ClusterAgents|Connect with the GitLab Agent"
msgstr ""
-msgid "ClusterAgents|Connect with certificate"
+msgid "ClusterAgents|Connect your cluster through the Agent"
msgstr ""
msgid "ClusterAgents|Connected"
@@ -7431,7 +7437,7 @@ msgstr ""
msgid "ClusterAgents|Copy token"
msgstr ""
-msgid "ClusterAgents|Create new cluster"
+msgid "ClusterAgents|Create a new cluster"
msgstr ""
msgid "ClusterAgents|Created by"
@@ -7443,28 +7449,31 @@ msgstr ""
msgid "ClusterAgents|Date created"
msgstr ""
+msgid "ClusterAgents|Deprecated"
+msgstr ""
+
msgid "ClusterAgents|Description"
msgstr ""
-msgid "ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}."
+msgid "ClusterAgents|Failed to register an agent"
msgstr ""
-msgid "ClusterAgents|GitLab Agents"
+msgid "ClusterAgents|For the advanced installation method %{linkStart}see the documentation%{linkEnd}."
msgstr ""
-msgid "ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}"
+msgid "ClusterAgents|GitLab Agent"
msgstr ""
-msgid "ClusterAgents|GitLab Kubernetes Agent"
+msgid "ClusterAgents|GitLab Agent for Kubernetes"
msgstr ""
-msgid "ClusterAgents|Go to the repository"
+msgid "ClusterAgents|Go to the repository files"
msgstr ""
-msgid "ClusterAgents|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}"
+msgid "ClusterAgents|How to register an agent?"
msgstr ""
-msgid "ClusterAgents|Install new Agent"
+msgid "ClusterAgents|Install a new agent"
msgstr ""
msgid "ClusterAgents|Last connected %{timeAgo}."
@@ -7479,15 +7488,6 @@ msgstr ""
msgid "ClusterAgents|Learn how to troubleshoot"
msgstr ""
-msgid "ClusterAgents|Learn more about installing a GitLab Kubernetes Agent"
-msgstr ""
-
-msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent registration."
-msgstr ""
-
-msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent."
-msgstr ""
-
msgid "ClusterAgents|Make sure you are using a valid token."
msgstr ""
@@ -7500,10 +7500,10 @@ msgstr ""
msgid "ClusterAgents|Never connected"
msgstr ""
-msgid "ClusterAgents|No Agent based integrations"
+msgid "ClusterAgents|No agents"
msgstr ""
-msgid "ClusterAgents|No Certificate based integrations"
+msgid "ClusterAgents|No clusters connected through cluster certificates"
msgstr ""
msgid "ClusterAgents|Not connected"
@@ -7515,7 +7515,10 @@ msgstr ""
msgid "ClusterAgents|Recommended installation method"
msgstr ""
-msgid "ClusterAgents|Register Agent"
+msgid "ClusterAgents|Register"
+msgstr ""
+
+msgid "ClusterAgents|Register an agent to generate a token that will be used to install the agent on your cluster in the next step."
msgstr ""
msgid "ClusterAgents|Registering Agent"
@@ -7527,51 +7530,48 @@ msgstr ""
msgid "ClusterAgents|Security"
msgstr ""
-msgid "ClusterAgents|Select an Agent"
-msgstr ""
-
-msgid "ClusterAgents|Select an Agent to register with GitLab and install on your cluster."
-msgstr ""
-
-msgid "ClusterAgents|Select which Agent you want to install"
+msgid "ClusterAgents|Select an agent"
msgstr ""
-msgid "ClusterAgents|The Agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}."
+msgid "ClusterAgents|Select an agent to register with GitLab"
msgstr ""
-msgid "ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}"
+msgid "ClusterAgents|The GitLab Agent provides an increased level of security when connecting Kubernetes clusters to GitLab. %{linkStart}Learn more about the GitLab Agent.%{linkEnd}"
msgstr ""
-msgid "ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window."
+msgid "ClusterAgents|The agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}."
msgstr ""
-msgid "ClusterAgents|The registration token will be used to connect the Agent on your cluster to GitLab. To learn more about the registration tokens and how they are used %{linkStart}go to the documentation%{linkEnd}."
+msgid "ClusterAgents|The recommended installation method includes the token. If you want to follow the advanced installation method provided in the docs, make sure you save the token value before you close this window."
msgstr ""
-msgid "ClusterAgents|The token value will not be shown again after you close this window."
+msgid "ClusterAgents|The registration token will be used to connect the agent on your cluster to GitLab. %{linkStart}What are registration tokens?%{linkEnd}"
msgstr ""
msgid "ClusterAgents|This agent has no tokens"
msgstr ""
-msgid "ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process."
+msgid "ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}What's the agent's configuration file?%{linkEnd}"
msgstr ""
msgid "ClusterAgents|Unknown user"
msgstr ""
-msgid "ClusterAgents|Use GitLab Agents to more securely integrate with your clusters to deploy your applications, run your pipelines, use review apps and much more."
+msgid "ClusterAgents|View all %{number} agents"
msgstr ""
-msgid "ClusterAgents|View all %{number} Agent based integrations"
+msgid "ClusterAgents|View all %{number} clusters"
msgstr ""
-msgid "ClusterAgents|View all %{number} Certificate based integrations"
+msgid "ClusterAgents|You cannot see this token again after you close this window."
msgstr ""
msgid "ClusterAgents|You will need to create a token to connect to your agent"
msgstr ""
+msgid "ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it."
+msgstr ""
+
msgid "ClusterAgent|User has insufficient permissions to create a token for this project"
msgstr ""
@@ -7722,6 +7722,9 @@ msgstr ""
msgid "ClusterIntegration|Connect with a certificate"
msgstr ""
+msgid "ClusterIntegration|Connect your cluster to GitLab through %{linkStart}cluster certificates%{linkEnd}."
+msgstr ""
+
msgid "ClusterIntegration|Connection Error"
msgstr ""
@@ -7881,9 +7884,6 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}"
-msgstr ""
-
msgid "ClusterIntegration|If you do not wish to delete all associated GitLab resources, you can simply remove the integration."
msgstr ""
@@ -7938,9 +7938,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Learn more about the GitLab managed clusters"
-msgstr ""
-
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
@@ -8220,6 +8217,9 @@ msgstr ""
msgid "ClusterIntegration|The URL used to access the Kubernetes API."
msgstr ""
+msgid "ClusterIntegration|The certificate-based method to connect clusters to GitLab was %{linkStart}deprecated%{linkEnd} in GitLab 14.5."
+msgstr ""
+
msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals."
msgstr ""
@@ -8274,7 +8274,7 @@ msgstr ""
msgid "ClusterIntegration|Unknown Error"
msgstr ""
-msgid "ClusterIntegration|Use certificates to integrate with your clusters to deploy your applications, run your pipelines, use review apps and much more in an easy way."
+msgid "ClusterIntegration|Use the %{linkStart}GitLab Agent%{linkEnd} to safely connect your Kubernetes clusters to GitLab. You can deploy your applications, run your pipelines, use Review Apps, and much more."
msgstr ""
msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
@@ -14456,9 +14456,6 @@ msgstr ""
msgid "Failed to publish issue on status page."
msgstr ""
-msgid "Failed to register Agent"
-msgstr ""
-
msgid "Failed to remove a Zoom meeting"
msgstr ""
@@ -23050,9 +23047,6 @@ msgstr ""
msgid "NetworkPolicies|None selected"
msgstr ""
-msgid "NetworkPolicies|Please %{installLinkStart}install%{installLinkEnd} and %{configureLinkStart}configure a Kubernetes Agent for this project%{configureLinkEnd} to enable alerts."
-msgstr ""
-
msgid "NetworkPolicies|Policy %{policyName} was successfully changed"
msgstr ""
@@ -23077,6 +23071,9 @@ msgstr ""
msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
msgstr ""
+msgid "NetworkPolicies|To enable alerts, %{installLinkStart}install an agent%{installLinkEnd} first."
+msgstr ""
+
msgid "NetworkPolicies|Traffic that does not match any rule will be blocked."
msgstr ""
@@ -24414,7 +24411,7 @@ msgstr ""
msgid "Open Selection"
msgstr ""
-msgid "Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command."
+msgid "Open a CLI and connect to the cluster you want to install the agent in. Use this installation method to minimize any manual steps. The token is already included in the command."
msgstr ""
msgid "Open epics"
diff --git a/spec/features/clusters/create_agent_spec.rb b/spec/features/clusters/create_agent_spec.rb
index 906f0d1b437..7ed31a8c549 100644
--- a/spec/features/clusters/create_agent_spec.rb
+++ b/spec/features/clusters/create_agent_spec.rb
@@ -25,13 +25,13 @@ RSpec.describe 'Cluster agent registration', :js do
it 'allows the user to select an agent to install, and displays the resulting agent token' do
click_button('Actions')
- expect(page).to have_content('Register Agent')
+ expect(page).to have_content('Register')
- click_button('Select an Agent')
+ click_button('Select an agent')
click_button('example-agent-2')
- click_button('Register Agent')
+ click_button('Register')
- expect(page).to have_content('The token value will not be shown again after you close this window.')
+ expect(page).to have_content('You cannot see this token again after you close this window.')
expect(page).to have_content('example-agent-token')
expect(page).to have_content('docker run --pull=always --rm')
diff --git a/spec/features/projects/cluster_agents_spec.rb b/spec/features/projects/cluster_agents_spec.rb
index 3ef710169f0..6a904b6f0e0 100644
--- a/spec/features/projects/cluster_agents_spec.rb
+++ b/spec/features/projects/cluster_agents_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'ClusterAgents', :js do
end
it 'displays empty state', :aggregate_failures do
- expect(page).to have_content('Install new Agent')
+ expect(page).to have_content('Install a new agent')
expect(page).to have_selector('.empty-state')
end
end
diff --git a/spec/features/projects/clusters/eks_spec.rb b/spec/features/projects/clusters/eks_spec.rb
index 09c10c0b3a9..a925e3a72f8 100644
--- a/spec/features/projects/clusters/eks_spec.rb
+++ b/spec/features/projects/clusters/eks_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'AWS EKS Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
click_link 'Connect with a certificate'
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 0da81c475a9..6e88cbf52b5 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'Gcp Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
click_link 'Connect with a certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
@@ -145,9 +145,9 @@ RSpec.describe 'Gcp Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
click_button(class: 'dropdown-toggle-split')
- click_link 'Connect with certificate'
+ click_link 'Connect with a certificate'
end
it 'user sees the "Environment scope" field' do
@@ -161,7 +161,7 @@ RSpec.describe 'Gcp Cluster', :js do
click_button 'Remove integration and resources'
fill_in 'confirm_cluster_name_input', with: cluster.name
click_button 'Remove integration'
- click_link 'Certificate based'
+ click_link 'Certificate'
end
it 'user sees creation form with the successful message' do
@@ -175,7 +175,7 @@ RSpec.describe 'Gcp Cluster', :js do
context 'when user has not dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
end
it 'user sees offer on cluster index page' do
@@ -192,7 +192,7 @@ RSpec.describe 'Gcp Cluster', :js do
context 'when user has dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
end
it 'user does not see offer after dismissing' do
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index d3f709bfb53..d9887ea4fe0 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
click_link 'Connect with a certificate'
click_link 'Connect existing cluster'
end
@@ -113,7 +113,7 @@ RSpec.describe 'User Cluster', :js do
click_button 'Remove integration and resources'
fill_in 'confirm_cluster_name_input', with: cluster.name
click_button 'Remove integration'
- click_link 'Certificate based'
+ click_link 'Certificate'
end
it 'user sees creation form with the successful message' do
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index a49fa4c9e31..6e45529c659 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -10,13 +10,13 @@ RSpec.describe 'Clusters', :js do
before do
project.add_maintainer(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when user does not have a cluster and visits cluster index page' do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
end
it 'sees empty state' do
@@ -34,17 +34,17 @@ RSpec.describe 'Clusters', :js do
before do
create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
click_button(class: 'dropdown-toggle-split')
end
it 'user sees an add cluster button' do
- expect(page).to have_content('Connect with certificate')
+ expect(page).to have_content('Connect with a certificate')
end
context 'when user filled form with environment scope' do
before do
- click_link 'Connect with certificate'
+ click_link 'Connect with a certificate'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: 'staging/*'
click_button 'Add Kubernetes cluster'
@@ -72,7 +72,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
- click_link 'Connect with certificate'
+ click_link 'Connect with a certificate'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: '*'
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0'
@@ -109,13 +109,13 @@ RSpec.describe 'Clusters', :js do
create(:cluster, :provided_by_gcp, name: 'default-cluster', environment_scope: '*', projects: [project])
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
end
context 'when user filled form with environment scope' do
before do
click_button(class: 'dropdown-toggle-split')
- click_link 'Create new cluster'
+ click_link 'Create a new cluster'
click_link 'Google GKE'
sleep 2 # wait for ajax
@@ -160,7 +160,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
click_button(class: 'dropdown-toggle-split')
- click_link 'Create new cluster'
+ click_link 'Create a new cluster'
click_link 'Google GKE'
sleep 2 # wait for ajax
@@ -190,7 +190,7 @@ RSpec.describe 'Clusters', :js do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
end
it 'user sees a table with one cluster' do
@@ -213,7 +213,7 @@ RSpec.describe 'Clusters', :js do
before do
visit project_clusters_path(project)
- click_link 'Certificate based'
+ click_link 'Certificate'
click_link 'Connect with a certificate'
click_link 'Create new cluster'
end
diff --git a/spec/finders/environments/environments_by_deployments_finder_spec.rb b/spec/finders/environments/environments_by_deployments_finder_spec.rb
index 7804ffa4ef1..1b86aced67d 100644
--- a/spec/finders/environments/environments_by_deployments_finder_spec.rb
+++ b/spec/finders/environments/environments_by_deployments_finder_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Environments::EnvironmentsByDeploymentsFinder do
project.add_maintainer(user)
end
- shared_examples 'execute' do
+ describe '#execute' do
context 'tagged deployment' do
let(:environment_two) { create(:environment, project: project) }
# Environments need to include commits, so rewind two commits to fit
@@ -124,16 +124,4 @@ RSpec.describe Environments::EnvironmentsByDeploymentsFinder do
end
end
end
-
- describe "#execute" do
- include_examples 'execute'
-
- context 'when environments_by_deployments_finder_exists_optimization is disabled' do
- before do
- stub_feature_flags(environments_by_deployments_finder_exists_optimization: false)
- end
-
- include_examples 'execute'
- end
- end
end
diff --git a/spec/frontend/clusters_list/components/agent_empty_state_spec.js b/spec/frontend/clusters_list/components/agent_empty_state_spec.js
index f6918311934..ed2a0d0b97b 100644
--- a/spec/frontend/clusters_list/components/agent_empty_state_spec.js
+++ b/spec/frontend/clusters_list/components/agent_empty_state_spec.js
@@ -1,4 +1,4 @@
-import { GlEmptyState, GlSprintf } from '@gitlab/ui';
+import { GlEmptyState, GlSprintf, GlLink, GlButton } from '@gitlab/ui';
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
import { INSTALL_AGENT_MODAL_ID } from '~/clusters_list/constants';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -6,8 +6,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { helpPagePath } from '~/helpers/help_page_helper';
const emptyStateImage = '/path/to/image';
-const multipleClustersDocsUrl = helpPagePath('user/project/clusters/multiple_kubernetes_clusters');
-const installDocsUrl = helpPagePath('administration/clusters/kas');
+const installDocsUrl = helpPagePath('user/clusters/agent/index');
describe('AgentEmptyStateComponent', () => {
let wrapper;
@@ -15,9 +14,8 @@ describe('AgentEmptyStateComponent', () => {
emptyStateImage,
};
- const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link');
- const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link');
- const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button');
+ const findInstallDocsLink = () => wrapper.findComponent(GlLink);
+ const findIntegrationButton = () => wrapper.findComponent(GlButton);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
beforeEach(() => {
@@ -44,8 +42,7 @@ describe('AgentEmptyStateComponent', () => {
expect(findIntegrationButton().exists()).toBe(true);
});
- it('renders correct href attributes for the links', () => {
- expect(findMultipleClustersDocsLink().attributes('href')).toBe(multipleClustersDocsUrl);
+ it('renders correct href attributes for the docs link', () => {
expect(findInstallDocsLink().attributes('href')).toBe(installDocsUrl);
});
diff --git a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js
index f7e1791d0f7..cf0f6881960 100644
--- a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js
@@ -6,35 +6,33 @@ import ClusterStore from '~/clusters_list/store';
const clustersEmptyStateImage = 'path/to/svg';
const newClusterPath = '/path/to/connect/cluster';
const emptyStateHelpText = 'empty state text';
-const canAddCluster = true;
describe('ClustersEmptyStateComponent', () => {
let wrapper;
- const propsData = {
- isChildComponent: false,
- };
-
- const provideData = {
+ const defaultProvideData = {
clustersEmptyStateImage,
- emptyStateHelpText: null,
newClusterPath,
};
- const entryData = {
- canAddCluster,
- };
-
const findButton = () => wrapper.findComponent(GlButton);
const findEmptyStateText = () => wrapper.findByTestId('clusters-empty-state-text');
- beforeEach(() => {
+ const createWrapper = ({
+ provideData = { emptyStateHelpText: null },
+ isChildComponent = false,
+ canAddCluster = true,
+ } = {}) => {
wrapper = shallowMountExtended(ClustersEmptyState, {
- store: ClusterStore(entryData),
- propsData,
- provide: provideData,
+ store: ClusterStore({ canAddCluster }),
+ propsData: { isChildComponent },
+ provide: { ...defaultProvideData, ...provideData },
stubs: { GlEmptyState },
});
+ };
+
+ beforeEach(() => {
+ createWrapper();
});
afterEach(() => {
@@ -55,16 +53,7 @@ describe('ClustersEmptyStateComponent', () => {
describe('when the component is loaded as a child component', () => {
beforeEach(() => {
- propsData.isChildComponent = true;
- wrapper = shallowMountExtended(ClustersEmptyState, {
- store: ClusterStore(entryData),
- propsData,
- provide: provideData,
- });
- });
-
- afterEach(() => {
- propsData.isChildComponent = false;
+ createWrapper({ isChildComponent: true });
});
it('should not render the action button', () => {
@@ -74,12 +63,7 @@ describe('ClustersEmptyStateComponent', () => {
describe('when the help text is provided', () => {
beforeEach(() => {
- provideData.emptyStateHelpText = emptyStateHelpText;
- wrapper = shallowMountExtended(ClustersEmptyState, {
- store: ClusterStore(entryData),
- propsData,
- provide: provideData,
- });
+ createWrapper({ provideData: { emptyStateHelpText } });
});
it('should show the empty state text', () => {
@@ -88,14 +72,8 @@ describe('ClustersEmptyStateComponent', () => {
});
describe('when the user cannot add clusters', () => {
- entryData.canAddCluster = false;
beforeEach(() => {
- wrapper = shallowMountExtended(ClustersEmptyState, {
- store: ClusterStore(entryData),
- propsData,
- provide: provideData,
- stubs: { GlEmptyState },
- });
+ createWrapper({ canAddCluster: false });
});
it('should disable the button', () => {
expect(findButton().props('disabled')).toBe(true);
diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
index 7cf8e6293a1..37665bf7abd 100644
--- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
@@ -59,10 +59,10 @@ describe('ClustersMainViewComponent', () => {
describe('tabs', () => {
it.each`
- tabTitle | queryParamValue | lineNumber
- ${'All'} | ${'all'} | ${0}
- ${'Agent'} | ${AGENT} | ${1}
- ${'Certificate based'} | ${CERTIFICATE_BASED} | ${2}
+ tabTitle | queryParamValue | lineNumber
+ ${'All'} | ${'all'} | ${0}
+ ${'Agent'} | ${AGENT} | ${1}
+ ${'Certificate'} | ${CERTIFICATE_BASED} | ${2}
`(
'renders correct tab title and query param value',
({ tabTitle, queryParamValue, lineNumber }) => {
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index eacf1244d55..d250eb7f6ad 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -219,15 +219,21 @@ describe('IDE services', () => {
describe('getProjectData', () => {
it('combines gql and API requests', () => {
const gqlProjectData = {
+ id: 'gid://gitlab/Project/1',
userPermissions: {
bogus: true,
},
};
+ const expectedResponse = {
+ ...projectData,
+ ...gqlProjectData,
+ id: 1,
+ };
Api.project.mockReturnValue(Promise.resolve({ data: { ...projectData } }));
query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
return services.getProjectData(TEST_NAMESPACE, TEST_PROJECT).then((response) => {
- expect(response).toEqual({ data: { ...projectData, ...gqlProjectData } });
+ expect(response).toEqual({ data: expectedResponse });
expect(Api.project).toHaveBeenCalledWith(TEST_PROJECT_ID);
expect(query).toHaveBeenCalledWith({
query: getIdeProject,
diff --git a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js
index 1b1e2d4df8f..b70fca8e9e6 100644
--- a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js
+++ b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js
@@ -5,7 +5,14 @@ import ActionsCell from '~/jobs/components/table/cells/actions_cell.vue';
import JobPlayMutation from '~/jobs/components/table/graphql/mutations/job_play.mutation.graphql';
import JobRetryMutation from '~/jobs/components/table/graphql/mutations/job_retry.mutation.graphql';
import JobUnscheduleMutation from '~/jobs/components/table/graphql/mutations/job_unschedule.mutation.graphql';
-import { playableJob, retryableJob, scheduledJob } from '../../../mock_data';
+import {
+ playableJob,
+ retryableJob,
+ scheduledJob,
+ cannotRetryJob,
+ cannotPlayJob,
+ cannotPlayScheduledJob,
+} from '../../../mock_data';
describe('Job actions cell', () => {
let wrapper;
@@ -58,6 +65,17 @@ describe('Job actions cell', () => {
});
it.each`
+ button | action | jobType
+ ${findPlayButton} | ${'play'} | ${cannotPlayJob}
+ ${findRetryButton} | ${'retry'} | ${cannotRetryJob}
+ ${findPlayScheduledJobButton} | ${'play scheduled'} | ${cannotPlayScheduledJob}
+ `('does not display the $action button if user cannot update build', ({ button, jobType }) => {
+ createComponent(jobType);
+
+ expect(button().exists()).toBe(false);
+ });
+
+ it.each`
button | action | jobType
${findPlayButton} | ${'play'} | ${playableJob}
${findRetryButton} | ${'retry'} | ${retryableJob}
diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js
index 5654c8e424e..86be739751b 100644
--- a/spec/frontend/jobs/mock_data.js
+++ b/spec/frontend/jobs/mock_data.js
@@ -1563,6 +1563,7 @@ export const mockJobsQueryResponse = {
userPermissions: {
readBuild: true,
readJobArtifacts: true,
+ updateBuild: true,
__typename: 'JobPermissions',
},
__typename: 'CiJob',
@@ -1636,10 +1637,15 @@ export const retryableJob = {
cancelable: false,
active: false,
stuck: false,
- userPermissions: { readBuild: true, __typename: 'JobPermissions' },
+ userPermissions: { readBuild: true, updateBuild: true, __typename: 'JobPermissions' },
__typename: 'CiJob',
};
+export const cannotRetryJob = {
+ ...retryableJob,
+ userPermissions: { readBuild: true, updateBuild: false, __typename: 'JobPermissions' },
+};
+
export const playableJob = {
artifacts: {
nodes: [
@@ -1700,10 +1706,25 @@ export const playableJob = {
cancelable: false,
active: false,
stuck: false,
- userPermissions: { readBuild: true, readJobArtifacts: true, __typename: 'JobPermissions' },
+ userPermissions: {
+ readBuild: true,
+ readJobArtifacts: true,
+ updateBuild: true,
+ __typename: 'JobPermissions',
+ },
__typename: 'CiJob',
};
+export const cannotPlayJob = {
+ ...playableJob,
+ userPermissions: {
+ readBuild: true,
+ readJobArtifacts: true,
+ updateBuild: false,
+ __typename: 'JobPermissions',
+ },
+};
+
export const scheduledJob = {
artifacts: { nodes: [], __typename: 'CiJobArtifactConnection' },
allowFailure: false,
@@ -1756,6 +1777,16 @@ export const scheduledJob = {
cancelable: false,
active: false,
stuck: false,
- userPermissions: { readBuild: true, __typename: 'JobPermissions' },
+ userPermissions: { readBuild: true, updateBuild: true, __typename: 'JobPermissions' },
__typename: 'CiJob',
};
+
+export const cannotPlayScheduledJob = {
+ ...scheduledJob,
+ userPermissions: {
+ readBuild: true,
+ readJobArtifacts: true,
+ updateBuild: false,
+ __typename: 'JobPermissions',
+ },
+};
diff --git a/spec/frontend/projects/new/components/new_project_url_select_spec.js b/spec/frontend/projects/new/components/new_project_url_select_spec.js
index bdce8af613b..258fa7636d4 100644
--- a/spec/frontend/projects/new/components/new_project_url_select_spec.js
+++ b/spec/frontend/projects/new/components/new_project_url_select_spec.js
@@ -5,7 +5,8 @@ import {
GlDropdownSectionHeader,
GlSearchBoxByType,
} from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
@@ -52,8 +53,7 @@ describe('NewProjectUrlSelect component', () => {
},
};
- const localVue = createLocalVue();
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const defaultProvide = {
namespaceFullPath: 'h5bp',
@@ -64,17 +64,19 @@ describe('NewProjectUrlSelect component', () => {
userNamespaceId: '1',
};
+ let mockQueryResponse;
+
const mountComponent = ({
search = '',
queryResponse = data,
provide = defaultProvide,
mountFn = shallowMount,
} = {}) => {
- const requestHandlers = [[searchQuery, jest.fn().mockResolvedValue({ data: queryResponse })]];
+ mockQueryResponse = jest.fn().mockResolvedValue({ data: queryResponse });
+ const requestHandlers = [[searchQuery, mockQueryResponse]];
const apolloProvider = createMockApollo(requestHandlers);
return mountFn(NewProjectUrlSelect, {
- localVue,
apolloProvider,
provide,
data() {
@@ -88,12 +90,19 @@ describe('NewProjectUrlSelect component', () => {
const findButtonLabel = () => wrapper.findComponent(GlButton);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
- const findHiddenInput = () => wrapper.find('input');
+ const findHiddenInput = () => wrapper.find('[name="project[namespace_id]"]');
+
const clickDropdownItem = async () => {
wrapper.findComponent(GlDropdownItem).vm.$emit('click');
await wrapper.vm.$nextTick();
};
+ const showDropdown = async () => {
+ findDropdown().vm.$emit('shown');
+ await wrapper.vm.$apollo.queries.currentUser.refetch();
+ jest.runOnlyPendingTimers();
+ };
+
afterEach(() => {
wrapper.destroy();
});
@@ -141,20 +150,18 @@ describe('NewProjectUrlSelect component', () => {
it('focuses on the input when the dropdown is opened', async () => {
wrapper = mountComponent({ mountFn: mount });
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
const spy = jest.spyOn(findInput().vm, 'focusInput');
- findDropdown().vm.$emit('shown');
+ await showDropdown();
expect(spy).toHaveBeenCalledTimes(1);
});
it('renders expected dropdown items', async () => {
wrapper = mountComponent({ mountFn: mount });
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
+
+ await showDropdown();
const listItems = wrapper.findAll('li');
@@ -167,15 +174,36 @@ describe('NewProjectUrlSelect component', () => {
expect(listItems.at(5).text()).toBe(data.currentUser.namespace.fullPath);
});
+ describe('query fetching', () => {
+ describe('on component mount', () => {
+ it('does not fetch query', () => {
+ wrapper = mountComponent({ mountFn: mount });
+
+ expect(mockQueryResponse).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('on dropdown shown', () => {
+ it('fetches query', async () => {
+ wrapper = mountComponent({ mountFn: mount });
+
+ await showDropdown();
+
+ expect(mockQueryResponse).toHaveBeenCalled();
+ });
+ });
+ });
+
describe('when selecting from a group template', () => {
- const groupId = getIdFromGraphQLId(data.currentUser.groups.nodes[1].id);
+ const { fullPath, id } = data.currentUser.groups.nodes[1];
beforeEach(async () => {
wrapper = mountComponent({ mountFn: mount });
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
- eventHub.$emit('select-template', groupId);
+ // Show dropdown to fetch projects
+ await showDropdown();
+
+ eventHub.$emit('select-template', getIdFromGraphQLId(id), fullPath);
});
it('filters the dropdown items to the selected group and children', async () => {
@@ -188,7 +216,7 @@ describe('NewProjectUrlSelect component', () => {
});
it('sets the selection to the group', async () => {
- expect(findDropdown().props('text')).toBe(data.currentUser.groups.nodes[1].fullPath);
+ expect(findDropdown().props('text')).toBe(fullPath);
});
});
@@ -214,12 +242,13 @@ describe('NewProjectUrlSelect component', () => {
});
it('emits `update-visibility` event to update the visibility radio options', async () => {
- wrapper = mountComponent();
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
+ wrapper = mountComponent({ mountFn: mount });
const spy = jest.spyOn(eventHub, '$emit');
+ // Show dropdown to fetch projects
+ await showDropdown();
+
await clickDropdownItem();
const namespace = data.currentUser.groups.nodes[0];
@@ -233,16 +262,16 @@ describe('NewProjectUrlSelect component', () => {
});
it('updates hidden input with selected namespace', async () => {
- wrapper = mountComponent();
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
+ wrapper = mountComponent({ mountFn: mount });
+
+ // Show dropdown to fetch projects
+ await showDropdown();
await clickDropdownItem();
- expect(findHiddenInput().attributes()).toMatchObject({
- name: 'project[namespace_id]',
- value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
- });
+ expect(findHiddenInput().attributes('value')).toBe(
+ getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
+ );
});
it('tracks clicking on the dropdown', () => {
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb
index 045cdb129cb..55ba6e56237 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb
@@ -41,6 +41,19 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher do
it_behaves_like 'match returned records'
end
+ context 'when intervalstyle setting is configured to "postgres"' do
+ it 'avoids nil durations' do
+ # ActiveRecord cannot parse the 'postgres' intervalstyle, it returns nil
+ # The setting is rolled back after the test case.
+ Analytics::CycleAnalytics::IssueStageEvent.connection.execute("SET LOCAL intervalstyle='postgres'")
+
+ records_fetcher.serialized_records do |relation|
+ durations = relation.map(&:total_time)
+ expect(durations).to all(be > 0)
+ end
+ end
+ end
+
context 'when sorting by end event ASC' do
let(:expected_issue_ids) { [issue_2.iid, issue_1.iid, issue_3.iid] }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index c63f2d79ad9..1d3aded613b 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1462,7 +1462,7 @@ RSpec.describe Issue do
it 'schedules rebalancing if there is no space left' do
lhs = build_stubbed(:issue, relative_position: 99, project: project)
to_move = build(:issue, project: project)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project_id, namespace_id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, project_id, namespace_id)
expect { to_move.move_between(lhs, issue) }.to raise_error(RelativePositioning::NoSpaceLeft)
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 18e03db11dc..a519bdab0c7 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -154,7 +154,7 @@ RSpec.describe Issues::CreateService do
end
it 'moves the issue to the end, in an asynchronous worker' do
- expect(IssuePlacementWorker).to receive(:perform_async).with(be_nil, Integer)
+ expect(Issues::PlacementWorker).to receive(:perform_async).with(be_nil, Integer)
described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params).execute
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 85b8fef685e..b5246505558 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -319,7 +319,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).not_to receive(:perform_async)
+ expect(Issues::RebalancingWorker).not_to receive(:perform_async)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
@@ -335,7 +335,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
@@ -349,7 +349,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
@@ -363,7 +363,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue1.id, issue2.id]
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
update_issue(opts)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
diff --git a/spec/workers/issue_placement_worker_spec.rb b/spec/workers/issue_placement_worker_spec.rb
index 50b9d58a5b0..9b5121d98e8 100644
--- a/spec/workers/issue_placement_worker_spec.rb
+++ b/spec/workers/issue_placement_worker_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe IssuePlacementWorker do
it 'schedules rebalancing if needed' do
issue_a.update!(relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
run_worker
end
@@ -52,7 +52,7 @@ RSpec.describe IssuePlacementWorker do
.with(have_attributes(count: described_class::QUERY_LIMIT))
.and_call_original
- expect(described_class).to receive(:perform_async).with(nil, project.id)
+ expect(Issues::PlacementWorker).to receive(:perform_async).with(nil, project.id)
run_worker
@@ -101,7 +101,7 @@ RSpec.describe IssuePlacementWorker do
it 'anticipates the failure to place the issues, and schedules rebalancing' do
allow(Issue).to receive(:move_nulls_to_end) { raise RelativePositioning::NoSpaceLeft }
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
expect(Gitlab::ErrorTracking)
.to receive(:log_exception)
.with(RelativePositioning::NoSpaceLeft, worker_arguments)
diff --git a/spec/workers/issues/placement_worker_spec.rb b/spec/workers/issues/placement_worker_spec.rb
index 694cdd2ef37..33fa0b31b72 100644
--- a/spec/workers/issues/placement_worker_spec.rb
+++ b/spec/workers/issues/placement_worker_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Issues::PlacementWorker do
it 'schedules rebalancing if needed' do
issue_a.update!(relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
run_worker
end
diff --git a/spec/workers/issues/rebalancing_worker_spec.rb b/spec/workers/issues/rebalancing_worker_spec.rb
index 438edd85f66..e1c0b348a4f 100644
--- a/spec/workers/issues/rebalancing_worker_spec.rb
+++ b/spec/workers/issues/rebalancing_worker_spec.rb
@@ -35,6 +35,20 @@ RSpec.describe Issues::RebalancingWorker do
described_class.new.perform # all arguments are nil
end
+
+ it 'does not schedule a new rebalance if it finished under 1h ago' do
+ container_type = arguments.second.present? ? ::Gitlab::Issues::Rebalancing::State::PROJECT : ::Gitlab::Issues::Rebalancing::State::NAMESPACE
+ container_id = arguments.second || arguments.third
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(::Gitlab::Issues::Rebalancing::State.send(:recently_finished_key, container_type, container_id), true)
+ end
+
+ expect(Issues::RelativePositionRebalancingService).not_to receive(:new)
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ described_class.new.perform(*arguments)
+ end
end
shared_examples 'safely handles non-existent ids' do
diff --git a/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb b/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb
index 02d1241d2ba..6723c425f34 100644
--- a/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb
+++ b/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb
@@ -10,15 +10,15 @@ RSpec.describe Issues::RescheduleStuckIssueRebalancesWorker, :clean_gitlab_redis
describe '#perform' do
it 'does not schedule a rebalance' do
- expect(IssueRebalancingWorker).not_to receive(:perform_async)
+ expect(Issues::RebalancingWorker).not_to receive(:perform_async)
worker.perform
end
it 'schedules a rebalance in case there are any rebalances started' do
expect(::Gitlab::Issues::Rebalancing::State).to receive(:fetch_rebalancing_groups_and_projects).and_return([[group.id], [project.id]])
- expect(IssueRebalancingWorker).to receive(:bulk_perform_async).with([[nil, nil, group.id]]).once
- expect(IssueRebalancingWorker).to receive(:bulk_perform_async).with([[nil, project.id, nil]]).once
+ expect(Issues::RebalancingWorker).to receive(:bulk_perform_async).with([[nil, nil, group.id]]).once
+ expect(Issues::RebalancingWorker).to receive(:bulk_perform_async).with([[nil, project.id, nil]]).once
worker.perform
end