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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-11-15 18:10:57 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-15 18:10:57 +0300
commite5c7d631a84940c66e46f1824ba7ce0c7f1d0ea4 (patch)
treeac1cce129d8c8cdcee6a668e27d8034d2da172e4 /app
parent0687020509cafe1e24eb0bfa0e0f5c9f6c4799d2 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters_list/clusters_util.js4
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_empty_state.vue24
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue162
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue11
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue14
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_actions.vue44
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_empty_state.vue1
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_main_view.vue68
-rw-r--r--app/assets/javascripts/clusters_list/components/install_agent_modal.vue46
-rw-r--r--app/assets/javascripts/clusters_list/constants.js26
-rw-r--r--app/assets/javascripts/clusters_list/graphql/cache_update.js28
-rw-r--r--app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql10
-rw-r--r--app/assets/javascripts/clusters_list/graphql/mutations/create_agent.mutation.graphql4
-rw-r--r--app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql10
-rw-r--r--app/assets/javascripts/clusters_list/index.js4
-rw-r--r--app/assets/javascripts/clusters_list/load_agents.js44
-rw-r--r--app/assets/javascripts/clusters_list/load_main_view.js50
-rw-r--r--app/assets/javascripts/mr_notes/index.js4
-rw-r--r--app/assets/javascripts/projects/commit/components/form_modal.vue10
-rw-r--r--app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js3
-rw-r--r--app/assets/javascripts/projects/commit/init_revert_commit_modal.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/registry/title_area.vue11
-rw-r--r--app/assets/stylesheets/pages/clusters.scss19
-rw-r--r--app/helpers/clusters_helper.rb23
-rw-r--r--app/helpers/routing/pseudonymization_helper.rb11
-rw-r--r--app/models/application_record.rb4
-rw-r--r--app/models/application_setting_implementation.rb4
-rw-r--r--app/models/concerns/cascading_namespace_setting_attribute.rb2
-rw-r--r--app/models/concerns/database_reflection.rb21
-rw-r--r--app/models/concerns/legacy_bulk_insert.rb54
-rw-r--r--app/models/concerns/sha256_attribute.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb2
-rw-r--r--app/models/concerns/transactions.rb28
-rw-r--r--app/models/concerns/x509_serial_number_attribute.rb2
-rw-r--r--app/models/deployment.rb2
-rw-r--r--app/models/design_management/version.rb2
-rw-r--r--app/models/merge_request_context_commit.rb2
-rw-r--r--app/models/merge_request_context_commit_diff_file.rb2
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/models/merge_request_diff_commit.rb2
-rw-r--r--app/services/design_management/copy_design_collection/copy_service.rb12
-rw-r--r--app/services/issuable/clone/attributes_rewriter.rb2
-rw-r--r--app/services/packages/create_dependency_service.rb10
-rw-r--r--app/services/packages/nuget/create_dependency_service.rb2
-rw-r--r--app/services/packages/update_tags_service.rb2
-rw-r--r--app/services/projects/detect_repository_languages_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_link_service.rb2
-rw-r--r--app/services/resource_events/change_labels_service.rb2
-rw-r--r--app/services/suggestions/create_service.rb2
-rw-r--r--app/views/admin/dashboard/index.html.haml4
-rw-r--r--app/views/admin/users/_tabs.html.haml10
-rw-r--r--app/views/clusters/clusters/index.html.haml20
-rw-r--r--app/views/users/_overview.html.haml29
-rw-r--r--app/workers/gitlab/jira_import/import_issue_worker.rb4
55 files changed, 599 insertions, 280 deletions
diff --git a/app/assets/javascripts/clusters_list/clusters_util.js b/app/assets/javascripts/clusters_list/clusters_util.js
index 9b870134512..c78c93fe1ba 100644
--- a/app/assets/javascripts/clusters_list/clusters_util.js
+++ b/app/assets/javascripts/clusters_list/clusters_util.js
@@ -6,3 +6,7 @@ export function generateAgentRegistrationCommand(agentToken, kasAddress) {
--agent-version stable \\
--namespace gitlab-kubernetes-agent | kubectl apply -f -`;
}
+
+export function getAgentConfigPath(clusterAgentName) {
+ return `.gitlab/agents/${clusterAgentName}`;
+}
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 06092039eb7..5c6f9de0624 100644
--- a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
@@ -1,10 +1,19 @@
<script>
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
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'),
+ getStartedDocsUrl: helpPagePath('user/clusters/agent/index', {
+ anchor: 'define-a-configuration-repository',
+ }),
+ integrationsDocsUrl: helpPagePath('user/clusters/agent/index', {
+ anchor: 'get-started-with-gitops-and-the-gitlab-agent',
+ }),
components: {
GlButton,
GlEmptyState,
@@ -15,14 +24,7 @@ export default {
directives: {
GlModalDirective,
},
- inject: [
- 'emptyStateImage',
- 'projectPath',
- 'multipleClustersDocsUrl',
- 'installDocsUrl',
- 'getStartedDocsUrl',
- 'integrationDocsUrl',
- ],
+ inject: ['emptyStateImage', 'projectPath'],
props: {
hasConfigurations: {
type: Boolean,
@@ -47,7 +49,7 @@ export default {
<gl-sprintf :message="$options.i18n.multipleClustersText">
<template #link="{ content }">
<gl-link
- :href="multipleClustersDocsUrl"
+ :href="$options.multipleClustersDocsUrl"
target="_blank"
data-testid="multiple-clusters-docs-link"
>
@@ -58,7 +60,7 @@ export default {
</p>
<p class="mw-460 gl-mx-auto">
- <gl-link :href="installDocsUrl" target="_blank" data-testid="install-docs-link">
+ <gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link">
{{ $options.i18n.learnMoreText }}
</gl-link>
</p>
@@ -75,7 +77,7 @@ export default {
<gl-button
category="primary"
variant="info"
- :href="getStartedDocsUrl"
+ :href="$options.getStartedDocsUrl"
target="_blank"
class="gl-ml-0!"
>
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index 487e512c06d..000730ac1ba 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -1,6 +1,5 @@
<script>
import {
- GlButton,
GlLink,
GlModalDirective,
GlTable,
@@ -12,11 +11,12 @@ import {
import { s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES, TROUBLESHOOTING_LINK } from '../constants';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES } from '../constants';
+import { getAgentConfigPath } from '../clusters_util';
export default {
components: {
- GlButton,
GlLink,
GlTable,
GlIcon,
@@ -29,10 +29,12 @@ export default {
GlModalDirective,
},
mixins: [timeagoMixin],
- inject: ['integrationDocsUrl'],
INSTALL_AGENT_MODAL_ID,
AGENT_STATUSES,
- TROUBLESHOOTING_LINK,
+
+ troubleshooting_link: helpPagePath('user/clusters/agent/index', {
+ anchor: 'troubleshooting',
+ }),
props: {
agents: {
required: true,
@@ -41,112 +43,102 @@ export default {
},
computed: {
fields() {
+ const tdClass = 'gl-py-5!';
return [
{
key: 'name',
label: s__('ClusterAgents|Name'),
+ tdClass,
},
{
key: 'status',
label: s__('ClusterAgents|Connection status'),
+ tdClass,
},
{
key: 'lastContact',
label: s__('ClusterAgents|Last contact'),
+ tdClass,
},
{
key: 'configuration',
label: s__('ClusterAgents|Configuration'),
+ tdClass,
},
];
},
},
+ methods: {
+ getCellId(item) {
+ return `connection-status-${item.name}`;
+ },
+ getAgentConfigPath,
+ },
};
</script>
<template>
- <div>
- <div class="gl-display-block gl-text-right gl-my-3">
- <gl-button
- v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
- variant="confirm"
- category="primary"
- >{{ s__('ClusterAgents|Install a new GitLab Agent') }}
- </gl-button>
- </div>
+ <gl-table
+ :items="agents"
+ :fields="fields"
+ stacked="md"
+ head-variant="white"
+ thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
+ class="gl-mb-4!"
+ data-testid="cluster-agent-list-table"
+ >
+ <template #cell(name)="{ item }">
+ <gl-link :href="item.webPath" data-testid="cluster-agent-name-link">
+ {{ item.name }}
+ </gl-link>
+ </template>
- <gl-table
- :items="agents"
- :fields="fields"
- stacked="md"
- head-variant="white"
- thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
- data-testid="cluster-agent-list-table"
- >
- <template #cell(name)="{ item }">
- <gl-link :href="item.webPath" data-testid="cluster-agent-name-link">
- {{ item.name }}
- </gl-link>
- </template>
-
- <template #cell(status)="{ item }">
- <span
- :id="`connection-status-${item.name}`"
- class="gl-pr-5"
- data-testid="cluster-agent-connection-status"
- >
- <span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
- <gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="12" /></span
- >{{ $options.AGENT_STATUSES[item.status].name }}
- </span>
- <gl-tooltip
- v-if="item.status === 'active'"
- :target="`connection-status-${item.name}`"
- placement="right"
- >
- <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
- ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
- </gl-sprintf>
- </gl-tooltip>
- <gl-popover
- v-else
- :target="`connection-status-${item.name}`"
- :title="$options.AGENT_STATUSES[item.status].tooltip.title"
- placement="right"
- container="viewport"
- >
- <p>
- <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.body"
- ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template></gl-sprintf
- >
- </p>
- <p class="gl-mb-0">
- {{ s__('ClusterAgents|For more troubleshooting information go to') }}
- <gl-link :href="$options.TROUBLESHOOTING_LINK" target="_blank" class="gl-font-sm">
- {{ $options.TROUBLESHOOTING_LINK }}</gl-link
- >
- </p>
- </gl-popover>
- </template>
+ <template #cell(status)="{ item }">
+ <span :id="getCellId(item)" class="gl-md-pr-5" data-testid="cluster-agent-connection-status">
+ <span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
+ <gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="12" /></span
+ >{{ $options.AGENT_STATUSES[item.status].name }}
+ </span>
+ <gl-tooltip v-if="item.status === 'active'" :target="getCellId(item)" placement="right">
+ <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
+ ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
+ </gl-sprintf>
+ </gl-tooltip>
+ <gl-popover
+ v-else
+ :target="getCellId(item)"
+ :title="$options.AGENT_STATUSES[item.status].tooltip.title"
+ placement="right"
+ container="viewport"
+ >
+ <p>
+ <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.body"
+ ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template></gl-sprintf
+ >
+ </p>
+ <p class="gl-mb-0">
+ <gl-link :href="$options.troubleshooting_link" target="_blank" class="gl-font-sm">
+ {{ s__('ClusterAgents|Learn how to troubleshoot') }}</gl-link
+ >
+ </p>
+ </gl-popover>
+ </template>
- <template #cell(lastContact)="{ item }">
- <span data-testid="cluster-agent-last-contact">
- <time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
- <span v-else>{{ s__('ClusterAgents|Never') }}</span>
- </span>
- </template>
+ <template #cell(lastContact)="{ item }">
+ <span data-testid="cluster-agent-last-contact">
+ <time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
+ <span v-else>{{ s__('ClusterAgents|Never') }}</span>
+ </span>
+ </template>
- <template #cell(configuration)="{ item }">
- <span data-testid="cluster-agent-configuration-link">
- <!-- eslint-disable @gitlab/vue-require-i18n-strings -->
- <gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
- .gitlab/agents/{{ item.name }}
- </gl-link>
+ <template #cell(configuration)="{ item }">
+ <span data-testid="cluster-agent-configuration-link">
+ <gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
+ {{ getAgentConfigPath(item.name) }}
+ </gl-link>
- <span v-else>.gitlab/agents/{{ item.name }}</span>
- <!-- eslint-enable @gitlab/vue-require-i18n-strings -->
- </span>
- </template>
- </gl-table>
- </div>
+ <span v-else>{{ getAgentConfigPath(item.name) }}</span>
+ </span>
+ </template>
+ </gl-table>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index ed44c1f5fa7..4fc79b32b9c 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -4,7 +4,6 @@ import { MAX_LIST_COUNT, ACTIVE_CONNECTION_TIME } from '../constants';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AgentEmptyState from './agent_empty_state.vue';
import AgentTable from './agent_table.vue';
-import InstallAgentModal from './install_agent_modal.vue';
export default {
apollo: {
@@ -26,7 +25,6 @@ export default {
components: {
AgentEmptyState,
AgentTable,
- InstallAgentModal,
GlAlert,
GlKeysetPagination,
GlLoadingIcon,
@@ -135,19 +133,18 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="isLoading" size="md" class="gl-mt-3" />
+ <gl-loading-icon v-if="isLoading" size="md" />
- <section v-else-if="agentList" class="gl-mt-3">
+ <section v-else-if="agentList">
<div v-if="agentList.length">
- <AgentTable :agents="agentList" />
+ <agent-table :agents="agentList" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination v-bind="agentPageInfo" @prev="prevPage" @next="nextPage" />
</div>
</div>
- <AgentEmptyState v-else :has-configurations="hasConfigurations" />
- <InstallAgentModal @agentRegistered="reloadAgents" />
+ <agent-empty-state v-else :has-configurations="hasConfigurations" />
</section>
<gl-alert v-else variant="danger" :dismissible="false">
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index cf0587341b5..bc27563b6d5 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -57,30 +57,37 @@ export default {
},
},
fields() {
+ const tdClass = 'gl-py-5!';
return [
{
key: 'name',
label: __('Kubernetes cluster'),
+ tdClass,
},
{
key: 'environment_scope',
label: __('Environment scope'),
+ tdClass,
},
{
key: 'node_size',
label: __('Nodes'),
+ tdClass,
},
{
key: 'total_cpu',
label: __('Total cores (CPUs)'),
+ tdClass,
},
{
key: 'total_memory',
label: __('Total memory (GB)'),
+ tdClass,
},
{
key: 'cluster_type',
label: __('Cluster level'),
+ tdClass,
formatter: (value) => CLUSTER_TYPES[value],
},
];
@@ -201,7 +208,7 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="loadingClusters" size="md" class="gl-mt-3" />
+ <gl-loading-icon v-if="loadingClusters" size="md" />
<section v-else>
<ancestor-notice />
@@ -210,10 +217,11 @@ export default {
v-if="hasClusters"
:items="clusters"
:fields="fields"
+ fixed
stacked="md"
head-variant="white"
- thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
- class="qa-clusters-table"
+ thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
+ class="qa-clusters-table gl-mb-4!"
data-testid="cluster_list_table"
>
<template #cell(name)="{ item }">
diff --git a/app/assets/javascripts/clusters_list/components/clusters_actions.vue b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
new file mode 100644
index 00000000000..25f67462223
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
@@ -0,0 +1,44 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlModalDirective } from '@gitlab/ui';
+import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '../constants';
+
+export default {
+ i18n: CLUSTERS_ACTIONS,
+ INSTALL_AGENT_MODAL_ID,
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: ['newClusterPath', 'addClusterPath'],
+};
+</script>
+
+<template>
+ <div class="nav-controls gl-ml-auto">
+ <gl-dropdown
+ ref="dropdown"
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ category="primary"
+ variant="confirm"
+ :text="$options.i18n.actionsButton"
+ split
+ right
+ >
+ <gl-dropdown-item :href="newClusterPath" data-testid="new-cluster-link" @click.stop>
+ {{ $options.i18n.createNewCluster }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ data-testid="connect-new-agent-link"
+ >
+ {{ $options.i18n.connectWithAgent }}
+ </gl-dropdown-item>
+ <gl-dropdown-item :href="addClusterPath" data-testid="connect-cluster-link" @click.stop>
+ {{ $options.i18n.connectExistingCluster }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+</template>
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 1f153a4f38e..993507b18bf 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
@@ -14,6 +14,7 @@ export default {
},
inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'newClusterPath'],
learnMoreHelpUrl: helpPagePath('user/project/clusters/index'),
+ multipleClustersHelpUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
computed: {
...mapState(['canAddCluster']),
},
diff --git a/app/assets/javascripts/clusters_list/components/clusters_main_view.vue b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
new file mode 100644
index 00000000000..c87cf593305
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlTabs, GlTab } from '@gitlab/ui';
+import { CLUSTERS_TABS } from '../constants';
+import Agents from './agents.vue';
+import InstallAgentModal from './install_agent_modal.vue';
+import ClustersActions from './clusters_actions.vue';
+import Clusters from './clusters.vue';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ ClustersActions,
+ Clusters,
+ Agents,
+ InstallAgentModal,
+ },
+ CLUSTERS_TABS,
+ props: {
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ },
+ data() {
+ return {
+ selectedTabIndex: 0,
+ };
+ },
+ methods: {
+ onTabChange(tabName) {
+ this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName);
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-tabs
+ v-model="selectedTabIndex"
+ sync-active-tab-with-query-params
+ nav-class="gl-flex-grow-1 gl-align-items-center"
+ lazy
+ >
+ <gl-tab
+ v-for="(tab, idx) in $options.CLUSTERS_TABS"
+ :key="idx"
+ :title="tab.title"
+ :query-param-value="tab.queryParamValue"
+ class="gl-line-height-20 gl-mt-5"
+ >
+ <component
+ :is="tab.component"
+ :default-branch-name="defaultBranchName"
+ data-testid="clusters-tab-component"
+ @changeTab="onTabChange"
+ />
+ </gl-tab>
+
+ <template #tabs-end>
+ <clusters-actions />
+ </template>
+ </gl-tabs>
+
+ <install-agent-modal :default-branch-name="defaultBranchName" />
+ </div>
+</template>
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 28da1e146c6..0efd3844cc9 100644
--- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -12,9 +12,11 @@ import { helpPagePath } from '~/helpers/help_page_helper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import { generateAgentRegistrationCommand } from '../clusters_util';
-import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL } from '../constants';
+import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } from '../constants';
+import { addAgentToStore } from '../graphql/cache_update';
import createAgent from '../graphql/mutations/create_agent.mutation.graphql';
import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
+import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AvailableAgentsDropdown from './available_agents_dropdown.vue';
export default {
@@ -33,12 +35,20 @@ export default {
GlSprintf,
},
inject: ['projectPath', 'kasAddress'],
+ props: {
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ },
data() {
return {
registering: false,
agentName: null,
agentToken: null,
error: null,
+ clusterAgent: null,
};
},
computed: {
@@ -62,20 +72,24 @@ export default {
advancedInstallPath() {
return helpPagePath('user/clusters/agent/install/index', { anchor: 'advanced-installation' });
},
+ getAgentsQueryVariables() {
+ return {
+ defaultBranchName: this.defaultBranchName,
+ first: MAX_LIST_COUNT,
+ last: null,
+ projectPath: this.projectPath,
+ };
+ },
},
methods: {
setAgentName(name) {
this.agentName = name;
},
- cancelClicked() {
- this.$refs.modal.hide();
- },
- doneClicked() {
- this.$emit('agentRegistered');
+ closeModal() {
this.$refs.modal.hide();
},
resetModal() {
- this.registering = null;
+ this.registering = false;
this.agentName = null;
this.agentToken = null;
this.error = null;
@@ -90,6 +104,14 @@ export default {
projectPath: this.projectPath,
},
},
+ update: (store, { data: { createClusterAgent } }) => {
+ addAgentToStore(
+ store,
+ createClusterAgent,
+ getAgentsQuery,
+ this.getAgentsQueryVariables,
+ );
+ },
})
.then(({ data: { createClusterAgent } }) => createClusterAgent);
},
@@ -117,6 +139,8 @@ export default {
throw new Error(agentErrors[0]);
}
+ this.clusterAgent = clusterAgent;
+
const { errors: tokenErrors, secret } = await this.createAgentTokenMutation(
clusterAgent.id,
);
@@ -240,10 +264,10 @@ export default {
</template>
<template #modal-footer>
- <gl-button v-if="canCancel" @click="cancelClicked">{{ $options.i18n.cancel }} </gl-button>
+ <gl-button v-if="canCancel" @click="closeModal">{{ $options.i18n.cancel }} </gl-button>
- <gl-button v-if="registered" variant="confirm" category="primary" @click="doneClicked"
- >{{ $options.i18n.done }}
+ <gl-button v-if="registered" variant="confirm" category="primary" @click="closeModal"
+ >{{ $options.i18n.close }}
</gl-button>
<gl-button
@@ -252,7 +276,7 @@ export default {
variant="confirm"
category="primary"
@click="registerAgent"
- >{{ $options.i18n.next }}
+ >{{ $options.i18n.registerAgentButton }}
</gl-button>
</template>
</gl-modal>
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index d77e183f95b..36e6a065cbf 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -3,8 +3,6 @@ import { __, s__, sprintf } from '~/locale';
export const MAX_LIST_COUNT = 25;
export const INSTALL_AGENT_MODAL_ID = 'install-agent';
export const ACTIVE_CONNECTION_TIME = 480000;
-export const TROUBLESHOOTING_LINK =
- 'https://docs.gitlab.com/ee/user/clusters/agent/#troubleshooting';
export const CLUSTER_ERRORS = {
default: {
@@ -66,8 +64,8 @@ export const STATUSES = {
};
export const I18N_INSTALL_AGENT_MODAL = {
- next: __('Next'),
- done: __('Done'),
+ registerAgentButton: s__('ClusterAgents|Register Agent'),
+ close: __('Close'),
cancel: __('Cancel'),
modalTitle: s__('ClusterAgents|Install new Agent'),
@@ -168,3 +166,23 @@ export const I18N_CLUSTERS_EMPTY_STATE = {
learnMoreLinkText: s__('ClusterIntegration|Learn more about the GitLab managed clusters'),
buttonText: s__('ClusterIntegration|Connect with a certificate'),
};
+
+export const CLUSTERS_TABS = [
+ {
+ title: s__('ClusterAgents|Agent'),
+ component: 'agents',
+ queryParamValue: 'agent',
+ },
+ {
+ title: s__('ClusterAgents|Certificate based'),
+ component: 'clusters',
+ queryParamValue: 'certificate_based',
+ },
+];
+
+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'),
+};
diff --git a/app/assets/javascripts/clusters_list/graphql/cache_update.js b/app/assets/javascripts/clusters_list/graphql/cache_update.js
new file mode 100644
index 00000000000..ce6daecc908
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/graphql/cache_update.js
@@ -0,0 +1,28 @@
+import produce from 'immer';
+import { getAgentConfigPath } from '../clusters_util';
+
+export function addAgentToStore(store, createClusterAgent, query, variables) {
+ const { clusterAgent } = createClusterAgent;
+ const sourceData = store.readQuery({
+ query,
+ variables,
+ });
+
+ const data = produce(sourceData, (draftData) => {
+ const configuration = {
+ name: clusterAgent.name,
+ path: getAgentConfigPath(clusterAgent.name),
+ webPath: clusterAgent.webPath,
+ __typename: 'TreeEntry',
+ };
+
+ draftData.project.clusterAgents.nodes.push(clusterAgent);
+ draftData.project.repository.tree.trees.nodes.push(configuration);
+ });
+
+ store.writeQuery({
+ query,
+ variables,
+ data,
+ });
+}
diff --git a/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql b/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
new file mode 100644
index 00000000000..9b40260471c
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
@@ -0,0 +1,10 @@
+fragment ClusterAgentFragment on ClusterAgent {
+ id
+ name
+ webPath
+ tokens {
+ nodes {
+ lastUsedAt
+ }
+ }
+}
diff --git a/app/assets/javascripts/clusters_list/graphql/mutations/create_agent.mutation.graphql b/app/assets/javascripts/clusters_list/graphql/mutations/create_agent.mutation.graphql
index c29756159f5..996b388089b 100644
--- a/app/assets/javascripts/clusters_list/graphql/mutations/create_agent.mutation.graphql
+++ b/app/assets/javascripts/clusters_list/graphql/mutations/create_agent.mutation.graphql
@@ -1,7 +1,9 @@
+#import "../fragments/cluster_agent.fragment.graphql"
+
mutation createClusterAgent($input: CreateClusterAgentInput!) {
createClusterAgent(input: $input) {
clusterAgent {
- id
+ ...ClusterAgentFragment
}
errors
}
diff --git a/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql b/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql
index 61989e00d9e..397895b7d01 100644
--- a/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql
+++ b/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql
@@ -1,4 +1,5 @@
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "../fragments/cluster_agent.fragment.graphql"
query getAgents(
$defaultBranchName: String!
@@ -13,14 +14,7 @@ query getAgents(
project(fullPath: $projectPath) {
clusterAgents(first: $first, last: $last, before: $beforeAgent, after: $afterAgent) {
nodes {
- id
- name
- webPath
- tokens {
- nodes {
- lastUsedAt
- }
- }
+ ...ClusterAgentFragment
}
pageInfo {
diff --git a/app/assets/javascripts/clusters_list/index.js b/app/assets/javascripts/clusters_list/index.js
index de18965abbd..7f1ef37814b 100644
--- a/app/assets/javascripts/clusters_list/index.js
+++ b/app/assets/javascripts/clusters_list/index.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadClusters from './load_clusters';
-import loadAgents from './load_agents';
+import loadMainView from './load_main_view';
Vue.use(VueApollo);
export default () => {
loadClusters(Vue);
- loadAgents(Vue, VueApollo);
+ loadMainView(Vue, VueApollo);
};
diff --git a/app/assets/javascripts/clusters_list/load_agents.js b/app/assets/javascripts/clusters_list/load_agents.js
deleted file mode 100644
index e65de67515a..00000000000
--- a/app/assets/javascripts/clusters_list/load_agents.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import createDefaultClient from '~/lib/graphql';
-import Agents from './components/agents.vue';
-
-export default (Vue, VueApollo) => {
- const el = document.querySelector('#js-cluster-agents-list');
-
- if (!el) {
- return null;
- }
-
- const defaultClient = createDefaultClient();
-
- const {
- emptyStateImage,
- defaultBranchName,
- projectPath,
- multipleClustersDocsUrl,
- installDocsUrl,
- getStartedDocsUrl,
- integrationDocsUrl,
- kasAddress,
- } = el.dataset;
-
- return new Vue({
- el,
- apolloProvider: new VueApollo({ defaultClient }),
- provide: {
- emptyStateImage,
- projectPath,
- multipleClustersDocsUrl,
- installDocsUrl,
- getStartedDocsUrl,
- integrationDocsUrl,
- kasAddress,
- },
- render(createElement) {
- return createElement(Agents, {
- props: {
- defaultBranchName,
- },
- });
- },
- });
-};
diff --git a/app/assets/javascripts/clusters_list/load_main_view.js b/app/assets/javascripts/clusters_list/load_main_view.js
new file mode 100644
index 00000000000..08c99b46e16
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/load_main_view.js
@@ -0,0 +1,50 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import ClustersMainView from './components/clusters_main_view.vue';
+import { createStore } from './store';
+
+Vue.use(VueApollo);
+
+export default () => {
+ const el = document.querySelector('.js-clusters-main-view');
+
+ if (!el) {
+ return null;
+ }
+
+ const defaultClient = createDefaultClient();
+
+ const {
+ emptyStateImage,
+ defaultBranchName,
+ projectPath,
+ kasAddress,
+ newClusterPath,
+ addClusterPath,
+ emptyStateHelpText,
+ clustersEmptyStateImage,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ apolloProvider: new VueApollo({ defaultClient }),
+ provide: {
+ emptyStateImage,
+ projectPath,
+ kasAddress,
+ newClusterPath,
+ addClusterPath,
+ emptyStateHelpText,
+ clustersEmptyStateImage,
+ },
+ store: createStore(el.dataset),
+ render(createElement) {
+ return createElement(ClustersMainView, {
+ props: {
+ defaultBranchName,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index ea3e4e5604c..a1377415efe 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -23,8 +23,8 @@ export default function initMrNotes() {
initNotesApp();
document.addEventListener('merged:UpdateActions', () => {
- initRevertCommitModal();
- initCherryPickCommitModal();
+ initRevertCommitModal('i_code_review_post_merge_submit_revert_modal');
+ initCherryPickCommitModal('i_code_review_post_merge_submit_cherry_pick_modal');
});
requestIdleCallback(() => {
diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue
index ec7d37644a8..f9dd72119d1 100644
--- a/app/assets/javascripts/projects/commit/components/form_modal.vue
+++ b/app/assets/javascripts/projects/commit/components/form_modal.vue
@@ -1,6 +1,7 @@
<script>
import { GlModal, GlForm, GlFormCheckbox, GlSprintf, GlFormGroup } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
+import api from '~/api';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import csrf from '~/lib/utils/csrf';
import eventHub from '../event_hub';
@@ -40,6 +41,11 @@ export default {
required: false,
default: false,
},
+ primaryActionEventName: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -83,6 +89,10 @@ export default {
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
handlePrimary() {
+ if (this.primaryActionEventName) {
+ api.trackRedisHllUserEvent(this.primaryActionEventName);
+ }
+
this.$refs.form.$el.submit();
},
resetModalHandler() {
diff --git a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
index 47ee8237fea..b21fd1a74de 100644
--- a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
+++ b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
@@ -9,7 +9,7 @@ import {
} from './constants';
import createStore from './store';
-export default function initInviteMembersModal() {
+export default function initInviteMembersModal(primaryActionEventName) {
const el = document.querySelector('.js-cherry-pick-commit-modal');
if (!el) {
return false;
@@ -52,6 +52,7 @@ export default function initInviteMembersModal() {
openModal: OPEN_CHERRY_PICK_MODAL,
modalId: CHERRY_PICK_MODAL_ID,
isCherryPick: true,
+ primaryActionEventName,
},
}),
});
diff --git a/app/assets/javascripts/projects/commit/init_revert_commit_modal.js b/app/assets/javascripts/projects/commit/init_revert_commit_modal.js
index df26aa3c830..849b2f4858c 100644
--- a/app/assets/javascripts/projects/commit/init_revert_commit_modal.js
+++ b/app/assets/javascripts/projects/commit/init_revert_commit_modal.js
@@ -10,7 +10,7 @@ import {
} from './constants';
import createStore from './store';
-export default function initInviteMembersModal() {
+export default function initInviteMembersModal(primaryActionEventName) {
const el = document.querySelector('.js-revert-commit-modal');
if (!el) {
return false;
@@ -49,6 +49,7 @@ export default function initInviteMembersModal() {
i18n: { ...I18N_REVERT_MODAL, ...I18N_MODAL },
openModal: OPEN_REVERT_MODAL,
modalId: REVERT_MODAL_ID,
+ primaryActionEventName,
},
}),
});
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index a1759b1a815..84dac95ce74 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,6 +1,7 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
import { GlLoadingIcon, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import api from '~/api';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
@@ -83,6 +84,8 @@ export default {
removeSourceBranch() {
this.isMakingRequest = true;
+ api.trackRedisHllUserEvent('i_code_review_post_merge_delete_branch');
+
this.service
.removeSourceBranch()
.then((res) => res.data)
@@ -103,9 +106,13 @@ export default {
});
},
openRevertModal() {
+ api.trackRedisHllUserEvent('i_code_review_post_merge_click_revert');
+
modalEventHub.$emit(OPEN_REVERT_MODAL);
},
openCherryPickModal() {
+ api.trackRedisHllUserEvent('i_code_review_post_merge_click_cherry_pick');
+
modalEventHub.$emit(OPEN_CHERRY_PICK_MODAL);
},
},
diff --git a/app/assets/javascripts/vue_shared/components/registry/title_area.vue b/app/assets/javascripts/vue_shared/components/registry/title_area.vue
index 4b21ec0330a..d108d8d689d 100644
--- a/app/assets/javascripts/vue_shared/components/registry/title_area.vue
+++ b/app/assets/javascripts/vue_shared/components/registry/title_area.vue
@@ -69,20 +69,23 @@ export default {
/>
<div class="gl-display-flex gl-flex-direction-column">
- <h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2" data-testid="title">
+ <h2 class="gl-font-size-h1 gl-mt-3 gl-mb-0" data-testid="title">
<slot name="title">{{ title }}</slot>
- </h1>
+ </h2>
<div
v-if="$slots['sub-header']"
- class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-3"
>
<slot name="sub-header"></slot>
</div>
</div>
</div>
- <div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3">
+ <div
+ v-if="metadataSlots.length > 0"
+ class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
+ >
<template v-if="!metadataLoading">
<div
v-for="(row, metadataIndex) in metadataSlots"
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index f54764f8d05..b450bca4f41 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -20,4 +20,23 @@
@include gl-flex-wrap;
}
}
+
+ .gl-card-body {
+ @include media-breakpoint-up(sm) {
+ @include gl-pt-2;
+ min-height: 372px;
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .nav-controls {
+ @include gl-w-full;
+ order: -1;
+
+ .gl-new-dropdown,
+ .split-content-button {
+ @include gl-w-full;
+ }
+ }
+ }
}
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index bc464b8809b..93b6b4e8fe2 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -16,19 +16,6 @@ module ClustersHelper
clusterable.is_a?(Project)
end
- def js_cluster_agents_list_data(clusterable_project)
- {
- default_branch_name: clusterable_project.default_branch,
- empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
- project_path: clusterable_project.full_path,
- multiple_clusters_docs_url: help_page_path('user/project/clusters/multiple_kubernetes_clusters'),
- install_docs_url: help_page_path('administration/clusters/kas'),
- get_started_docs_url: help_page_path('user/clusters/agent/index', anchor: 'define-a-configuration-repository'),
- integration_docs_url: help_page_path('user/clusters/agent/index', anchor: 'get-started-with-gitops-and-the-gitlab-agent'),
- kas_address: Gitlab::Kas.external_url
- }
- end
-
def js_clusters_list_data(clusterable)
{
ancestor_help_path: help_page_path('user/group/clusters/index', anchor: 'cluster-precedence'),
@@ -45,6 +32,16 @@ module ClustersHelper
}
end
+ def js_clusters_data(clusterable)
+ {
+ default_branch_name: clusterable.default_branch,
+ empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
+ project_path: clusterable.full_path,
+ add_cluster_path: clusterable.new_path(tab: 'add'),
+ kas_address: Gitlab::Kas.external_url
+ }.merge(js_clusters_list_data(clusterable))
+ end
+
def js_cluster_form_data(cluster, can_edit)
{
enabled: cluster.enabled?.to_s,
diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb
index 58cbf8dbdea..ac30669dc83 100644
--- a/app/helpers/routing/pseudonymization_helper.rb
+++ b/app/helpers/routing/pseudonymization_helper.rb
@@ -3,10 +3,7 @@
module Routing
module PseudonymizationHelper
class MaskHelper
- QUERY_PARAMS_TO_MASK = %w[
- assignee_username
- author_username
- ].freeze
+ QUERY_PARAMS_TO_NOT_MASK = %w[].freeze
def initialize(request_object, group, project)
@request = request_object
@@ -58,10 +55,10 @@ module Routing
query_string_hash = Rack::Utils.parse_nested_query(@request.query_string)
- QUERY_PARAMS_TO_MASK.each do |maskable_attribute|
- next unless query_string_hash.has_key?(maskable_attribute)
+ query_string_hash.keys.each do |key|
+ next if QUERY_PARAMS_TO_NOT_MASK.include?(key)
- query_string_hash[maskable_attribute] = "masked_#{maskable_attribute}"
+ query_string_hash[key] = "masked_#{key}"
end
query_string_hash
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 7737614ae25..0f7b6388441 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
+ include DatabaseReflection
+ include Transactions
+ include LegacyBulkInsert
+
self.abstract_class = true
alias_method :reset, :reload
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 028279b6150..54ec8b2c3e4 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -244,11 +244,11 @@ module ApplicationSettingImplementation
end
def home_page_url_column_exists?
- ::Gitlab::Database.main.cached_column_exists?(:application_settings, :home_page_url)
+ ApplicationSetting.database.cached_column_exists?(:home_page_url)
end
def help_page_support_url_column_exists?
- ::Gitlab::Database.main.cached_column_exists?(:application_settings, :help_page_support_url)
+ ApplicationSetting.database.cached_column_exists?(:help_page_support_url)
end
def disabled_oauth_sign_in_sources=(sources)
diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb
index c70c6dca105..731729a1ed5 100644
--- a/app/models/concerns/cascading_namespace_setting_attribute.rb
+++ b/app/models/concerns/cascading_namespace_setting_attribute.rb
@@ -127,7 +127,7 @@ module CascadingNamespaceSettingAttribute
end
def alias_boolean(attribute)
- return unless Gitlab::Database.main.exists? && type_for_attribute(attribute).type == :boolean
+ return unless database.exists? && type_for_attribute(attribute).type == :boolean
alias_method :"#{attribute}?", attribute
end
diff --git a/app/models/concerns/database_reflection.rb b/app/models/concerns/database_reflection.rb
new file mode 100644
index 00000000000..1842f5bf4ec
--- /dev/null
+++ b/app/models/concerns/database_reflection.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# A module that makes it easier/less verbose to reflect upon a database
+# connection.
+#
+# Using this module you can write this:
+#
+# User.database.database_name
+#
+# Instead of this:
+#
+# Gitlab::Database::Reflection.new(User).database_name
+module DatabaseReflection
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def database
+ @database_reflection ||= ::Gitlab::Database::Reflection.new(self)
+ end
+ end
+end
diff --git a/app/models/concerns/legacy_bulk_insert.rb b/app/models/concerns/legacy_bulk_insert.rb
new file mode 100644
index 00000000000..1249dfb70cd
--- /dev/null
+++ b/app/models/concerns/legacy_bulk_insert.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module LegacyBulkInsert
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Bulk inserts a number of rows into a table, optionally returning their
+ # IDs.
+ #
+ # This method is deprecated, and you should use the BulkInsertSafe module
+ # instead.
+ #
+ # table - The name of the table to insert the rows into.
+ # rows - An Array of Hash instances, each mapping the columns to their
+ # values.
+ # return_ids - When set to true the return value will be an Array of IDs of
+ # the inserted rows
+ # disable_quote - A key or an Array of keys to exclude from quoting (You
+ # become responsible for protection from SQL injection for
+ # these keys!)
+ # on_conflict - Defines an upsert. Values can be: :disabled (default) or
+ # :do_nothing
+ def legacy_bulk_insert(table, rows, return_ids: false, disable_quote: [], on_conflict: nil)
+ return if rows.empty?
+
+ keys = rows.first.keys
+ columns = keys.map { |key| connection.quote_column_name(key) }
+
+ disable_quote = Array(disable_quote).to_set
+ tuples = rows.map do |row|
+ keys.map do |k|
+ disable_quote.include?(k) ? row[k] : connection.quote(row[k])
+ end
+ end
+
+ sql = <<-EOF
+ INSERT INTO #{table} (#{columns.join(', ')})
+ VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
+ EOF
+
+ sql = "#{sql} ON CONFLICT DO NOTHING" if on_conflict == :do_nothing
+
+ sql = "#{sql} RETURNING id" if return_ids
+
+ result = connection.execute(sql)
+
+ if return_ids
+ result.values.map { |tuple| tuple[0].to_i }
+ else
+ []
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/sha256_attribute.rb b/app/models/concerns/sha256_attribute.rb
index 17fda6c806c..3c906642b1a 100644
--- a/app/models/concerns/sha256_attribute.rb
+++ b/app/models/concerns/sha256_attribute.rb
@@ -39,7 +39,7 @@ module Sha256Attribute
end
def database_exists?
- Gitlab::Database.main.exists?
+ database.exists?
end
end
end
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index 27277bc5296..ba7c6c0cd8b 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -32,7 +32,7 @@ module ShaAttribute
end
def database_exists?
- Gitlab::Database.main.exists?
+ database.exists?
end
end
end
diff --git a/app/models/concerns/transactions.rb b/app/models/concerns/transactions.rb
new file mode 100644
index 00000000000..a186ebc8475
--- /dev/null
+++ b/app/models/concerns/transactions.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Transactions
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # inside_transaction? will return true if the caller is running within a
+ # transaction. Handles special cases when running inside a test environment,
+ # where tests may be wrapped in transactions
+ def inside_transaction?
+ base = Rails.env.test? ? @open_transactions_baseline.to_i : 0
+
+ connection.open_transactions > base
+ end
+
+ # These methods that access @open_transactions_baseline are not thread-safe.
+ # These are fine though because we only call these in RSpec's main thread.
+ # If we decide to run specs multi-threaded, we would need to use something
+ # like ThreadGroup to keep track of this value
+ def set_open_transactions_baseline
+ @open_transactions_baseline = connection.open_transactions
+ end
+
+ def reset_open_transactions_baseline
+ @open_transactions_baseline = 0
+ end
+ end
+end
diff --git a/app/models/concerns/x509_serial_number_attribute.rb b/app/models/concerns/x509_serial_number_attribute.rb
index dfb1e151b41..e51ed95bf70 100644
--- a/app/models/concerns/x509_serial_number_attribute.rb
+++ b/app/models/concerns/x509_serial_number_attribute.rb
@@ -39,7 +39,7 @@ module X509SerialNumberAttribute
end
def database_exists?
- Gitlab::Database.main.exists?
+ database.exists?
end
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index f7a7f7a4a5f..5fddc661602 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -300,7 +300,7 @@ class Deployment < ApplicationRecord
"#{id} as deployment_id",
"#{environment_id} as environment_id").to_sql
- # We don't use `Gitlab::Database.main.bulk_insert` here so that we don't need to
+ # We don't use `ApplicationRecord.legacy_bulk_insert` here so that we don't need to
# first pluck lots of IDs into memory.
#
# We also ignore any duplicates so this method can be called multiple times
diff --git a/app/models/design_management/version.rb b/app/models/design_management/version.rb
index 6cda03557d1..5819404efb9 100644
--- a/app/models/design_management/version.rb
+++ b/app/models/design_management/version.rb
@@ -88,7 +88,7 @@ module DesignManagement
rows = design_actions.map { |action| action.row_attrs(version) }
- Gitlab::Database.main.bulk_insert(::DesignManagement::Action.table_name, rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(::DesignManagement::Action.table_name, rows) # rubocop:disable Gitlab/BulkInsert
version.designs.reset
version.validate!
design_actions.each(&:performed)
diff --git a/app/models/merge_request_context_commit.rb b/app/models/merge_request_context_commit.rb
index 09824ed4468..ebbdecf8aa7 100644
--- a/app/models/merge_request_context_commit.rb
+++ b/app/models/merge_request_context_commit.rb
@@ -26,7 +26,7 @@ class MergeRequestContextCommit < ApplicationRecord
# create MergeRequestContextCommit by given commit sha and it's diff file record
def self.bulk_insert(rows, **args)
- Gitlab::Database.main.bulk_insert('merge_request_context_commits', rows, **args) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert('merge_request_context_commits', rows, **args) # rubocop:disable Gitlab/BulkInsert
end
def to_commit
diff --git a/app/models/merge_request_context_commit_diff_file.rb b/app/models/merge_request_context_commit_diff_file.rb
index b9efebe3af2..fdf57068928 100644
--- a/app/models/merge_request_context_commit_diff_file.rb
+++ b/app/models/merge_request_context_commit_diff_file.rb
@@ -14,7 +14,7 @@ class MergeRequestContextCommitDiffFile < ApplicationRecord
# create MergeRequestContextCommitDiffFile by given diff file record(s)
def self.bulk_insert(*args)
- Gitlab::Database.main.bulk_insert('merge_request_context_commit_diff_files', *args) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert('merge_request_context_commit_diff_files', *args) # rubocop:disable Gitlab/BulkInsert
end
def path
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 13ef47bec45..2516ff05bda 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -515,7 +515,7 @@ class MergeRequestDiff < ApplicationRecord
transaction do
MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
- Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
save!
end
@@ -535,7 +535,7 @@ class MergeRequestDiff < ApplicationRecord
transaction do
MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
- Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
update!(stored_externally: false)
end
@@ -595,7 +595,7 @@ class MergeRequestDiff < ApplicationRecord
rows = build_external_merge_request_diff_files(rows) if use_external_diff?
# Faster inserts
- Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
end
def build_external_diff_tempfile(rows)
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
index b1cae0d1e49..66f1e45fd49 100644
--- a/app/models/merge_request_diff_commit.rb
+++ b/app/models/merge_request_diff_commit.rb
@@ -74,7 +74,7 @@ class MergeRequestDiffCommit < ApplicationRecord
)
end
- Gitlab::Database.main.bulk_insert(self.table_name, rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(self.table_name, rows) # rubocop:disable Gitlab/BulkInsert
end
def self.prepare_commits_for_bulk_insert(commits)
diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb
index c43696442d2..5e557e9ea53 100644
--- a/app/services/design_management/copy_design_collection/copy_service.rb
+++ b/app/services/design_management/copy_design_collection/copy_service.rb
@@ -181,12 +181,12 @@ module DesignManagement
)
end
- # TODO Replace `Gitlab::Database.main.bulk_insert` with `BulkInsertSafe`
+ # TODO Replace `ApplicationRecord.legacy_bulk_insert` with `BulkInsertSafe`
# once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed.
# When this is fixed, we can remove the call to
# `with_project_iid_supply` above, since the objects will be instantiated
# and callbacks (including `ensure_project_iid!`) will fire.
- ::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
+ ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Design.table_name,
new_rows,
return_ids: true
@@ -207,9 +207,9 @@ module DesignManagement
)
end
- # TODO Replace `Gitlab::Database.main.bulk_insert` with `BulkInsertSafe`
+ # TODO Replace `ApplicationRecord.legacy_bulk_insert` with `BulkInsertSafe`
# once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed.
- ::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
+ ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Version.table_name,
new_rows,
return_ids: true
@@ -239,7 +239,7 @@ module DesignManagement
end
# We cannot use `BulkInsertSafe` because of the uploader mounted in `Action`.
- ::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
+ ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Action.table_name,
new_rows
)
@@ -278,7 +278,7 @@ module DesignManagement
# We cannot use `BulkInsertSafe` due to the LfsObjectsProject#update_project_statistics
# callback that fires after_commit.
- ::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
+ ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
LfsObjectsProject.table_name,
new_rows,
on_conflict: :do_nothing # Upsert
diff --git a/app/services/issuable/clone/attributes_rewriter.rb b/app/services/issuable/clone/attributes_rewriter.rb
index d8b639bb422..279d3051848 100644
--- a/app/services/issuable/clone/attributes_rewriter.rb
+++ b/app/services/issuable/clone/attributes_rewriter.rb
@@ -99,7 +99,7 @@ module Issuable
yield(event)
end.compact
- Gitlab::Database.main.bulk_insert(table_name, events) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(table_name, events) # rubocop:disable Gitlab/BulkInsert
end
end
diff --git a/app/services/packages/create_dependency_service.rb b/app/services/packages/create_dependency_service.rb
index 2c80ec66dbc..10a86e44cb0 100644
--- a/app/services/packages/create_dependency_service.rb
+++ b/app/services/packages/create_dependency_service.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
module Packages
+ # rubocop: disable Gitlab/BulkInsert
class CreateDependencyService < BaseService
attr_reader :package, :dependencies
@@ -51,7 +52,7 @@ module Packages
}
end
- ids = database.bulk_insert(Packages::Dependency.table_name, rows, return_ids: true, on_conflict: :do_nothing)
+ ids = ApplicationRecord.legacy_bulk_insert(Packages::Dependency.table_name, rows, return_ids: true, on_conflict: :do_nothing)
return ids if ids.size == names_and_version_patterns.size
Packages::Dependency.uncached do
@@ -72,11 +73,8 @@ module Packages
}
end
- database.bulk_insert(Packages::DependencyLink.table_name, rows)
- end
-
- def database
- ::Gitlab::Database.main
+ ApplicationRecord.legacy_bulk_insert(Packages::DependencyLink.table_name, rows)
end
end
+ # rubocop: enable Gitlab/BulkInsert
end
diff --git a/app/services/packages/nuget/create_dependency_service.rb b/app/services/packages/nuget/create_dependency_service.rb
index 3fc42056d43..85f295ac7b7 100644
--- a/app/services/packages/nuget/create_dependency_service.rb
+++ b/app/services/packages/nuget/create_dependency_service.rb
@@ -41,7 +41,7 @@ module Packages
}
end
- ::Gitlab::Database.main.bulk_insert(::Packages::Nuget::DependencyLinkMetadatum.table_name, rows.compact) # rubocop:disable Gitlab/BulkInsert
+ ::ApplicationRecord.legacy_bulk_insert(::Packages::Nuget::DependencyLinkMetadatum.table_name, rows.compact) # rubocop:disable Gitlab/BulkInsert
end
def raw_dependency_for(dependency)
diff --git a/app/services/packages/update_tags_service.rb b/app/services/packages/update_tags_service.rb
index 2bdf75a6617..f29c54dacb9 100644
--- a/app/services/packages/update_tags_service.rb
+++ b/app/services/packages/update_tags_service.rb
@@ -15,7 +15,7 @@ module Packages
tags_to_create = @tags - existing_tags
@package.tags.with_name(tags_to_destroy).delete_all if tags_to_destroy.any?
- ::Gitlab::Database.main.bulk_insert(Packages::Tag.table_name, rows(tags_to_create)) if tags_to_create.any? # rubocop:disable Gitlab/BulkInsert
+ ::ApplicationRecord.legacy_bulk_insert(Packages::Tag.table_name, rows(tags_to_create)) if tags_to_create.any? # rubocop:disable Gitlab/BulkInsert
end
private
diff --git a/app/services/projects/detect_repository_languages_service.rb b/app/services/projects/detect_repository_languages_service.rb
index 0356a6b0ccd..9db0b71d106 100644
--- a/app/services/projects/detect_repository_languages_service.rb
+++ b/app/services/projects/detect_repository_languages_service.rb
@@ -21,7 +21,7 @@ module Projects
.update_all(share: update[:share])
end
- Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
RepositoryLanguage.table_name,
detection.insertions(matching_programming_languages)
)
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index 7c00b9e6105..cf3cc5cd8e0 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -38,7 +38,7 @@ module Projects
rows = existent_lfs_objects
.not_linked_to_project(project)
.map { |existing_lfs_object| { project_id: project.id, lfs_object_id: existing_lfs_object.id } }
- Gitlab::Database.main.bulk_insert(:lfs_objects_projects, rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(:lfs_objects_projects, rows) # rubocop:disable Gitlab/BulkInsert
iterations += 1
linked_existing_objects += existent_lfs_objects.map(&:oid)
diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb
index bc2d3a946cc..03ac839c509 100644
--- a/app/services/resource_events/change_labels_service.rb
+++ b/app/services/resource_events/change_labels_service.rb
@@ -23,7 +23,7 @@ module ResourceEvents
label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['remove'])
end
- Gitlab::Database.main.bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert
resource.expire_note_etag_cache
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue)
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
index eb98ed57d55..239cd86e0ec 100644
--- a/app/services/suggestions/create_service.rb
+++ b/app/services/suggestions/create_service.rb
@@ -25,7 +25,7 @@ module Suggestions
end
rows.in_groups_of(100, false) do |rows|
- Gitlab::Database.main.bulk_insert('suggestions', rows) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert('suggestions', rows) # rubocop:disable Gitlab/BulkInsert
end
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_add_suggestion_action(note: @note)
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 681e7ccb613..4197d5b961f 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -153,9 +153,9 @@
%span.float-right
#{Rails::VERSION::STRING}
%p
- = Gitlab::Database.main.human_adapter_name
+ = ApplicationRecord.database.human_adapter_name
%span.float-right
- = Gitlab::Database.main.version
+ = ApplicationRecord.database.version
%p
= _('Redis')
%span.float-right
diff --git a/app/views/admin/users/_tabs.html.haml b/app/views/admin/users/_tabs.html.haml
index 90f06eeaf3f..6c14e1189fe 100644
--- a/app/views/admin/users/_tabs.html.haml
+++ b/app/views/admin/users/_tabs.html.haml
@@ -1,7 +1,3 @@
-%ul.nav-links.nav-tabs.nav.js-users-tabs{ role: 'tablist' }
- %li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: admin_users_path, class: active_when(current_page?(admin_users_path)), role: 'tab' }
- = s_('AdminUsers|Users')
- %li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: admin_cohorts_path, class: active_when(current_page?(admin_cohorts_path)), role: 'tab' }
- = s_('AdminUsers|Cohorts')
+= gl_tabs_nav({ class: 'js-users-tabs' }) do
+ = gl_tab_link_to s_('AdminUsers|Users'), admin_users_path
+ = gl_tab_link_to s_('AdminUsers|Cohorts'), admin_cohorts_path
diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index 45287a01cc9..457e34b306a 100644
--- a/app/views/clusters/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
@@ -3,24 +3,10 @@
= render_gcp_signup_offer
-.clusters-container.gl-my-2
+.clusters-container
- if display_cluster_agents?(clusterable)
- .js-toggle-container
- %ul.nav-links.nav-tabs.nav{ role: 'tablist' }
- %li.nav-item{ role: 'presentation' }
- %a.nav-link.active{ href: "#certificate-clusters-pane", id: "certificate-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
- %span= s_('ClusterIntegration|Clusters connected with a certificate')
-
- %li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: "#agent-clusters-pane", id: "agent-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
- %span= s_('ClusterIntegration|GitLab Agent managed clusters')
-
- .tab-content
- .tab-pane.active{ id: 'certificate-clusters-pane', role: 'tabpanel' }
- = render 'cluster_list', clusters: @clusters
-
- .tab-pane{ id: 'agent-clusters-pane', role: 'tabpanel' }
- #js-cluster-agents-list{ data: js_cluster_agents_list_data(clusterable) }
+ .gl-my-6
+ .js-clusters-main-view{ data: js_clusters_data(clusterable) }
- else
= render 'cluster_list', clusters: @clusters
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index eb9caa796b5..0d904de9372 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -10,21 +10,20 @@
= s_('UserProfile|Retry')
.user-calendar-activities
- if @user.user_readme
- .row
- .col-12.gl-my-6
- .gl-border-gray-100.gl-border-1.gl-border-solid.gl-rounded-small.gl-py-4.gl-px-6
- .gl-display-flex
- %ol.breadcrumb.gl-breadcrumb-list.gl-mb-4
- %li.breadcrumb-item.gl-breadcrumb-item
- = link_to @user.username, project_path(@user.user_project)
- %span.gl-breadcrumb-separator
- = sprite_icon("chevron-right", size: 16)
- %li.breadcrumb-item.gl-breadcrumb-item
- = link_to @user.user_readme.path, @user.user_project.readme_url
- - if current_user == @user
- .gl-ml-auto
- = link_to _('Edit'), edit_blob_path(@user.user_project, @user.user_project.default_branch, @user.user_readme.path)
- = render 'projects/blob/viewer', viewer: @user.user_readme.rich_viewer, load_async: false
+ .row.justify-content-center
+ .col-12.col-md-10.col-lg-8.gl-my-6
+ .gl-display-flex
+ %ol.breadcrumb.gl-breadcrumb-list.gl-mb-4
+ %li.breadcrumb-item.gl-breadcrumb-item
+ = link_to @user.username, project_path(@user.user_project)
+ %span.gl-breadcrumb-separator
+ = sprite_icon("chevron-right", size: 16)
+ %li.breadcrumb-item.gl-breadcrumb-item
+ = link_to @user.user_readme.path, @user.user_project.readme_url
+ - if current_user == @user
+ .gl-ml-auto
+ = link_to _('Edit'), edit_blob_path(@user.user_project, @user.user_project.default_branch, @user.user_readme.path)
+ = render 'projects/blob/viewer', viewer: @user.user_readme.rich_viewer, load_async: false
.row
%div{ class: activity_pane_class }
- if can?(current_user, :read_cross_project)
diff --git a/app/workers/gitlab/jira_import/import_issue_worker.rb b/app/workers/gitlab/jira_import/import_issue_worker.rb
index eabe7328b92..3824cc1f3ef 100644
--- a/app/workers/gitlab/jira_import/import_issue_worker.rb
+++ b/app/workers/gitlab/jira_import/import_issue_worker.rb
@@ -54,7 +54,7 @@ module Gitlab
label_link_attrs << build_label_attrs(issue_id, import_label_id.to_i)
- Gitlab::Database.main.bulk_insert(LabelLink.table_name, label_link_attrs) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(LabelLink.table_name, label_link_attrs) # rubocop:disable Gitlab/BulkInsert
end
def assign_issue(project_id, issue_id, assignee_ids)
@@ -62,7 +62,7 @@ module Gitlab
assignee_attrs = assignee_ids.map { |user_id| { issue_id: issue_id, user_id: user_id } }
- Gitlab::Database.main.bulk_insert(IssueAssignee.table_name, assignee_attrs) # rubocop:disable Gitlab/BulkInsert
+ ApplicationRecord.legacy_bulk_insert(IssueAssignee.table_name, assignee_attrs) # rubocop:disable Gitlab/BulkInsert
end
def build_label_attrs(issue_id, label_id)