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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml1
-rw-r--r--app/assets/javascripts/diffs/components/app.vue77
-rw-r--r--app/assets/javascripts/diffs/constants.js1
-rw-r--r--app/assets/javascripts/diffs/index.js4
-rw-r--r--app/assets/javascripts/diffs/store/actions.js67
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js28
-rw-r--r--app/assets/javascripts/diffs/store/utils.js5
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue20
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue6
-rw-r--r--app/assets/javascripts/environments/index.js2
-rw-r--r--app/controllers/clusters/clusters_controller.rb10
-rw-r--r--app/models/clusters/cluster.rb4
-rw-r--r--app/presenters/clusterable_presenter.rb4
-rw-r--r--app/presenters/group_clusterable_presenter.rb5
-rw-r--r--app/presenters/instance_clusterable_presenter.rb5
-rw-r--r--app/presenters/project_clusterable_presenter.rb5
-rw-r--r--app/serializers/diff_file_metadata_entity.rb3
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml8
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml4
-rw-r--r--changelogs/unreleased/31759-clear-cluster-cache.yml5
-rw-r--r--changelogs/unreleased/32557-convert-generic-epic-error-banners-to-form-validation-messages.yml5
-rw-r--r--changelogs/unreleased/env-tooltips.yml5
-rw-r--r--config/routes.rb1
-rw-r--r--doc/ci/yaml/README.md11
-rw-r--r--doc/user/group/clusters/index.md15
-rw-r--r--doc/user/project/clusters/index.md15
-rw-r--r--locale/gitlab.pot24
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb27
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb36
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb32
-rw-r--r--spec/features/merge_request/user_expands_diff_spec.rb3
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb1
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb1
-rw-r--r--spec/features/projects/view_on_env_spec.rb1
-rw-r--r--spec/javascripts/diffs/components/app_spec.js67
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js89
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js27
-rw-r--r--spec/javascripts/environments/environments_app_spec.js1
-rw-r--r--spec/lib/gitlab/ci/build/context/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/context/global_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb16
-rw-r--r--spec/presenters/group_clusterable_presenter_spec.rb6
-rw-r--r--spec/presenters/instance_clusterable_presenter_spec.rb6
-rw-r--r--spec/presenters/project_clusterable_presenter_spec.rb6
-rw-r--r--spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb4
51 files changed, 622 insertions, 53 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 1d5cf7642c2..27dce2239d8 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -56,7 +56,6 @@ Style/FrozenStringLiteralComment:
- 'qa/**/*'
- 'rubocop/**/*'
- 'scripts/**/*'
- - 'spec/lib/gitlab/**/*'
RSpec/FilePath:
Exclude:
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 19b85710710..8039a9a7602 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -1,5 +1,6 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import createFlash from '~/flash';
@@ -36,11 +37,20 @@ export default {
GlLoadingIcon,
PanelResizer,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
endpoint: {
type: String,
required: true,
},
+ endpointMetadata: {
+ type: String,
+ required: true,
+ },
+ endpointBatch: {
+ type: String,
+ required: true,
+ },
projectPath: {
type: String,
required: true,
@@ -92,6 +102,7 @@ export default {
computed: {
...mapState({
isLoading: state => state.diffs.isLoading,
+ isBatchLoading: state => state.diffs.isBatchLoading,
diffFiles: state => state.diffs.diffFiles,
diffViewType: state => state.diffs.diffViewType,
mergeRequestDiffs: state => state.diffs.mergeRequestDiffs,
@@ -153,6 +164,8 @@ export default {
mounted() {
this.setBaseConfig({
endpoint: this.endpoint,
+ endpointMetadata: this.endpointMetadata,
+ endpointBatch: this.endpointBatch,
projectPath: this.projectPath,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
@@ -185,6 +198,8 @@ export default {
...mapActions('diffs', [
'setBaseConfig',
'fetchDiffFiles',
+ 'fetchDiffFilesMeta',
+ 'fetchDiffFilesBatch',
'startRenderDiffsQueue',
'assignDiscussionsToDiff',
'setHighlightedRow',
@@ -196,24 +211,51 @@ export default {
this.assignedDiscussions = false;
this.fetchData(false);
},
+ isLatestVersion() {
+ return window.location.search.indexOf('diff_id') === -1;
+ },
fetchData(toggleTree = true) {
- this.fetchDiffFiles()
- .then(() => {
- if (toggleTree) {
- this.hideTreeListIfJustOneFile();
- }
+ if (this.isLatestVersion() && this.glFeatures.diffsBatchLoad) {
+ this.fetchDiffFilesMeta()
+ .then(() => {
+ if (toggleTree) this.hideTreeListIfJustOneFile();
+ })
+ .catch(() => {
+ createFlash(__('Something went wrong on our end. Please try again!'));
+ });
- requestIdleCallback(
- () => {
- this.setDiscussions();
- this.startRenderDiffsQueue();
- },
- { timeout: 1000 },
- );
- })
- .catch(() => {
- createFlash(__('Something went wrong on our end. Please try again!'));
- });
+ this.fetchDiffFilesBatch()
+ .then(() => {
+ requestIdleCallback(
+ () => {
+ this.setDiscussions();
+ this.startRenderDiffsQueue();
+ },
+ { timeout: 1000 },
+ );
+ })
+ .catch(() => {
+ createFlash(__('Something went wrong on our end. Please try again!'));
+ });
+ } else {
+ this.fetchDiffFiles()
+ .then(() => {
+ if (toggleTree) {
+ this.hideTreeListIfJustOneFile();
+ }
+
+ requestIdleCallback(
+ () => {
+ this.setDiscussions();
+ this.startRenderDiffsQueue();
+ },
+ { timeout: 1000 },
+ );
+ })
+ .catch(() => {
+ createFlash(__('Something went wrong on our end. Please try again!'));
+ });
+ }
if (!this.isNotesFetched) {
eventHub.$emit('fetchNotesData');
@@ -324,7 +366,8 @@ export default {
}"
>
<commit-widget v-if="commit" :commit="commit" />
- <template v-if="renderDiffFiles">
+ <div v-if="isBatchLoading" class="loading"><gl-loading-icon /></div>
+ <template v-else-if="renderDiffFiles">
<diff-file
v-for="file in diffFiles"
:key="file.newPath"
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index d84e1af11f3..9de4c38bdf0 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -57,3 +57,4 @@ export const MIN_RENDERING_MS = 2;
export const START_RENDERING_INDEX = 200;
export const INLINE_DIFF_LINES_KEY = 'highlighted_diff_lines';
export const PARALLEL_DIFF_LINES_KEY = 'parallel_diff_lines';
+export const DIFFS_PER_PAGE = 10;
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index c9580e3d3b4..375ac80021f 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -67,6 +67,8 @@ export default function initDiffsApp(store) {
return {
endpoint: dataset.endpoint,
+ endpointMetadata: dataset.endpointMetadata || '',
+ endpointBatch: dataset.endpointBatch || '',
projectPath: dataset.projectPath,
helpPagePath: dataset.helpPagePath,
currentUser: JSON.parse(dataset.currentUserData) || {},
@@ -100,6 +102,8 @@ export default function initDiffsApp(store) {
return createElement('diffs-app', {
props: {
endpoint: this.endpoint,
+ endpointMetadata: this.endpointMetadata,
+ endpointBatch: this.endpointBatch,
currentUser: this.currentUser,
projectPath: this.projectPath,
helpPagePath: this.helpPagePath,
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 6695d9fe96c..d4594399ff5 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -13,6 +13,7 @@ import {
convertExpandLines,
idleCallback,
allDiscussionWrappersExpanded,
+ prepareDiffData,
} from './utils';
import * as types from './mutation_types';
import {
@@ -33,12 +34,27 @@ import {
START_RENDERING_INDEX,
INLINE_DIFF_LINES_KEY,
PARALLEL_DIFF_LINES_KEY,
+ DIFFS_PER_PAGE,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
export const setBaseConfig = ({ commit }, options) => {
- const { endpoint, projectPath, dismissEndpoint, showSuggestPopover } = options;
- commit(types.SET_BASE_CONFIG, { endpoint, projectPath, dismissEndpoint, showSuggestPopover });
+ const {
+ endpoint,
+ endpointMetadata,
+ endpointBatch,
+ projectPath,
+ dismissEndpoint,
+ showSuggestPopover,
+ } = options;
+ commit(types.SET_BASE_CONFIG, {
+ endpoint,
+ endpointMetadata,
+ endpointBatch,
+ projectPath,
+ dismissEndpoint,
+ showSuggestPopover,
+ });
};
export const fetchDiffFiles = ({ state, commit }) => {
@@ -67,6 +83,53 @@ export const fetchDiffFiles = ({ state, commit }) => {
.catch(() => worker.terminate());
};
+export const fetchDiffFilesBatch = ({ commit, state }) => {
+ const baseUrl = `${state.endpointBatch}?per_page=${DIFFS_PER_PAGE}`;
+ const url = page => (page ? `${baseUrl}&page=${page}` : baseUrl);
+
+ commit(types.SET_BATCH_LOADING, true);
+
+ const getBatch = page =>
+ axios
+ .get(url(page))
+ .then(({ data: { pagination, diff_files } }) => {
+ commit(types.SET_DIFF_DATA_BATCH, { diff_files });
+ commit(types.SET_BATCH_LOADING, false);
+ return pagination.next_page;
+ })
+ .then(nextPage => nextPage && getBatch(nextPage));
+
+ return getBatch()
+ .then(handleLocationHash)
+ .catch(() => null);
+};
+
+export const fetchDiffFilesMeta = ({ commit, state }) => {
+ const worker = new TreeWorker();
+
+ commit(types.SET_LOADING, true);
+
+ worker.addEventListener('message', ({ data }) => {
+ commit(types.SET_TREE_DATA, data);
+
+ worker.terminate();
+ });
+
+ return axios
+ .get(state.endpointMetadata)
+ .then(({ data }) => {
+ const strippedData = { ...data };
+ strippedData.diff_files = [];
+ commit(types.SET_LOADING, false);
+ commit(types.SET_MERGE_REQUEST_DIFFS, data.merge_request_diffs || []);
+ commit(types.SET_DIFF_DATA, strippedData);
+
+ prepareDiffData(data);
+ worker.postMessage(data.diff_files);
+ })
+ .catch(() => worker.terminate());
+};
+
export const setHighlightedRow = ({ commit }, lineCode) => {
const fileHash = lineCode.split('_')[0];
commit(types.SET_HIGHLIGHTED_ROW, lineCode);
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 6821c8445ea..8c52e3178e5 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -8,6 +8,7 @@ const defaultViewType = INLINE_DIFF_VIEW_TYPE;
export default () => ({
isLoading: true,
+ isBatchLoading: false,
addedLines: null,
removedLines: null,
endpoint: '',
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 9db56331faa..5a90d78b2bc 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -1,6 +1,8 @@
export const SET_BASE_CONFIG = 'SET_BASE_CONFIG';
export const SET_LOADING = 'SET_LOADING';
+export const SET_BATCH_LOADING = 'SET_BATCH_LOADING';
export const SET_DIFF_DATA = 'SET_DIFF_DATA';
+export const SET_DIFF_DATA_BATCH = 'SET_DIFF_DATA_BATCH';
export const SET_DIFF_VIEW_TYPE = 'SET_DIFF_VIEW_TYPE';
export const SET_MERGE_REQUEST_DIFFS = 'SET_MERGE_REQUEST_DIFFS';
export const TOGGLE_LINE_HAS_FORM = 'TOGGLE_LINE_HAS_FORM';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index a6915a46c00..de2f68d729c 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -12,14 +12,32 @@ import * as types from './mutation_types';
export default {
[types.SET_BASE_CONFIG](state, options) {
- const { endpoint, projectPath, dismissEndpoint, showSuggestPopover } = options;
- Object.assign(state, { endpoint, projectPath, dismissEndpoint, showSuggestPopover });
+ const {
+ endpoint,
+ endpointMetadata,
+ endpointBatch,
+ projectPath,
+ dismissEndpoint,
+ showSuggestPopover,
+ } = options;
+ Object.assign(state, {
+ endpoint,
+ endpointMetadata,
+ endpointBatch,
+ projectPath,
+ dismissEndpoint,
+ showSuggestPopover,
+ });
},
[types.SET_LOADING](state, isLoading) {
Object.assign(state, { isLoading });
},
+ [types.SET_BATCH_LOADING](state, isBatchLoading) {
+ Object.assign(state, { isBatchLoading });
+ },
+
[types.SET_DIFF_DATA](state, data) {
prepareDiffData(data);
@@ -28,6 +46,12 @@ export default {
});
},
+ [types.SET_DIFF_DATA_BATCH](state, data) {
+ prepareDiffData(data);
+
+ state.diffFiles.push(...data.diff_files);
+ },
+
[types.RENDER_FILE](state, file) {
Object.assign(file, {
renderIt: true,
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index d46bdea9b50..2326018b999 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -252,10 +252,11 @@ export function prepareDiffData(diffData) {
showingLines += file.parallel_diff_lines.length;
}
+ const name = (file.viewer && file.viewer.name) || diffViewerModes.text;
+
Object.assign(file, {
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
- collapsed:
- file.viewer.name === diffViewerModes.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ collapsed: name === diffViewerModes.text && showingLines > MAX_LINES_TO_BE_RENDERED,
isShowingFullFile: false,
isLoadingFullFile: false,
discussions: [],
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index c94039326aa..dfd4d5474ff 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -6,6 +6,7 @@ import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import environmentItemMixin from 'ee_else_ce/environments/mixins/environment_item_mixin';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
@@ -26,7 +27,6 @@ const timeagoInstance = new Timeago();
export default {
components: {
- UserAvatarLink,
CommitComponent,
Icon,
ActionsComponent,
@@ -35,6 +35,8 @@ export default {
RollbackComponent,
TerminalButtonComponent,
MonitoringButtonComponent,
+ TooltipOnTruncate,
+ UserAvatarLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -508,12 +510,16 @@ export default {
</div>
<div class="table-section section-15 d-none d-sm-none d-md-block" role="gridcell">
- <a
- v-if="shouldRenderBuildName"
- :href="buildPath"
- class="build-link cgray flex-truncate-parent"
- >
- <span class="flex-truncate-child">{{ buildName }}</span>
+ <a v-if="shouldRenderBuildName" :href="buildPath" class="build-link cgray">
+ <tooltip-on-truncate
+ :title="buildName"
+ truncate-target="child"
+ class="flex-truncate-parent"
+ >
+ <span class="flex-truncate-child">
+ {{ buildName }}
+ </span>
+ </tooltip-on-truncate>
</a>
</div>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 81927d18f8b..50c667e6966 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -31,10 +31,6 @@ export default {
type: Boolean,
required: true,
},
- cssContainerClass: {
- type: String,
- required: true,
- },
newEnvironmentPath: {
type: String,
required: true,
@@ -93,7 +89,7 @@ export default {
};
</script>
<template>
- <div :class="cssContainerClass">
+ <div>
<stop-environment-modal :environment="environmentInStopModal" />
<confirm-rollback-modal :environment="environmentInRollbackModal" />
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index dcdaf8731f8..9a68619d4f7 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -21,7 +21,6 @@ export default () =>
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
deployBoardsHelpPath: environmentsData.deployBoardsHelpPath,
- cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: parseBoolean(environmentsData.canCreateEnvironment),
canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment),
};
@@ -33,7 +32,6 @@ export default () =>
newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath,
deployBoardsHelpPath: this.deployBoardsHelpPath,
- cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canReadEnvironment: this.canReadEnvironment,
...this.canaryCalloutProps,
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 673ead04709..9da8ad229fe 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -3,14 +3,14 @@
class Clusters::ClustersController < Clusters::BaseController
include RoutableActions
- before_action :cluster, only: [:cluster_status, :show, :update, :destroy]
+ before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache]
before_action :generate_gcp_authorize_url, only: [:new]
before_action :validate_gcp_token, only: [:new]
before_action :gcp_cluster, only: [:new]
before_action :user_cluster, only: [:new]
before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role, :revoke_aws_role, :aws_proxy]
before_action :authorize_update_cluster!, only: [:update]
- before_action :authorize_admin_cluster!, only: [:destroy]
+ before_action :authorize_admin_cluster!, only: [:destroy, :clear_cache]
before_action :update_applications_status, only: [:cluster_status]
before_action only: [:new, :create_gcp] do
push_frontend_feature_flag(:create_eks_clusters)
@@ -169,6 +169,12 @@ class Clusters::ClustersController < Clusters::BaseController
render json: response.body, status: response.status
end
+ def clear_cache
+ cluster.delete_cached_resources!
+
+ redirect_to cluster.show_path, notice: _('Cluster cache cleared.')
+ end
+
private
def destroy_params
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 98e754a1370..62b2217a9af 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -267,6 +267,10 @@ module Clusters
end
end
+ def delete_cached_resources!
+ kubernetes_namespaces.delete_all(:delete_all)
+ end
+
private
def unique_management_project_environment_scope
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 2306f55f1f4..7677e6f026f 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -65,6 +65,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError
end
+ def clear_cluster_cache_path(cluster)
+ raise NotImplementedError
+ end
+
def cluster_path(cluster, params = {})
raise NotImplementedError
end
diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb
index 54cea19b18e..21db2f6f96b 100644
--- a/app/presenters/group_clusterable_presenter.rb
+++ b/app/presenters/group_clusterable_presenter.rb
@@ -19,6 +19,11 @@ class GroupClusterablePresenter < ClusterablePresenter
update_applications_group_cluster_path(clusterable, cluster, application)
end
+ override :clear_cluster_cache_path
+ def clear_cluster_cache_path(cluster)
+ clear_cache_group_cluster_path(clusterable, cluster)
+ end
+
override :cluster_path
def cluster_path(cluster, params = {})
group_cluster_path(clusterable, cluster, params)
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
index c6572e8ce71..34d3f347689 100644
--- a/app/presenters/instance_clusterable_presenter.rb
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -37,6 +37,11 @@ class InstanceClusterablePresenter < ClusterablePresenter
update_applications_admin_cluster_path(cluster, application)
end
+ override :clear_cluster_cache_path
+ def clear_cluster_cache_path(cluster)
+ clear_cache_admin_cluster_path(cluster)
+ end
+
override :cluster_path
def cluster_path(cluster, params = {})
admin_cluster_path(cluster, params)
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
index 3fab69fff7a..5c56d42ed27 100644
--- a/app/presenters/project_clusterable_presenter.rb
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -19,6 +19,11 @@ class ProjectClusterablePresenter < ClusterablePresenter
update_applications_project_cluster_path(clusterable, cluster, application)
end
+ override :clear_cluster_cache_path
+ def clear_cluster_cache_path(cluster)
+ clear_cache_project_cluster_path(clusterable, cluster)
+ end
+
override :cluster_path
def cluster_path(cluster, params = {})
project_cluster_path(clusterable, cluster, params)
diff --git a/app/serializers/diff_file_metadata_entity.rb b/app/serializers/diff_file_metadata_entity.rb
index 500a844b170..05280518f39 100644
--- a/app/serializers/diff_file_metadata_entity.rb
+++ b/app/serializers/diff_file_metadata_entity.rb
@@ -7,4 +7,7 @@ class DiffFileMetadataEntity < Grape::Entity
expose :old_path
expose :new_file?, as: :new_file
expose :deleted_file?, as: :deleted_file
+ expose :file_hash do |diff_file|
+ Digest::SHA1.hexdigest(diff_file.file_path)
+ end
end
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 59cdf2016fb..5e34b457231 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -28,6 +28,14 @@
.form-group
= field.submit _('Save changes'), class: 'btn btn-success qa-save-domain'
+ - if @cluster.managed?
+ .sub-section.form-group
+ %h4
+ = s_('ClusterIntegration|Clear cluster cache')
+ %p
+ = s_("ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts.")
+ = link_to(s_('ClusterIntegration|Clear cluster cache'), clusterable.clear_cluster_cache_path(@cluster), method: :delete, class: 'btn btn-primary')
+
.sub-section.form-group
%h4.text-danger
= s_('ClusterIntegration|Remove Kubernetes cluster integration')
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index dee6bc8bae4..c2bc5376fd7 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -78,6 +78,8 @@
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
#js-diffs-app.diffs.tab-pane{ data: { "is-locked" => @merge_request.discussion_locked?,
endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
+ endpoint_metadata: diffs_metadata_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
+ endpoint_batch: diffs_batch_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
help_page_path: suggest_changes_help_path,
current_user_data: @current_user_data,
project_path: project_path(@merge_request.project),
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index d341520e4a2..5da86195243 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -6,7 +6,7 @@
.issues-filters{ class: ("w-100" if type == :boards_modal) }
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class, "v-pre" => type == :boards_modal }
- .d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0
+ .d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0.w-100
- if type == :boards
= render "shared/boards/switcher", board: board
= form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
@@ -162,8 +162,8 @@
%button.clear-search.hidden{ type: 'button' }
= icon('times')
.filter-dropdown-container.d-flex.flex-column.flex-md-row
- #js-board-labels-toggle
- if type == :boards
+ #js-board-labels-toggle
.js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
- if user_can_admin_list
= render 'shared/issuable/board_create_list_dropdown', board: board
diff --git a/changelogs/unreleased/31759-clear-cluster-cache.yml b/changelogs/unreleased/31759-clear-cluster-cache.yml
new file mode 100644
index 00000000000..29e0dda43ec
--- /dev/null
+++ b/changelogs/unreleased/31759-clear-cluster-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Add option to delete cached Kubernetes namespaces
+merge_request: 20411
+author:
+type: added
diff --git a/changelogs/unreleased/32557-convert-generic-epic-error-banners-to-form-validation-messages.yml b/changelogs/unreleased/32557-convert-generic-epic-error-banners-to-form-validation-messages.yml
new file mode 100644
index 00000000000..d40cfa2fce3
--- /dev/null
+++ b/changelogs/unreleased/32557-convert-generic-epic-error-banners-to-form-validation-messages.yml
@@ -0,0 +1,5 @@
+---
+title: Convert flash epic error to form validation error
+merge_request: 20130
+author:
+type: changed
diff --git a/changelogs/unreleased/env-tooltips.yml b/changelogs/unreleased/env-tooltips.yml
new file mode 100644
index 00000000000..f2d33bea1e5
--- /dev/null
+++ b/changelogs/unreleased/env-tooltips.yml
@@ -0,0 +1,5 @@
+---
+title: Fix tooltip hovers in environments table
+merge_request: 20737
+author:
+type: fixed
diff --git a/config/routes.rb b/config/routes.rb
index 9fb4d94f068..c98c0358336 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -166,6 +166,7 @@ Rails.application.routes.draw do
end
get :cluster_status, format: :json
+ delete :clear_cache
end
end
end
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 05b660ccdf6..ce3d6247d86 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -184,6 +184,17 @@ that the YAML parser knows to interpret the whole thing as a string rather than
a "key: value" pair. Be careful when using special characters:
`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``.
+If any of the script commands return an exit code different from zero, the job
+will fail and further commands will not be executed. This behavior can be avoided by
+storing the exit code in a variable:
+
+```yaml
+job:
+ script:
+ - false && true; exit_code=$?
+ - if [ $exit_code -ne 0 ]; then echo "Previous command failed"; fi;
+```
+
#### YAML anchors for `script`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/23005) in GitLab 12.5.
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 1fe456902a2..2b36c3bdf5b 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -75,6 +75,21 @@ NOTE: **Note:**
If you [install applications](#installing-applications) on your cluster, GitLab will create
the resources required to run these even if you have chosen to manage your own cluster.
+### Clearing the cluster cache
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/31759) in GitLab 12.6.
+
+If you choose to allow GitLab to manage your cluster for you, GitLab stores a cached
+version of the namespaces and service accounts it creates for your projects. If you
+modify these resources in your cluster manually, this cache can fall out of sync with
+your cluster, which can cause deployment jobs to fail.
+
+To clear the cache:
+
+1. Navigate to your group’s **Kubernetes** page, and select your cluster.
+1. Expand the **Advanced settings** section.
+1. Click **Clear cluster cache**.
+
## Base domain
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/24580) in GitLab 11.8.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index c5c2c2c07e7..2aa746fc596 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -132,6 +132,21 @@ NOTE: **Note:**
If you [install applications](#installing-applications) on your cluster, GitLab will create
the resources required to run these even if you have chosen to manage your own cluster.
+#### Clearing the cluster cache
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/31759) in GitLab 12.6.
+
+If you choose to allow GitLab to manage your cluster for you, GitLab stores a cached
+version of the namespaces and service accounts it creates for your projects. If you
+modify these resources in your cluster manually, this cache can fall out of sync with
+your cluster, which can cause deployment jobs to fail.
+
+To clear the cache:
+
+1. Navigate to your project’s **Operations > Kubernetes** page, and select your cluster.
+1. Expand the **Advanced settings** section.
+1. Click **Clear cluster cache**.
+
### Base domain
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/24580) in GitLab 11.8.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e703e87f0d6..799612af9cd 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3469,6 +3469,9 @@ msgstr ""
msgid "Cluster Health"
msgstr ""
+msgid "Cluster cache cleared."
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -3601,6 +3604,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Clear cluster cache"
+msgstr ""
+
+msgid "ClusterIntegration|Clear the local cache of namespace and service accounts. This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts."
+msgstr ""
+
msgid "ClusterIntegration|Cloud Run"
msgstr ""
@@ -6644,6 +6653,9 @@ msgstr ""
msgid "Epic"
msgstr ""
+msgid "Epic cannot be found."
+msgstr ""
+
msgid "Epic events"
msgstr ""
@@ -9611,6 +9623,9 @@ msgstr ""
msgid "Issue board focus mode"
msgstr ""
+msgid "Issue cannot be found."
+msgstr ""
+
msgid "Issue events"
msgstr ""
@@ -17678,6 +17693,9 @@ msgstr ""
msgid "This environment has no deployments yet."
msgstr ""
+msgid "This epic already has the maximum number of child epics."
+msgstr ""
+
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
@@ -19487,12 +19505,6 @@ msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
-msgid "We can't find an epic that matches what you are looking for."
-msgstr ""
-
-msgid "We can't find an issue that matches what you are looking for."
-msgstr ""
-
msgid "We could not determine the path to remove the epic"
msgstr ""
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index ebae931764d..326d0808092 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -448,6 +448,33 @@ describe Admin::ClustersController do
end
end
+ describe 'DELETE clear cluster cache' do
+ let(:cluster) { create(:cluster, :instance) }
+ let!(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: create(:project)
+ )
+ end
+
+ def go
+ delete :clear_cache, params: { id: cluster }
+ end
+
+ it 'deletes the namespaces associated with the cluster' do
+ expect { go }.to change { Clusters::KubernetesNamespace.count }
+
+ expect(response).to redirect_to(admin_cluster_path(cluster))
+ expect(cluster.kubernetes_namespaces).to be_empty
+ end
+
+ describe 'security' do
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ end
+ end
+
describe 'GET #cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, :instance) }
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index d027405703b..d1669c84e3e 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -516,6 +516,42 @@ describe Groups::ClustersController do
end
end
+ describe 'DELETE clear cluster cache' do
+ let(:cluster) { create(:cluster, :group, groups: [group]) }
+ let!(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: create(:project)
+ )
+ end
+
+ def go
+ delete :clear_cache,
+ params: {
+ group_id: group,
+ id: cluster
+ }
+ end
+
+ it 'deletes the namespaces associated with the cluster' do
+ expect { go }.to change { Clusters::KubernetesNamespace.count }
+
+ expect(response).to redirect_to(group_cluster_path(group, cluster))
+ expect(cluster.kubernetes_namespaces).to be_empty
+ end
+
+ describe 'security' do
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(group) }
+ it { expect { go }.to be_allowed_for(:maintainer).of(group) }
+ it { expect { go }.to be_denied_for(:developer).of(group) }
+ it { expect { go }.to be_denied_for(:reporter).of(group) }
+ it { expect { go }.to be_denied_for(:guest).of(group) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ end
+ end
+
describe 'GET cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, cluster_type: :group_type, groups: [group]) }
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 5a0512a042e..9c21b472c15 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -517,6 +517,38 @@ describe Projects::ClustersController do
end
end
+ describe 'DELETE clear cluster cache' do
+ let(:cluster) { create(:cluster, :project, projects: [project]) }
+ let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
+
+ def go
+ delete :clear_cache,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
+ }
+ end
+
+ it 'deletes the namespaces associated with the cluster' do
+ expect { go }.to change { Clusters::KubernetesNamespace.count }
+
+ expect(response).to redirect_to(project_cluster_path(project, cluster))
+ expect(cluster.kubernetes_namespaces).to be_empty
+ end
+
+ describe 'security' do
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(project) }
+ it { expect { go }.to be_allowed_for(:maintainer).of(project) }
+ it { expect { go }.to be_denied_for(:developer).of(project) }
+ it { expect { go }.to be_denied_for(:reporter).of(project) }
+ it { expect { go }.to be_denied_for(:guest).of(project) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ end
+ end
+
describe 'GET cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb
index ba7abd3af2c..9b040271468 100644
--- a/spec/features/merge_request/user_expands_diff_spec.rb
+++ b/spec/features/merge_request/user_expands_diff_spec.rb
@@ -8,6 +8,7 @@ describe 'User expands diff', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes)
allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes)
@@ -20,7 +21,7 @@ describe 'User expands diff', :js do
it_behaves_like 'rendering a single diff version'
it 'allows user to expand diff' do
- page.within find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd"]') do
+ page.within find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9"]') do
click_link 'Click to expand it.'
wait_for_requests
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 7cb46d90092..9cbea8a8466 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -21,6 +21,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
end
it_behaves_like 'rendering a single diff version'
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index e882b401122..70afe056c64 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -22,6 +22,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
project.add_maintainer(user)
sign_in user
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 82dd779577c..de142344c26 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -11,6 +11,7 @@ describe 'Merge request > User sees diff', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
end
it_behaves_like 'rendering a single diff version'
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index c3fce9761df..b3aef601c7b 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -17,6 +17,7 @@ describe 'Merge request > User sees versions', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index 5a29477e597..313f438e23b 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -10,6 +10,7 @@ describe 'User views diffs', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
visit(diffs_project_merge_request_path(project, merge_request))
wait_for_requests
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index 832985f1a30..c2d4cefad12 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -10,6 +10,7 @@ describe 'View on environment', :js do
before do
stub_feature_flags(single_mr_diff_view: false)
+ stub_feature_flags(diffs_batch_load: false)
project.add_maintainer(user)
end
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index fdf8bcee756..52f7674a7b3 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -34,6 +34,8 @@ describe('diffs/components/app', () => {
localVue,
propsData: {
endpoint: `${TEST_HOST}/diff/endpoint`,
+ endpointMetadata: `${TEST_HOST}/diff/endpointMetadata`,
+ endpointBatch: `${TEST_HOST}/diff/endpointBatch`,
projectPath: 'namespace/project',
currentUser: {},
changesEmptyStateIllustration: '',
@@ -42,6 +44,11 @@ describe('diffs/components/app', () => {
...props,
},
store,
+ methods: {
+ isLatestVersion() {
+ return true;
+ },
+ },
});
}
@@ -59,6 +66,58 @@ describe('diffs/components/app', () => {
wrapper.destroy();
});
+ describe('fetch diff methods', () => {
+ beforeEach(() => {
+ spyOn(window, 'requestIdleCallback').and.callFake(fn => fn());
+ createComponent();
+ spyOn(wrapper.vm, 'fetchDiffFiles').and.callFake(() => Promise.resolve());
+ spyOn(wrapper.vm, 'fetchDiffFilesMeta').and.callFake(() => Promise.resolve());
+ spyOn(wrapper.vm, 'fetchDiffFilesBatch').and.callFake(() => Promise.resolve());
+ spyOn(wrapper.vm, 'setDiscussions');
+ spyOn(wrapper.vm, 'startRenderDiffsQueue');
+ });
+
+ it('calls fetchDiffFiles if diffsBatchLoad is not enabled', () => {
+ wrapper.vm.glFeatures.diffsBatchLoad = false;
+ wrapper.vm.fetchData(false);
+
+ expect(wrapper.vm.fetchDiffFiles).toHaveBeenCalled();
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.setDiscussions).toHaveBeenCalled();
+ expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled();
+ expect(wrapper.vm.fetchDiffFilesMeta).not.toHaveBeenCalled();
+ expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled();
+ });
+ });
+
+ it('calls fetchDiffFiles if diffsBatchLoad is enabled, and not latest version', () => {
+ wrapper.vm.glFeatures.diffsBatchLoad = true;
+ wrapper.vm.isLatestVersion = () => false;
+ wrapper.vm.fetchData(false);
+
+ expect(wrapper.vm.fetchDiffFiles).toHaveBeenCalled();
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.setDiscussions).toHaveBeenCalled();
+ expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled();
+ expect(wrapper.vm.fetchDiffFilesMeta).not.toHaveBeenCalled();
+ expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled();
+ });
+ });
+
+ it('calls batch methods if diffsBatchLoad is enabled, and latest version', () => {
+ wrapper.vm.glFeatures.diffsBatchLoad = true;
+ wrapper.vm.fetchData(false);
+
+ expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled();
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.setDiscussions).toHaveBeenCalled();
+ expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled();
+ expect(wrapper.vm.fetchDiffFilesMeta).toHaveBeenCalled();
+ expect(wrapper.vm.fetchDiffFilesBatch).toHaveBeenCalled();
+ });
+ });
+ });
+
it('adds container-limiting classes when showFileTree is false with inline diffs', () => {
createComponent({}, ({ state }) => {
state.diffs.showTreeList = false;
@@ -93,6 +152,14 @@ describe('diffs/components/app', () => {
expect(wrapper.contains(GlLoadingIcon)).toBe(true);
});
+ it('displays loading icon on batch loading', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.isBatchLoading = true;
+ });
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(true);
+ });
+
it('displays diffs container when not loading', () => {
createComponent();
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 874891fcc6e..3235febe0dc 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -8,6 +8,8 @@ import {
import actions, {
setBaseConfig,
fetchDiffFiles,
+ fetchDiffFilesBatch,
+ fetchDiffFilesMeta,
assignDiscussionsToDiff,
removeDiscussionsFromDiff,
startRenderDiffsQueue,
@@ -68,18 +70,41 @@ describe('DiffsStoreActions', () => {
describe('setBaseConfig', () => {
it('should set given endpoint and project path', done => {
const endpoint = '/diffs/set/endpoint';
+ const endpointMetadata = '/diffs/set/endpoint/metadata';
+ const endpointBatch = '/diffs/set/endpoint/batch';
const projectPath = '/root/project';
const dismissEndpoint = '/-/user_callouts';
const showSuggestPopover = false;
testAction(
setBaseConfig,
- { endpoint, projectPath, dismissEndpoint, showSuggestPopover },
- { endpoint: '', projectPath: '', dismissEndpoint: '', showSuggestPopover: true },
+ {
+ endpoint,
+ endpointBatch,
+ endpointMetadata,
+ projectPath,
+ dismissEndpoint,
+ showSuggestPopover,
+ },
+ {
+ endpoint: '',
+ endpointBatch: '',
+ endpointMetadata: '',
+ projectPath: '',
+ dismissEndpoint: '',
+ showSuggestPopover: true,
+ },
[
{
type: types.SET_BASE_CONFIG,
- payload: { endpoint, projectPath, dismissEndpoint, showSuggestPopover },
+ payload: {
+ endpoint,
+ endpointMetadata,
+ endpointBatch,
+ projectPath,
+ dismissEndpoint,
+ showSuggestPopover,
+ },
},
],
[],
@@ -114,6 +139,64 @@ describe('DiffsStoreActions', () => {
});
});
+ describe('fetchDiffFilesBatch', () => {
+ it('should fetch batch diff files', done => {
+ const endpointBatch = '/fetch/diffs_batch';
+ const batch1 = `${endpointBatch}?per_page=10`;
+ const batch2 = `${endpointBatch}?per_page=10&page=2`;
+ const mock = new MockAdapter(axios);
+ const res1 = { diff_files: [], pagination: { next_page: 2 } };
+ const res2 = { diff_files: [], pagination: {} };
+ mock.onGet(batch1).reply(200, res1);
+ mock.onGet(batch2).reply(200, res2);
+
+ testAction(
+ fetchDiffFilesBatch,
+ {},
+ { endpointBatch },
+ [
+ { type: types.SET_BATCH_LOADING, payload: true },
+ { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
+ { type: types.SET_BATCH_LOADING, payload: false },
+ { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: [] } },
+ { type: types.SET_BATCH_LOADING, payload: false },
+ ],
+ [],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('fetchDiffFilesMeta', () => {
+ it('should fetch diff meta information', done => {
+ const endpointMetadata = '/fetch/diffs_meta';
+ const mock = new MockAdapter(axios);
+ const data = { diff_files: [] };
+ const res = { data };
+ mock.onGet(endpointMetadata).reply(200, res);
+
+ testAction(
+ fetchDiffFilesMeta,
+ {},
+ { endpointMetadata },
+ [
+ { type: types.SET_LOADING, payload: true },
+ { type: types.SET_LOADING, payload: false },
+ { type: types.SET_MERGE_REQUEST_DIFFS, payload: [] },
+ { type: types.SET_DIFF_DATA, payload: { data, diff_files: [] } },
+ ],
+ [],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+
describe('setHighlightedRow', () => {
it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
testAction(setHighlightedRow, 'ABC_123', {}, [
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 3e033b6c9dc..19bf5bdd592 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -28,6 +28,16 @@ describe('DiffsStoreMutations', () => {
});
});
+ describe('SET_BATCH_LOADING', () => {
+ it('should set loading state', () => {
+ const state = {};
+
+ mutations[types.SET_BATCH_LOADING](state, false);
+
+ expect(state.isBatchLoading).toEqual(false);
+ });
+ });
+
describe('SET_DIFF_DATA', () => {
it('should set diff data type properly', () => {
const state = {};
@@ -45,6 +55,23 @@ describe('DiffsStoreMutations', () => {
});
});
+ describe('SET_DIFFSET_DIFF_DATA_BATCH_DATA', () => {
+ it('should set diff data batch type properly', () => {
+ const state = { diffFiles: [] };
+ const diffMock = {
+ diff_files: [diffFileMockData],
+ };
+
+ mutations[types.SET_DIFF_DATA_BATCH](state, diffMock);
+
+ const firstLine = state.diffFiles[0].parallel_diff_lines[0];
+
+ expect(firstLine.right.text).toBeUndefined();
+ expect(state.diffFiles[0].renderIt).toEqual(true);
+ expect(state.diffFiles[0].collapsed).toEqual(false);
+ });
+ });
+
describe('SET_DIFF_VIEW_TYPE', () => {
it('should set diff view type properly', () => {
const state = {};
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 0dcd8868aba..10d37c86ea7 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -10,7 +10,6 @@ describe('Environment', () => {
endpoint: 'environments.json',
canCreateEnvironment: true,
canReadEnvironment: true,
- cssContainerClass: 'container',
newEnvironmentPath: 'environments/new',
helpPagePath: 'help',
canaryDeploymentFeatureId: 'canary_deployment',
diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb
index 3adde213f59..1b73b9a083d 100644
--- a/spec/lib/gitlab/ci/build/context/build_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Context::Build do
diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb
index 6bc8f862779..65cc41ed3f9 100644
--- a/spec/lib/gitlab/ci/build/context/global_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/global_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Context::Global do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 049db4f7013..7c419a195cd 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -960,4 +960,20 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe '#delete_cached_resources!' do
+ let!(:cluster) { create(:cluster, :project) }
+ let!(:staging_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, namespace: 'staging') }
+ let!(:production_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, namespace: 'production') }
+
+ subject { cluster.delete_cached_resources! }
+
+ it 'deletes associated namespace records' do
+ expect(cluster.kubernetes_namespaces).to match_array([staging_namespace, production_namespace])
+
+ subject
+
+ expect(cluster.kubernetes_namespaces).to be_empty
+ end
+ end
end
diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb
index 11a8decc9cc..d40ca856f7b 100644
--- a/spec/presenters/group_clusterable_presenter_spec.rb
+++ b/spec/presenters/group_clusterable_presenter_spec.rb
@@ -83,6 +83,12 @@ describe GroupClusterablePresenter do
it { is_expected.to eq(update_applications_group_cluster_path(group, cluster, application)) }
end
+ describe '#clear_cluster_cache_path' do
+ subject { presenter.clear_cluster_cache_path(cluster) }
+
+ it { is_expected.to eq(clear_cache_group_cluster_path(group, cluster)) }
+ end
+
describe '#cluster_path' do
subject { presenter.cluster_path(cluster) }
diff --git a/spec/presenters/instance_clusterable_presenter_spec.rb b/spec/presenters/instance_clusterable_presenter_spec.rb
index 9f1268379f5..3e7ee7a0ff6 100644
--- a/spec/presenters/instance_clusterable_presenter_spec.rb
+++ b/spec/presenters/instance_clusterable_presenter_spec.rb
@@ -34,4 +34,10 @@ describe InstanceClusterablePresenter do
it { is_expected.to eq(aws_proxy_admin_clusters_path(resource: resource)) }
end
+
+ describe '#clear_cluster_cache_path' do
+ subject { presenter.clear_cluster_cache_path(cluster) }
+
+ it { is_expected.to eq(clear_cache_admin_cluster_path(cluster)) }
+ end
end
diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb
index 441c2a50fea..b3dad4abde5 100644
--- a/spec/presenters/project_clusterable_presenter_spec.rb
+++ b/spec/presenters/project_clusterable_presenter_spec.rb
@@ -83,6 +83,12 @@ describe ProjectClusterablePresenter do
it { is_expected.to eq(update_applications_project_cluster_path(project, cluster, application)) }
end
+ describe '#clear_cluster_cache_path' do
+ subject { presenter.clear_cluster_cache_path(cluster) }
+
+ it { is_expected.to eq(clear_cache_project_cluster_path(project, cluster)) }
+ end
+
describe '#cluster_path' do
subject { presenter.cluster_path(cluster) }
diff --git a/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb b/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
index 80120629a32..18d025a4b07 100644
--- a/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
+++ b/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
@@ -3,6 +3,10 @@
# This pending test can be removed when `single_mr_diff_view` is enabled by default
# disabling the feature flag above is then not needed anymore.
RSpec.shared_examples 'rendering a single diff version' do |attribute|
+ before do
+ stub_feature_flags(diffs_batch_load: false)
+ end
+
pending 'allows editing diff settings single_mr_diff_view is enabled' do
project = create(:project, :repository)
user = project.creator