Commit d110aee5 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents d009cbad 6997ccbc
6ee267a02055a7f48871d66fae594417c4cdec9b
b84ba4f096da54ebb6a85c14ab736474c72f1a2a
......@@ -14,14 +14,10 @@ export default () => {
if (pipelineTableViewEl) {
// Update MR and Commits tabs
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
if (
event.detail.pipelines &&
event.detail.pipelines.count &&
event.detail.pipelines.count.all
) {
if (event.detail.pipelineCount) {
const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelines.count.all;
badge.textContent = event.detail.pipelineCount;
}
});
......
......@@ -133,15 +133,15 @@ export default {
this.store.storePagination(resp.headers);
this.setCommonData(pipelines);
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: {
pipelines: resp.data,
},
});
if (resp.headers?.['x-total']) {
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: { pipelineCount: resp.headers['x-total'] },
});
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
}
}
},
/**
......
......@@ -87,6 +87,20 @@
width: 145px;
}
.empty-state--agent {
.text-content {
@include gl-max-w-full;
@include media-breakpoint-up(lg) {
max-width: 70%;
}
}
.gl-alert-actions {
@include gl-mt-0;
@include gl-flex-wrap;
}
}
.top-area .nav-controls > .btn.btn-add-cluster {
margin-right: 0;
}
......
......@@ -100,7 +100,7 @@ export default {
bulkEnableDevopsAdoptionNamespaces: { enabledNamespaces, errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsAdded', enabledNamespaces);
if (!requestErrors.length) this.$emit('enabledNamespacesAdded', enabledNamespaces);
},
})
.catch((error) => {
......
......@@ -19,7 +19,10 @@ import {
import bulkEnableDevopsAdoptionNamespacesMutation from '../graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import devopsAdoptionEnabledNamespacesQuery from '../graphql/queries/devops_adoption_enabled_namespaces.query.graphql';
import getGroupsQuery from '../graphql/queries/get_groups.query.graphql';
import { addSegmentsToCache, deleteSegmentsFromCache } from '../utils/cache_updates';
import {
addEnabledNamespacesToCache,
deleteEnabledNamespacesFromCache,
} from '../utils/cache_updates';
import { shouldPollTableData } from '../utils/helpers';
import DevopsAdoptionAddDropdown from './devops_adoption_add_dropdown.vue';
import DevopsAdoptionOverview from './devops_adoption_overview.vue';
......@@ -69,15 +72,15 @@ export default {
openModal: false,
errors: {
[DEVOPS_ADOPTION_ERROR_KEYS.groups]: false,
[DEVOPS_ADOPTION_ERROR_KEYS.segments]: false,
[DEVOPS_ADOPTION_ERROR_KEYS.addSegment]: false,
[DEVOPS_ADOPTION_ERROR_KEYS.enabledNamespaces]: false,
[DEVOPS_ADOPTION_ERROR_KEYS.addEnabledNamespaces]: false,
},
groups: {
nodes: [],
pageInfo: null,
},
pollingTableData: null,
segmentsQueryVariables: {
enabledNamespaceQueryVariables: {
displayNamespaceId: this.isGroup ? this.groupGid : null,
},
adoptionTabClicked: false,
......@@ -92,7 +95,7 @@ export default {
isSingleRequest: true,
},
variables() {
return this.segmentsQueryVariables;
return this.enabledNamespaceQueryVariables;
},
result({ data }) {
if (this.isGroup) {
......@@ -106,7 +109,7 @@ export default {
}
},
error(error) {
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.segments, error);
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.enabledNamespaces, error);
},
},
},
......@@ -117,7 +120,7 @@ export default {
hasGroupData() {
return Boolean(this.groups?.nodes?.length);
},
hasSegmentsData() {
hasEnabledNamespaceData() {
return Boolean(this.devopsAdoptionEnabledNamespaces?.nodes?.length);
},
hasLoadingError() {
......@@ -191,14 +194,14 @@ export default {
} = data;
if (errors.length) {
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.addSegment, errors);
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.addEnabledNamespaces, errors);
} else {
this.addSegmentsToCache(enabledNamespaces);
this.addEnabledNamespacesToCache(enabledNamespaces);
}
},
})
.catch((error) => {
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.addSegment, error);
this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.addEnabledNamespaces, error);
})
.finally(() => {
this.isLoadingEnableGroup = false;
......@@ -206,7 +209,7 @@ export default {
},
pollTableData() {
const shouldPoll = shouldPollTableData({
segments: this.devopsAdoptionEnabledNamespaces.nodes,
enabledNamespaces: this.devopsAdoptionEnabledNamespaces.nodes,
timestamp: this.devopsAdoptionEnabledNamespaces?.nodes[0]?.latestSnapshot?.recordedAt,
openModal: this.openModal,
});
......@@ -250,15 +253,15 @@ export default {
})
.catch((error) => this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.groups, error));
},
addSegmentsToCache(segments) {
addEnabledNamespacesToCache(enabledNamespaces) {
const { cache } = this.$apollo.getClient();
addSegmentsToCache(cache, segments, this.segmentsQueryVariables);
addEnabledNamespacesToCache(cache, enabledNamespaces, this.enabledNamespaceQueryVariables);
},
deleteSegmentsFromCache(ids) {
deleteEnabledNamespacesFromCache(ids) {
const { cache } = this.$apollo.getClient();
deleteSegmentsFromCache(cache, ids, this.segmentsQueryVariables);
deleteEnabledNamespacesFromCache(cache, ids, this.enabledNamespaceQueryVariables);
},
selectTab() {
const [value] = getParameterValues('tab');
......@@ -327,18 +330,18 @@ export default {
<devops-adoption-section
v-else
:is-loading="isLoadingAdoptionData"
:has-segments-data="hasSegmentsData"
:has-enabled-namespace-data="hasEnabledNamespaceData"
:timestamp="timestamp"
:has-group-data="hasGroupData"
:cols="tab.cols"
:segments="devopsAdoptionEnabledNamespaces"
:enabled-namespaces="devopsAdoptionEnabledNamespaces"
:search-term="searchTerm"
:disabled-group-nodes="disabledGroupNodes"
:is-loading-groups="isLoadingGroups"
:has-subgroups="hasSubgroups"
@segmentsRemoved="deleteSegmentsFromCache"
@enabledNamespacesRemoved="deleteEnabledNamespacesFromCache"
@fetchGroups="fetchGroups"
@segmentsAdded="addSegmentsToCache"
@enabledNamespacesAdded="addEnabledNamespacesToCache"
@trackModalOpenState="trackModalOpenState"
/>
</gl-tab>
......@@ -359,7 +362,7 @@ export default {
:is-loading-groups="isLoadingGroups"
:has-subgroups="hasSubgroups"
@fetchGroups="fetchGroups"
@segmentsAdded="addSegmentsToCache"
@enabledNamespacesAdded="addEnabledNamespacesToCache"
/>
</span>
</template>
......
<script>
import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID } from '../constants';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_DELETE_MODAL_ID } from '../constants';
import disableDevopsAdoptionNamespaceMutation from '../graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
export default {
name: 'DevopsAdoptionDeleteModal',
components: { GlModal, GlSprintf, GlAlert },
i18n: DEVOPS_ADOPTION_STRINGS.deleteModal,
devopsSegmentDeleteModalId: DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
deleteModalId: DEVOPS_ADOPTION_DELETE_MODAL_ID,
props: {
segment: {
namespace: {
type: Object,
required: true,
},
......@@ -43,14 +43,14 @@ export default {
return this.errors[0];
},
displayName() {
return this.segment.namespace?.fullName;
return this.namespace.namespace?.fullName;
},
},
methods: {
async deleteSegment() {
async deleteEnabledNamespace() {
try {
const {
segment: { id },
namespace: { id },
} = this;
this.loading = true;
......@@ -65,7 +65,7 @@ export default {
id: [id],
},
update: () => {
this.$emit('segmentsRemoved', [id]);
this.$emit('enabledNamespacesRemoved', [id]);
},
});
......@@ -90,11 +90,11 @@ export default {
<template>
<gl-modal
ref="modal"
:modal-id="$options.devopsSegmentDeleteModalId"
:modal-id="$options.deleteModalId"
size="sm"
:action-primary="primaryOptions"
:action-cancel="cancelOptions"
@primary.prevent="deleteSegment"
@primary.prevent="deleteEnabledNamespace"
@hide="$emit('trackModalOpenState', false)"
@show="$emit('trackModalOpenState', true)"
>
......
......@@ -21,7 +21,7 @@ export default {
type: Boolean,
required: true,
},
hasSegmentsData: {
hasEnabledNamespaceData: {
type: Boolean,
required: true,
},
......@@ -37,7 +37,7 @@ export default {
type: Array,
required: true,
},
segments: {
enabledNamespaces: {
type: Object,
required: false,
default: () => {},
......@@ -64,7 +64,7 @@ export default {
</script>
<template>
<gl-loading-icon v-if="isLoading" size="md" class="gl-my-5" />
<div v-else-if="hasSegmentsData" class="gl-mt-3">
<div v-else-if="hasEnabledNamespaceData" class="gl-mt-3">
<div class="gl-mb-3" data-testid="tableHeader">
<p class="gl-text-gray-400">
<gl-sprintf :message="$options.i18n.tableHeaderText">
......@@ -79,14 +79,14 @@ export default {
:is-loading-groups="isLoadingGroups"
:has-subgroups="hasSubgroups"
@fetchGroups="$emit('fetchGroups', $event)"
@segmentsAdded="$emit('segmentsAdded', $event)"
@enabledNamespacesAdded="$emit('enabledNamespacesAdded', $event)"
@trackModalOpenState="$emit('trackModalOpenState', $event)"
/>
</div>
<devops-adoption-table
:cols="cols"
:segments="segments.nodes"
@segmentsRemoved="$emit('segmentsRemoved', $event)"
:enabled-namespaces="enabledNamespaces.nodes"
@enabledNamespacesRemoved="$emit('enabledNamespacesRemoved', $event)"
@trackModalOpenState="$emit('trackModalOpenState', $event)"
/>
</div>
......
......@@ -11,9 +11,9 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
DEVOPS_ADOPTION_TABLE_TEST_IDS,
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY,
DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_DESC_STORAGE_KEY,
DEVOPS_ADOPTION_DELETE_MODAL_ID,
DEVOPS_ADOPTION_TABLE_SORT_BY_STORAGE_KEY,
DEVOPS_ADOPTION_TABLE_SORT_DESC_STORAGE_KEY,
DEVOPS_ADOPTION_TABLE_REMOVE_BUTTON_DISABLED,
DEVOPS_ADOPTION_GROUP_COL_LABEL,
} from '../constants';
......@@ -70,12 +70,12 @@ export default {
...i18n,
removeButtonDisabled: DEVOPS_ADOPTION_TABLE_REMOVE_BUTTON_DISABLED,
},
devopsSegmentDeleteModalId: DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
deleteModalId: DEVOPS_ADOPTION_DELETE_MODAL_ID,
testids: DEVOPS_ADOPTION_TABLE_TEST_IDS,
sortByStorageKey: DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY,
sortDescStorageKey: DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_DESC_STORAGE_KEY,
sortByStorageKey: DEVOPS_ADOPTION_TABLE_SORT_BY_STORAGE_KEY,
sortDescStorageKey: DEVOPS_ADOPTION_TABLE_SORT_DESC_STORAGE_KEY,
props: {
segments: {
enabledNamespaces: {
type: Array,
required: true,
},
......@@ -88,7 +88,7 @@ export default {
return {
sortBy: NAME_HEADER,
sortDesc: false,
selectedSegment: null,
selectedNamespace: null,
};
},
computed: {
......@@ -113,8 +113,8 @@ export default {
},
},
methods: {
setSelectedSegment(segment) {
this.selectedSegment = segment;
setSelectedNamespace(namespace) {
this.selectedNamespace = namespace;
},
headerSlotName(key) {
return `head(${key})`;
......@@ -149,7 +149,7 @@ export default {
/>
<gl-table
:fields="tableHeaderFields"
:items="segments"
:items="enabledNamespaces"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
thead-class="gl-border-t-0 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
......@@ -169,7 +169,7 @@ export default {
</template>
<template #cell(name)="{ item }">
<div :data-testid="$options.testids.SEGMENT">
<div :data-testid="$options.testids.NAMESPACE">
<strong v-if="item.latestSnapshot">{{ item.namespace.fullName }}</strong>
<template v-else>
<span class="gl-text-gray-400">{{ item.namespace.fullName }}</span>
......@@ -196,20 +196,20 @@ export default {
:data-testid="$options.testids.ACTIONS"
>
<gl-button
v-gl-modal="$options.devopsSegmentDeleteModalId"
v-gl-modal="$options.deleteModalId"
:disabled="isCurrentGroup(item)"
category="tertiary"
icon="remove"
:aria-label="$options.i18n.removeButton"
@click="setSelectedSegment(item)"
@click="setSelectedNamespace(item)"
/>
</span>
</template>
</gl-table>
<devops-adoption-delete-modal
v-if="selectedSegment"
:segment="selectedSegment"
@segmentsRemoved="$emit('segmentsRemoved', $event)"
v-if="selectedNamespace"
:namespace="selectedNamespace"
@enabledNamespacesRemoved="$emit('enabledNamespacesRemoved', $event)"
@trackModalOpenState="$emit('trackModalOpenState', $event)"
/>
</div>
......
......@@ -8,14 +8,14 @@ export const DEBOUNCE_DELAY = 500;
export const DEVOPS_ADOPTION_PROGRESS_BAR_HEIGHT = '8px';
export const DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID = 'devopsSegmentDeleteModal';
export const DEVOPS_ADOPTION_DELETE_MODAL_ID = 'devopsDeleteModal';
export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM';
export const DEVOPS_ADOPTION_ERROR_KEYS = {
groups: 'groupsError',
segments: 'segmentsError',
addSegment: 'addSegmentsError',
enabledNamespaces: 'enabledNamespacesError',
addEnabledNamespaces: 'addEnabledNamespacesError',
};
export const TABLE_HEADER_TEXT = s__(
......@@ -46,10 +46,10 @@ export const DEVOPS_ADOPTION_STRINGS = {
[DEVOPS_ADOPTION_ERROR_KEYS.groups]: s__(
'DevopsAdoption|There was an error fetching Groups. Please refresh the page.',
),
[DEVOPS_ADOPTION_ERROR_KEYS.segments]: s__(
[DEVOPS_ADOPTION_ERROR_KEYS.enabledNamespaces]: s__(
'DevopsAdoption|There was an error fetching Group adoption data. Please refresh the page.',
),
[DEVOPS_ADOPTION_ERROR_KEYS.addSegment]: s__(
[DEVOPS_ADOPTION_ERROR_KEYS.addEnabledNamespaces]: s__(
'DevopsAdoption|There was an error enabling the current group. Please refresh the page.',
),
tableHeader: {
......@@ -92,17 +92,15 @@ export const DEVOPS_ADOPTION_STRINGS = {
export const DEVOPS_ADOPTION_TABLE_TEST_IDS = {
TABLE_HEADERS: 'header',
SEGMENT: 'segmentCol',
NAMESPACE: 'namespaceCol',
ACTIONS: 'actionsCol',
LOCAL_STORAGE_SORT_BY: 'localStorageSortBy',
LOCAL_STORAGE_SORT_DESC: 'localStorageSortDesc',
};
export const DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY =
'devops_adoption_segments_table_sort_by';
export const DEVOPS_ADOPTION_TABLE_SORT_BY_STORAGE_KEY = 'devops_adoption_table_sort_by';
export const DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_DESC_STORAGE_KEY =
'devops_adoption_segments_table_sort_desc';
export const DEVOPS_ADOPTION_TABLE_SORT_DESC_STORAGE_KEY = 'devops_adoption_table_sort_desc';
export const DEVOPS_ADOPTION_GROUP_COL_LABEL = __('Group');
......
import produce from 'immer';
import devopsAdoptionEnabledNamespacesQuery from '../graphql/queries/devops_adoption_enabled_namespaces.query.graphql';
export const addSegmentsToCache = (store, segments, variables) => {
export const addEnabledNamespacesToCache = (store, enabledNamespaces, variables) => {
const sourceData = store.readQuery({
query: devopsAdoptionEnabledNamespacesQuery,
variables,
......@@ -10,7 +10,7 @@ export const addSegmentsToCache = (store, segments, variables) => {
const data = produce(sourceData, (draftData) => {
draftData.devopsAdoptionEnabledNamespaces.nodes = [
...draftData.devopsAdoptionEnabledNamespaces.nodes,
...segments,
...enabledNamespaces,
];
});
......@@ -21,7 +21,7 @@ export const addSegmentsToCache = (store, segments, variables) => {
});
};
export const deleteSegmentsFromCache = (store, segmentIds, variables) => {
export const deleteEnabledNamespacesFromCache = (store, enabledNamespaceIds, variables) => {
const sourceData = store.readQuery({
query: devopsAdoptionEnabledNamespacesQuery,
variables,
......@@ -29,7 +29,7 @@ export const deleteSegmentsFromCache = (store, segmentIds, variables) => {
const updatedData = produce(sourceData, (draftData) => {
draftData.devopsAdoptionEnabledNamespaces.nodes = draftData.devopsAdoptionEnabledNamespaces.nodes.filter(
({ id }) => !segmentIds.includes(id),
({ id }) => !enabledNamespaceIds.includes(id),
);
});
......
import { isToday } from '~/lib/utils/datetime_utility';
/**
* A helper function which accepts the segments,
* A helper function which accepts the enabledNamespaces,
*
* @param {Object} params the segment data, timestamp and check for open modals
* @param {Object} params the enabledNamespaces data, timestamp and check for open modals
*
* @return {Boolean} a boolean to determine if table data should be polled
*/
export const shouldPollTableData = ({ segments, timestamp, openModal }) => {
export const shouldPollTableData = ({ enabledNamespaces, timestamp, openModal }) => {
if (openModal) {
return false;
} else if (!segments.length) {
} else if (!enabledNamespaces.length) {
return true;
}
const anyPendingSegments = segments.some((node) => node.latestSnapshot === null);
const anyPendingEnabledNamespaces = enabledNamespaces.some(
(node) => node.latestSnapshot === null,
);
const dataNotRefreshedToday = !isToday(new Date(timestamp));
return anyPendingSegments || dataNotRefreshedToday;
return anyPendingEnabledNamespaces || dataNotRefreshedToday;
};
<script>
import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
export default {
components: {
......@@ -7,12 +7,26 @@ export default {
GlEmptyState,
GlLink,
GlSprintf,
GlAlert,
},
props: {
image: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
hasConfigurations: {
type: Boolean,
required: true,
},
},
computed: {
repositoryPath() {
return `/${this.projectPath}`;
},
},
};
</script>
......@@ -21,9 +35,10 @@ export default {
<gl-empty-state
:svg-path="image"
:title="s__('ClusterAgents|Integrate Kubernetes with a GitLab Agent')"
class="empty-state--agent"
>
<template #description>
<p>
<p class="mw-460 gl-mx-auto">
<gl-sprintf
:message="
s__(
......@@ -39,7 +54,7 @@ export default {
</gl-sprintf>
</p>
<p>
<p class="mw-460 gl-mx-auto">
<gl-sprintf
:message="
s__(
......@@ -57,10 +72,40 @@ export default {
</template>
</gl-sprintf>
</p>
<gl-alert
v-if="!hasConfigurations"
variant="warning"
class="gl-mb-5 text-left"
:dismissible="false"
>
{{
s__(
'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
)
}}
<template #actions>
<gl-button
category="primary"
variant="info"
href="https://docs.gitlab.com/ee/user/clusters/agent/#define-a-configuration-repository"
target="_blank"
class="gl-ml-0!"
>
{{ s__('ClusterAgents|Read more about getting started') }}
</gl-button>
<gl-button category="secondary" variant="info" :href="repositoryPath">
{{ s__('ClusterAgents|Go to the repository') }}
</gl-button>
</template>
</gl-alert>
</template>
<template #actions>
<gl-button
:disabled="!hasConfigurations"
data-testid="integration-primary-button"
category="primary"
variant="success"
href="https://docs.gitlab.com/ee/user/clusters/agent/#get-started-with-gitops-and-the-gitlab-agent"
......
......@@ -78,6 +78,9 @@ export default {
treePageInfo() {
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
},
hasConfigurations() {
return Boolean(this.agents?.project?.repository?.tree?.trees?.nodes?.length);
},
},
methods: {
nextPage() {
......@@ -121,7 +124,12 @@ export default {
</div>
</div>
<AgentEmptyState v-else :image="emptyStateImage" />
<AgentEmptyState
v-else
:image="emptyStateImage"
:project-path="projectPath"
:has-configurations="hasConfigurations"
/>
</section>
<gl-alert v-else variant="danger" :dismissible="false">
......
......@@ -68,6 +68,10 @@ module Elastic
[]
end
def running?
started? && !halted? && !completed?
end
private
def timestamps(completed:)
......
......@@ -49,16 +49,17 @@
.gl-card-body
.form-group
.form-check
- pending_migrations = elasticsearch_available && Elastic::DataMigrationService.pending_migrations? && Gitlab::CurrentSettings.elasticsearch_pause_indexing?
- disable_checkbox = !Gitlab::CurrentSettings.elasticsearch_indexing? || pending_migrations || @last_elasticsearch_reindexing_task&.in_progress?
- first_pending_migration = Elastic::DataMigrationService.pending_migrations.first if elasticsearch_available
- pending_migration_running_and_pauses_indexing = first_pending_migration&.running? && first_pending_migration&.pause_indexing?
- disable_checkbox = !Gitlab::CurrentSettings.elasticsearch_indexing? || pending_migration_running_and_pauses_indexing || @last_elasticsearch_reindexing_task&.in_progress?
= f.check_box :elasticsearch_pause_indexing, class: 'form-check-input', data: { qa_selector: 'pause_checkbox' }, disabled: disable_checkbox
= f.label :elasticsearch_pause_indexing, class: 'form-check-label' do
= _('Pause Elasticsearch indexing')
.form-text.gl-text-gray-600.gl-mt-0
= _('Changes are still tracked. Useful for cluster/index migrations.')
- if pending_migrations
- if pending_migration_running_and_pauses_indexing
.form-text.text-warning
= _('There are pending advanced search migrations. Indexing must remain paused until the migrations are completed.')
= _('There are pending advanced search migrations which require indexing to be paused. Indexing must remain paused until the migrations are completed.')
.form-group
.form-check
......
......@@ -149,8 +149,8 @@ describe('DevopsAdoptionAddDropdown', () => {
});
});
it('emits the segmentsAdded event', () => {
const [params] = wrapper.emitted().segmentsAdded[0];
it('emits the enabledNamespacesAdded event', () => {
const [params] = wrapper.emitted().enabledNamespacesAdded[0];
expect(params).toStrictEqual([devopsAdoptionNamespaceData.nodes[0]]);
});
......@@ -174,8 +174,8 @@ describe('DevopsAdoptionAddDropdown', () => {
);
});
it('does not emit the segmentsAdded event', () => {
expect(wrapper.emitted().segmentsAdded).not.toBeDefined();
it('does not emit the enabledNamespacesAdded event', () => {
expect(wrapper.emitted().enabledNamespacesAdded).not.toBeDefined();
});
});
});
......
......@@ -15,7 +15,7 @@ import {
import bulkEnableDevopsAdoptionNamespacesMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import devopsAdoptionEnabledNamespaces from 'ee/analytics/devops_report/devops_adoption/graphql/queries/devops_adoption_enabled_namespaces.query.graphql';
import getGroupsQuery from 'ee/analytics/devops_report/devops_adoption/graphql/queries/get_groups.query.graphql';
import { addSegmentsToCache } from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates';
import { addEnabledNamespacesToCache } from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -24,7 +24,7 @@ import API from '~/api';
import { groupNodes, devopsAdoptionNamespaceData } from '../mock_data';
jest.mock('ee/analytics/devops_report/devops_adoption/utils/cache_updates', () => ({
addSegmentsToCache: jest.fn(),
addEnabledNamespacesToCache: jest.fn(),
}));
const localVue = createLocalVue();
......@@ -234,9 +234,9 @@ describe('DevopsAdoptionApp', () => {
);
});
it('calls addSegmentsToCache with the correct variables', () => {
expect(addSegmentsToCache).toHaveBeenCalledTimes(1);
expect(addSegmentsToCache).toHaveBeenCalledWith(
it('calls addEnabledNamespacesToCache with the correct variables', () => {
expect(addEnabledNamespacesToCache).toHaveBeenCalledTimes(1);
expect(addEnabledNamespacesToCache).toHaveBeenCalledWith(
expect.anything(),
[devopsAdoptionNamespaceData.nodes[0]],
{
......@@ -270,7 +270,7 @@ describe('DevopsAdoptionApp', () => {
it('displays the error message ', () => {
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.addSegmentsError);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.addEnabledNamespacesError);
});
it('calls Sentry', () => {
......@@ -301,7 +301,7 @@ describe('DevopsAdoptionApp', () => {
it('displays the error message ', () => {
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.segmentsError);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.enabledNamespacesError);
});
it('calls Sentry', () => {
......
......@@ -4,7 +4,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import DevopsAdoptionDeleteModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_delete_modal.vue';
import { DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID } from 'ee/analytics/devops_report/devops_adoption/constants';
import { DEVOPS_ADOPTION_DELETE_MODAL_ID } from 'ee/analytics/devops_report/devops_adoption/constants';
import disableDevopsAdoptionNamespaceMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -38,16 +38,16 @@ const mutateWithErrors = jest.fn().mockRejectedValue(genericDeleteErrorMessage);
describe('DevopsAdoptionDeleteModal', () => {
let wrapper;
const createComponent = ({ deleteSegmentsSpy = mutate, props = {} } = {}) => {
const createComponent = ({ deleteEnabledNamespacesSpy = mutate, props = {} } = {}) => {
const mockApollo = createMockApollo([
[disableDevopsAdoptionNamespaceMutation, deleteSegmentsSpy],
[disableDevopsAdoptionNamespaceMutation, deleteEnabledNamespacesSpy],
]);
wrapper = shallowMount(DevopsAdoptionDeleteModal, {
localVue,
apolloProvider: mockApollo,
propsData: {
segment: devopsAdoptionNamespaceData.nodes[0],
namespace: devopsAdoptionNamespaceData.nodes[0],
...props,
},
stubs: {
......@@ -63,7 +63,6 @@ describe('DevopsAdoptionDeleteModal', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('default display', () => {
......@@ -73,7 +72,7 @@ describe('DevopsAdoptionDeleteModal', () => {
const modal = findModal();
expect(modal.exists()).toBe(true);
expect(modal.props('modalId')).toBe(DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID);
expect(modal.props('modalId')).toBe(DEVOPS_ADOPTION_DELETE_MODAL_ID);
});
it('displays the confirmation message', () => {
......@@ -104,7 +103,7 @@ describe('DevopsAdoptionDeleteModal', () => {
describe('submitting the form', () => {
describe('while waiting for the mutation', () => {
beforeEach(() => createComponent({ deleteSegmentsSpy: mutateLoading }));
beforeEach(() => createComponent({ deleteEnabledNamespacesSpy: mutateLoading }));
it('disables the cancel button', async () => {
expect(cancelButtonDisabledState()).toBe(false);
......@@ -142,8 +141,8 @@ describe('DevopsAdoptionDeleteModal', () => {
});
});
it('emits segmentsRemoved with the correct variables', () => {
const [params] = wrapper.emitted().segmentsRemoved[0];
it('emits dNamespacesRemoved with the correct variables', () => {
const [params] = wrapper.emitted().enabledNamespacesRemoved[0];
expect(params).toStrictEqual([devopsAdoptionNamespaceData.nodes[0].id]);
});
......@@ -161,7 +160,7 @@ describe('DevopsAdoptionDeleteModal', () => {
`(
'displays a $errorType error if the mutation has a $errorLocation error',
async ({ mutationSpy, message }) => {
createComponent({ deleteSegmentsSpy: mutationSpy });
createComponent({ deleteEnabledNamespacesSpy: mutationSpy });
findModal().vm.$emit('primary', mockEvent);
......@@ -178,7 +177,7 @@ describe('DevopsAdoptionDeleteModal', () => {
it('calls sentry on top level error', async () => {
jest.spyOn(Sentry, 'captureException');
createComponent({ deleteSegmentsSpy: mutateWithErrors });
createComponent({ deleteEnabledNamespacesSpy: mutateWithErrors });
findModal().vm.$emit('primary', mockEvent);
......
......@@ -27,7 +27,6 @@ describe('DevopsAdoptionEmptyState', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('contains the correct svg', () => {
......
......@@ -17,11 +17,11 @@ describe('DevopsAdoptionSection', () => {
shallowMount(DevopsAdoptionSection, {
propsData: {
isLoading: false,
hasSegmentsData: true,
hasEnabledNamespaceData: true,
timestamp: '2020-10-31 23:59',
hasGroupData: true,
cols: DEVOPS_ADOPTION_TABLE_CONFIGURATION[0].cols,
segments: devopsAdoptionNamespaceData,
enabledNamespaces: devopsAdoptionNamespaceData,
disabledGroupNodes: groupNodes,
searchTerm: '',
isLoadingGroups: false,
......@@ -59,7 +59,7 @@ describe('DevopsAdoptionSection', () => {
});
});
describe('with segment data', () => {
describe('with enabledNamespace data', () => {
beforeEach(() => {
createComponent();
});
......@@ -81,9 +81,9 @@ describe('DevopsAdoptionSection', () => {
});
});
describe('with no segment data', () => {
describe('with no enabledNamespace data', () => {
beforeEach(() => {
createComponent({ hasSegmentsData: false });
createComponent({ hasEnabledNamespaceData: false });
});
it('displays an empty state', () => {
......
......@@ -21,7 +21,7 @@ describe('DevopsAdoptionTable', () => {
wrapper = mount(DevopsAdoptionTable, {
propsData: {
cols: DEVOPS_ADOPTION_TABLE_CONFIGURATION[0].cols,
segments: devopsAdoptionNamespaceData.nodes,
enabledNamespaces: devopsAdoptionNamespaceData.nodes,
},
provide,
directives: {
......@@ -100,15 +100,15 @@ describe('DevopsAdoptionTable', () => {
});
describe('table fields', () => {
describe('segment name', () => {
describe('enabled namespace name', () => {
beforeEach(() => {
createComponent();
});
it('displays the correct segment name', () => {
it('displays the correct name', () => {
createComponent();
expect(findCol(TEST_IDS.SEGMENT).text()).toBe('Group 1');
expect(findCol(TEST_IDS.NAMESPACE).text()).toBe('Group 1');
});
describe('"This group" badge', () => {
......@@ -122,7 +122,7 @@ describe('DevopsAdoptionTable', () => {
`('$scenario', ({ expected, provide }) => {
createComponent({ provide });
const badge = findColSubComponent(TEST_IDS.SEGMENT, GlBadge);
const badge = findColSubComponent(TEST_IDS.NAMESPACE, GlBadge);
expect(badge.exists()).toBe(expected);
});
......@@ -134,7 +134,7 @@ describe('DevopsAdoptionTable', () => {
});
it('grays the text out', () => {
const name = findColRowChild(TEST_IDS.SEGMENT, 1, 'span');
const name = findColRowChild(TEST_IDS.NAMESPACE, 1, 'span');
expect(name.classes()).toStrictEqual(['gl-text-gray-400']);
});
......@@ -143,7 +143,7 @@ describe('DevopsAdoptionTable', () => {
let icon;
beforeEach(() => {
icon = findColRowChild(TEST_IDS.SEGMENT, 1, GlIcon);
icon = findColRowChild(TEST_IDS.NAMESPACE, 1, GlIcon);
});
it('displays the icon', () => {
......@@ -198,7 +198,7 @@ describe('DevopsAdoptionTable', () => {
createComponent();
wrapper.setData({
selectedSegment: devopsAdoptionNamespaceData.nodes[0],
selectedNamespace: devopsAdoptionNamespaceData.nodes[0],
});
});
......@@ -217,14 +217,14 @@ describe('DevopsAdoptionTable', () => {
headers = findTable().findAll(`[data-testid="${TEST_IDS.TABLE_HEADERS}"]`);
});
it('sorts the segments by name', async () => {
expect(findCol(TEST_IDS.SEGMENT).text()).toBe('Group 1');
it('sorts the namespaces by name', async () => {
expect(findCol(TEST_IDS.NAMESPACE).text()).toBe('Group 1');
headers.at(0).trigger('click');
await nextTick();
expect(findCol(TEST_IDS.SEGMENT).text()).toBe('Group 2');
expect(findCol(TEST_IDS.NAMESPACE).text()).toBe('Group 2');
});
it('should update local storage when the sort column changes', async () => {
......
......@@ -95,8 +95,6 @@ export const devopsAdoptionTableHeaders = [
},
];
export const segmentName = 'Foooo';
export const genericErrorMessage = 'An error occurred while saving changes. Please try again.';
export const dataErrorMessage = 'Name already taken.';
......
import {
deleteSegmentsFromCache,
addSegmentsToCache,
deleteEnabledNamespacesFromCache,
addEnabledNamespacesToCache,
} from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates';
import { devopsAdoptionNamespaceData } from '../mock_data';
describe('addSegmentsToCache', () => {
describe('addEnabledNamespacesToCache', () => {
const store = {
readQuery: jest.fn(() => ({ devopsAdoptionEnabledNamespaces: { nodes: [] } })),
writeQuery: jest.fn(),
};
it('calls writeQuery with the correct response', () => {
addSegmentsToCache(store, devopsAdoptionNamespaceData.nodes);
addEnabledNamespacesToCache(store, devopsAdoptionNamespaceData.nodes);
expect(store.writeQuery).toHaveBeenCalledWith(
expect.objectContaining({
......@@ -25,7 +25,7 @@ describe('addSegmentsToCache', () => {
});
});
describe('deleteSegmentsFromCache', () => {
describe('deleteEnabledNamespacesFromCache', () => {
const store = {
readQuery: jest.fn(() => ({ devopsAdoptionEnabledNamespaces: devopsAdoptionNamespaceData })),
writeQuery: jest.fn(),
......@@ -33,7 +33,7 @@ describe('deleteSegmentsFromCache', () => {
it('calls writeQuery with the correct response', () => {
// Remove the item at the first index
deleteSegmentsFromCache(store, [devopsAdoptionNamespaceData.nodes[0].id]);
deleteEnabledNamespacesFromCache(store, [devopsAdoptionNamespaceData.nodes[0].id]);
expect(store.writeQuery).toHaveBeenCalledWith(
expect.not.objectContaining({
......
......@@ -8,15 +8,15 @@ describe('shouldPollTableData', () => {
const previousDay = '2020-07-05T00:00:00.000Z';
it.each`
scenario | segments | timestamp | openModal | expected
${'no segment data'} | ${[]} | ${mockDate} | ${false} | ${true}
${'no timestamp'} | ${comepleteData} | ${null} | ${false} | ${true}
${'open modal'} | ${comepleteData} | ${mockDate} | ${true} | ${false}
${'segment data, timestamp is today, modal is closed'} | ${comepleteData} | ${mockDate} | ${false} | ${false}
${'segment data, timestamp is yesterday, modal is closed'} | ${comepleteData} | ${previousDay} | ${false} | ${true}
${'segment data, timestamp is today, modal is open'} | ${comepleteData} | ${mockDate} | ${true} | ${false}
${'pending segment data, timestamp is today, modal is closed'} | ${pendingData} | ${mockDate} | ${false} | ${true}
`('returns $expected when $scenario', ({ segments, timestamp, openModal, expected }) => {
expect(shouldPollTableData({ segments, timestamp, openModal })).toBe(expected);
scenario | enabledNamespaces | timestamp | openModal | expected
${'no namespaces data'} | ${[]} | ${mockDate} | ${false} | ${true}
${'no timestamp'} | ${comepleteData} | ${null} | ${false} | ${true}
${'open modal'} | ${comepleteData} | ${mockDate} | ${true} | ${false}
${'namespaces data, timestamp is today, modal is closed'} | ${comepleteData} | ${mockDate} | ${false} | ${false}
${'namespaces data, timestamp is yesterday, modal is closed'} | ${comepleteData} | ${previousDay} | ${false} | ${true}
${'namespaces data, timestamp is today, modal is open'} | ${comepleteData} | ${mockDate} | ${true} | ${false}
${'pending namespaces data, timestamp is today, modal is closed'} | ${pendingData} | ${mockDate} | ${false} | ${true}
`('returns $expected when $scenario', ({ enabledNamespaces, timestamp, openModal, expected }) => {
expect(shouldPollTableData({ enabledNamespaces, timestamp, openModal })).toBe(expected);
});
});
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlEmptyState, GlSprintf } from '@gitlab/ui';
import AgentEmptyState from 'ee/clusters_list/components/agent_empty_state.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('AgentEmptyStateComponent', () => {
let wrapper;
const propsData = {
image: '/image/path',
projectPath: 'path/to/project',
hasConfigurations: false,
};
const findConfigurationsAlert = () => wrapper.findComponent(GlAlert);
const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button');
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
beforeEach(() => {
wrapper = shallowMount(AgentEmptyState, { propsData, stubs: { GlEmptyState, GlSprintf } });
wrapper = shallowMountExtended(AgentEmptyState, {
propsData,
stubs: { GlEmptyState, GlSprintf },
});
});
afterEach(() => {
......@@ -20,8 +29,27 @@ describe('AgentEmptyStateComponent', () => {
}
});
it('should render content', () => {
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
expect(wrapper.text()).toContain('Integrate with the GitLab Agent');
describe('when there are no agent configurations in repository', () => {
it('should render notification message box', () => {
expect(findConfigurationsAlert().exists()).toBe(true);
});
it('should disable integration button', () => {
expect(findIntegrationButton().attributes('disabled')).toBe('true');
});
});
describe('when there is a list of agent configurations', () => {
beforeEach(() => {
propsData.hasConfigurations = true;
wrapper = shallowMountExtended(AgentEmptyState, {
propsData,
});
});
it('should render content without notification message box', () => {
expect(findEmptyState().exists()).toBe(true);
expect(findConfigurationsAlert().exists()).toBe(false);
expect(findIntegrationButton().attributes('disabled')).toBeUndefined();
});
});
});
......@@ -138,6 +138,28 @@ describe('Agents', () => {
expect(findAgentTable().exists()).toBe(false);
expect(findEmptyState().exists()).toBe(true);
});
it('should pass the correct project path to empty state component', () => {
expect(findEmptyState().props('projectPath')).toEqual('path/to/project');
});
});
describe('when the agent configurations are present', () => {
const trees = [
{
name: 'agent-1',
path: '.gitlab/agents/agent-1',
webPath: '/project/path/.gitlab/agents/agent-1',
},
];
beforeEach(() => {
return createWrapper({ agents: [], trees });
});
it('should pass the correct hasConfigurations boolean value to empty state component', () => {
expect(findEmptyState().props('hasConfigurations')).toEqual(true);
});
});
describe('when agents query has errored', () => {
......
......@@ -88,4 +88,28 @@ RSpec.describe Elastic::MigrationRecord, :elastic do
expect(described_class.load_versions(completed: false)).to eq([])
end
end
describe '#running?' do
using RSpec::Parameterized::TableSyntax
before do
allow(record).to receive(:halted?).and_return(halted)
allow(record).to receive(:started?).and_return(started)
allow(record).to receive(:completed?).and_return(completed)
end
where(:started, :halted, :completed, :expected) do
false | false | false | false
true | false | false | true
true | true | false | false
true | true | true | false
true | false | true | false
end
with_them do
it 'returns the expected result' do
expect(record.running?).to eq(expected)
end
end
end
end
......@@ -44,13 +44,34 @@ RSpec.describe 'admin/application_settings/_elasticsearch_form' do
end
context 'pending migrations' do
using RSpec::Parameterized::TableSyntax
let(:pending_migrations) { true }
let(:pause_indexing) { true }
let(:migration) { Elastic::DataMigrationService.migrations.first }
it 'renders a disabled pause checkbox' do
render
before do
allow(Elastic::DataMigrationService).to receive(:pending_migrations).and_return([migration])
allow(migration).to receive(:running?).and_return(running)
allow(migration).to receive(:pause_indexing?).and_return(pause_indexing)
end
where(:running, :pause_indexing, :disabled) do
false | false | false
false | true | false
true | false | false
true | true | true
end
with_them do
it 'renders pause checkbox with disabled set appropriately' do
render
expect(rendered).to have_css('input[id=application_setting_elasticsearch_pause_indexing][disabled="disabled"]')
if disabled
expect(rendered).to have_css('input[id=application_setting_elasticsearch_pause_indexing][disabled="disabled"]')
else
expect(rendered).not_to have_css('input[id=application_setting_elasticsearch_pause_indexing][disabled="disabled"]')
end
end
end
end
end
......
......@@ -6958,6 +6958,9 @@ msgstr ""
msgid "ClusterAgents|Description"
msgstr ""
msgid "ClusterAgents|Go to the repository"
msgstr ""
msgid "ClusterAgents|Integrate Kubernetes with a GitLab Agent"
msgstr ""
......@@ -6979,6 +6982,9 @@ msgstr ""
msgid "ClusterAgents|Never"
msgstr ""
msgid "ClusterAgents|Read more about getting started"
msgstr ""
msgid "ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}"
msgstr ""
......@@ -6988,6 +6994,9 @@ msgstr ""
msgid "ClusterAgents|This agent has no tokens"
msgstr ""
msgid "ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process."
msgstr ""
msgid "ClusterAgents|Unknown user"
msgstr ""
......@@ -32817,7 +32826,7 @@ msgstr ""
msgid "There are no variables yet."
msgstr ""
msgid "There are pending advanced search migrations. Indexing must remain paused until the migrations are completed."
msgid "There are pending advanced search migrations which require indexing to be paused. Indexing must remain paused until the migrations are completed."
msgstr ""
msgid "There are running deployments on the environment. Please retry later."
......
......@@ -66,7 +66,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
describe('with pipelines', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(200, [pipeline]);
mock.onGet('endpoint.json').reply(200, [pipeline], { 'x-total': 10 });
createComponent();
......@@ -110,7 +110,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
document.body.appendChild(element);
element.addEventListener('update-pipelines-count', (event) => {
expect(event.detail.pipelines).toEqual([pipeline]);
expect(event.detail.pipelineCount).toEqual(10);
done();
});
......
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