Commit 23d23711 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 274dff4f
......@@ -56,7 +56,6 @@ Style/FrozenStringLiteralComment:
- 'qa/**/*'
- 'rubocop/**/*'
- 'scripts/**/*'
- 'spec/lib/gitlab/**/*'
RSpec/FilePath:
Exclude:
......
<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,7 +211,33 @@ export default {
this.assignedDiscussions = false;
this.fetchData(false);
},
isLatestVersion() {
return window.location.search.indexOf('diff_id') === -1;
},
fetchData(toggleTree = true) {
if (this.isLatestVersion() && this.glFeatures.diffsBatchLoad) {
this.fetchDiffFilesMeta()
.then(() => {
if (toggleTree) this.hideTreeListIfJustOneFile();
})
.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) {
......@@ -214,6 +255,7 @@ export default {
.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"
......
......@@ -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;
......@@ -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,
......
......@@ -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);
......
......@@ -8,6 +8,7 @@ const defaultViewType = INLINE_DIFF_VIEW_TYPE;
export default () => ({
isLoading: true,
isBatchLoading: false,
addedLines: null,
removedLines: null,
endpoint: '',
......
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';
......
......@@ -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,
......
......@@ -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: [],
......
......@@ -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"
<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>
<span class="flex-truncate-child">
{{ buildName }}
</span>
</tooltip-on-truncate>
</a>
</div>
......
......@@ -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" />
......
......@@ -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,
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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)
......
......@@ -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)
......
......@@ -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
......@@ -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')
......
......@@ -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),
......
......@@ -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
......
---
title: Add option to delete cached Kubernetes namespaces
merge_request: 20411
author:
type: added
---
title: Convert flash epic error to form validation error
merge_request: 20130
author:
type: changed
---
title: Fix tooltip hovers in environments table
merge_request: 20737
author:
type: fixed
......@@ -166,6 +166,7 @@ Rails.application.routes.draw do
end
get :cluster_status, format: :json
delete :clear_cache
end
end
end
......
......@@ -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.
......
......@@ -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.
......
......@@ -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.
......
......@@ -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 ""
......
......@@ -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) }
......
......@@ -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]) }
......
......@@ -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]) }
......
......@@ -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
......
......@@ -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'
......
......@@ -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
......
......@@ -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'
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
......@@ -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();
......
......@@ -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', {}, [
......
......@@ -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 = {};
......
......@@ -10,7 +10,6 @@ describe('Environment', () => {
endpoint: 'environments.json',
canCreateEnvironment: true,
canReadEnvironment: true,
cssContainerClass: 'container',
newEnvironmentPath: 'environments/new',
helpPagePath: 'help',
canaryDeploymentFeatureId: 'canary_deployment',
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Build::Context::Build do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Build::Context::Global do
......
......@@ -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
......@@ -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) }
......
......@@ -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
......@@ -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) }
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment