diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-12 12:09:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-12 12:09:31 +0300 |
commit | 0e1a6f6a2b28464e6ad151da4dced6d603bd11b0 (patch) | |
tree | b84d68dca1be62e789da50841ed283d99a4284b5 /app | |
parent | 143f7be045960f8d51dea738781535d614956f84 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
21 files changed, 303 insertions, 189 deletions
diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index 731aea996fb..10aac2f649e 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -1,4 +1,6 @@ +import { inactiveListId } from '~/boards/constants'; + export default () => ({ isShowingLabels: true, - activeListId: 0, + activeListId: inactiveListId, }); diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue index 46dacf30f39..eb575b9ed6c 100644 --- a/app/assets/javascripts/clusters_list/components/clusters.vue +++ b/app/assets/javascripts/clusters_list/components/clusters.vue @@ -1,6 +1,6 @@ <script> import { mapState, mapActions } from 'vuex'; -import { GlTable, GlLoadingIcon, GlBadge } from '@gitlab/ui'; +import { GlTable, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui'; import tooltip from '~/vue_shared/directives/tooltip'; import { CLUSTER_TYPES, STATUSES } from '../constants'; import { __, sprintf } from '~/locale'; @@ -8,54 +8,58 @@ import { __, sprintf } from '~/locale'; export default { components: { GlTable, + GlLink, GlLoadingIcon, GlBadge, }, directives: { tooltip, }, - fields: [ - { - key: 'name', - label: __('Kubernetes cluster'), - }, - { - key: 'environmentScope', - label: __('Environment scope'), - }, - { - key: 'size', - label: __('Size'), - }, - { - key: 'cpu', - label: __('Total cores (vCPUs)'), - }, - { - key: 'memory', - label: __('Total memory (GB)'), - }, - { - key: 'clusterType', - label: __('Cluster level'), - formatter: value => CLUSTER_TYPES[value], - }, - ], computed: { ...mapState(['clusters', 'loading']), + fields() { + return [ + { + key: 'name', + label: __('Kubernetes cluster'), + }, + { + key: 'environment_scope', + label: __('Environment scope'), + }, + // Wait for backend to send these fields + // { + // key: 'size', + // label: __('Size'), + // }, + // { + // key: 'cpu', + // label: __('Total cores (vCPUs)'), + // }, + // { + // key: 'memory', + // label: __('Total memory (GB)'), + // }, + { + key: 'cluster_type', + label: __('Cluster level'), + formatter: value => CLUSTER_TYPES[value], + }, + ]; + }, }, mounted() { - // TODO - uncomment this once integrated with BE - // this.fetchClusters(); + this.fetchClusters(); }, methods: { ...mapActions(['fetchClusters']), statusClass(status) { - return STATUSES[status].className; + const iconClass = STATUSES[status] || STATUSES.default; + return iconClass.className; }, statusTitle(status) { - const { title } = STATUSES[status]; - return sprintf(__('Status: %{title}'), { title }, false); + const iconTitle = STATUSES[status] || STATUSES.default; + return sprintf(__('Status: %{title}'), { title: iconTitle.title }, false); }, }, }; @@ -63,17 +67,13 @@ export default { <template> <gl-loading-icon v-if="loading" size="md" class="mt-3" /> - <gl-table - v-else - :items="clusters" - :fields="$options.fields" - stacked="md" - variant="light" - class="qa-clusters-table" - > + <gl-table v-else :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table"> <template #cell(name)="{ item }"> <div class="d-flex flex-row-reverse flex-md-row js-status"> - {{ item.name }} + <gl-link data-qa-selector="cluster" :data-qa-cluster-name="item.name" :href="item.path"> + {{ item.name }} + </gl-link> + <gl-loading-icon v-if="item.status === 'deleting'" v-tooltip @@ -84,13 +84,13 @@ export default { <div v-else v-tooltip - class="cluster-status-indicator rounded-circle align-self-center gl-w-8 gl-h-8 mr-2 ml-md-2" + class="cluster-status-indicator rounded-circle align-self-center gl-w-4 gl-h-4 mr-2 ml-md-2" :class="statusClass(item.status)" :title="statusTitle(item.status)" ></div> </div> </template> - <template #cell(clusterType)="{value}"> + <template #cell(cluster_type)="{value}"> <gl-badge variant="light"> {{ value }} </gl-badge> diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js index 9428f08176c..eebcaa086f9 100644 --- a/app/assets/javascripts/clusters_list/constants.js +++ b/app/assets/javascripts/clusters_list/constants.js @@ -7,8 +7,9 @@ export const CLUSTER_TYPES = { }; export const STATUSES = { + default: { className: 'bg-white', title: __('Unknown') }, disabled: { className: 'disabled', title: __('Disabled') }, - connected: { className: 'bg-success', title: __('Connected') }, + created: { className: 'bg-success', title: __('Connected') }, unreachable: { className: 'bg-danger', title: __('Unreachable') }, authentication_failure: { className: 'bg-warning', title: __('Authentication Failure') }, deleting: { title: __('Deleting') }, diff --git a/app/assets/javascripts/clusters_list/store/actions.js b/app/assets/javascripts/clusters_list/store/actions.js index 79bc9932438..d0ad92f5536 100644 --- a/app/assets/javascripts/clusters_list/store/actions.js +++ b/app/assets/javascripts/clusters_list/store/actions.js @@ -1,7 +1,5 @@ -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import Poll from '~/lib/utils/poll'; import axios from '~/lib/utils/axios_utils'; -import Visibility from 'visibilityjs'; import flash from '~/flash'; import { __ } from '~/locale'; import * as types from './mutation_types'; @@ -14,23 +12,16 @@ export const fetchClusters = ({ state, commit }) => { data: state.endpoint, method: 'fetchClusters', successCallback: ({ data }) => { - commit(types.SET_CLUSTERS_DATA, convertObjectPropsToCamelCase(data, { deep: true })); - commit(types.SET_LOADING_STATE, false); + if (data.clusters) { + commit(types.SET_CLUSTERS_DATA, data); + commit(types.SET_LOADING_STATE, false); + poll.stop(); + } }, errorCallback: () => flash(__('An error occurred while loading clusters')), }); - if (!Visibility.hidden()) { - poll.makeRequest(); - } - - Visibility.change(() => { - if (!Visibility.hidden()) { - poll.restart(); - } else { - poll.stop(); - } - }); + poll.makeRequest(); }; // prevent babel-plugin-rewire from generating an invalid default during karma tests diff --git a/app/assets/javascripts/clusters_list/store/mutations.js b/app/assets/javascripts/clusters_list/store/mutations.js index ffd3c4601bf..ce53a033628 100644 --- a/app/assets/javascripts/clusters_list/store/mutations.js +++ b/app/assets/javascripts/clusters_list/store/mutations.js @@ -4,9 +4,10 @@ export default { [types.SET_LOADING_STATE](state, value) { state.loading = value; }, - [types.SET_CLUSTERS_DATA](state, clusters) { + [types.SET_CLUSTERS_DATA](state, data) { Object.assign(state, { - clusters, + clusters: data.clusters, + hasAncestorClusters: data.has_ancestor_clusters, }); }, }; diff --git a/app/assets/javascripts/clusters_list/store/state.js b/app/assets/javascripts/clusters_list/store/state.js index ed032ed8435..31e73558c2e 100644 --- a/app/assets/javascripts/clusters_list/store/state.js +++ b/app/assets/javascripts/clusters_list/store/state.js @@ -1,5 +1,6 @@ export default (initialState = {}) => ({ endpoint: initialState.endpoint, - loading: false, // TODO - set this to true once integrated with BE + hasAncestorClusters: false, + loading: true, clusters: [], }); diff --git a/app/assets/javascripts/design_management/index.js b/app/assets/javascripts/design_management/index.js index 63e59f816d7..f0b02d07d93 100644 --- a/app/assets/javascripts/design_management/index.js +++ b/app/assets/javascripts/design_management/index.js @@ -7,7 +7,7 @@ import getDesignListQuery from './graphql/queries/get_design_list.query.graphql' import { DESIGNS_ROUTE_NAME, ROOT_ROUTE_NAME } from './router/constants'; export default () => { - const el = document.getElementById('js-design-management'); + const el = document.querySelector('.js-design-management'); const badge = document.querySelector('.js-designs-count'); const { issueIid, projectPath, issuePath } = el.dataset; const router = createRouter(issuePath); diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index 3e56379dc47..acfa97f625c 100644 --- a/app/assets/javascripts/design_management/pages/design/index.vue +++ b/app/assets/javascripts/design_management/pages/design/index.vue @@ -259,8 +259,10 @@ export default { }); }, trackEvent() { + // TODO: This needs to be made aware of referers, or if it's rendered in a different context than a Issue trackDesignDetailView( 'issue-design-collection', + 'issue', this.$route.query.version || this.latestVersionId, this.isLatestVersion, ); diff --git a/app/assets/javascripts/design_management/utils/tracking.js b/app/assets/javascripts/design_management/utils/tracking.js index c94aa83ecc0..39c20376271 100644 --- a/app/assets/javascripts/design_management/utils/tracking.js +++ b/app/assets/javascripts/design_management/utils/tracking.js @@ -4,8 +4,9 @@ function assembleDesignPayload(payloadArr) { return { value: { 'internal-object-refrerer': payloadArr[0], - 'version-number': payloadArr[1], - 'current-version': payloadArr[2], + 'design-collection-owner': payloadArr[1], + 'design-version-number': payloadArr[2], + 'design-is-current-version': payloadArr[3], }, }; } @@ -14,9 +15,14 @@ function assembleDesignPayload(payloadArr) { const DESIGN_TRACKING_PAGE_NAME = 'projects:issues:design'; // eslint-disable-next-line import/prefer-default-export -export function trackDesignDetailView(refrerer = '', designVersion = 1, latestVersion = false) { +export function trackDesignDetailView( + referer = '', + owner = '', + designVersion = 1, + latestVersion = false, +) { Tracking.event(DESIGN_TRACKING_PAGE_NAME, 'design_viewed', { label: 'design_viewed', - ...assembleDesignPayload([refrerer, designVersion, latestVersion]), + ...assembleDesignPayload([referer, owner, designVersion, latestVersion]), }); } diff --git a/app/assets/javascripts/issuables_list/eventhub.js b/app/assets/javascripts/issuables_list/eventhub.js index d1601a7d8f3..e31806ad199 100644 --- a/app/assets/javascripts/issuables_list/eventhub.js +++ b/app/assets/javascripts/issuables_list/eventhub.js @@ -1,5 +1,3 @@ -import Vue from 'vue'; +import createEventHub from '~/helpers/event_hub_factory'; -const issueablesEventBus = new Vue(); - -export default issueablesEventBus; +export default createEventHub(); diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index 75df80a0f6c..46c9b2fe0af 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -12,6 +12,16 @@ export default function() { initIssueableApp(); initSentryErrorStackTraceApp(); initRelatedMergeRequestsApp(); + + // .js-design-management is currently EE-only. + // This will be moved to CE as part of https://gitlab.com/gitlab-org/gitlab/-/issues/212566#frontend + // at which point this conditional can be removed. + if (document.querySelector('.js-design-management')) { + import(/* webpackChunkName: 'design_management' */ '~/design_management') + .then(module => module.default()) + .catch(() => {}); + } + new Issue(); // eslint-disable-line no-new new ShortcutsIssuable(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/registry/explorer/components/image_list.vue b/app/assets/javascripts/registry/explorer/components/image_list.vue new file mode 100644 index 00000000000..bc209b12738 --- /dev/null +++ b/app/assets/javascripts/registry/explorer/components/image_list.vue @@ -0,0 +1,124 @@ +<script> +import { GlPagination, GlTooltipDirective, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + +import { + ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + LIST_DELETE_BUTTON_DISABLED, + REMOVE_REPOSITORY_LABEL, + ROW_SCHEDULED_FOR_DELETION, +} from '../constants'; + +export default { + name: 'ImageList', + components: { + GlPagination, + ClipboardButton, + GlDeprecatedButton, + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + images: { + type: Array, + required: true, + }, + pagination: { + type: Object, + required: true, + }, + }, + i18n: { + LIST_DELETE_BUTTON_DISABLED, + REMOVE_REPOSITORY_LABEL, + ROW_SCHEDULED_FOR_DELETION, + ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + }, + computed: { + currentPage: { + get() { + return this.pagination.page; + }, + set(page) { + this.$emit('pageChange', page); + }, + }, + }, + methods: { + encodeListItem(item) { + const params = JSON.stringify({ name: item.path, tags_path: item.tags_path, id: item.id }); + return window.btoa(params); + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-flex-direction-column"> + <div + v-for="(listItem, index) in images" + :key="index" + v-gl-tooltip="{ + placement: 'left', + disabled: !listItem.deleting, + title: $options.i18n.ROW_SCHEDULED_FOR_DELETION, + }" + data-testid="rowItem" + > + <div + class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-py-2 gl-px-1 border-bottom" + :class="{ 'border-top': index === 0, 'disabled-content': listItem.deleting }" + > + <div class="gl-display-flex gl-align-items-center"> + <router-link + data-testid="detailsLink" + :to="{ name: 'details', params: { id: encodeListItem(listItem) } }" + > + {{ listItem.path }} + </router-link> + <clipboard-button + v-if="listItem.location" + :disabled="listItem.deleting" + :text="listItem.location" + :title="listItem.location" + css-class="btn-default btn-transparent btn-clipboard" + /> + <gl-icon + v-if="listItem.failedDelete" + v-gl-tooltip + :title="$options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE" + name="warning" + class="text-warning align-middle" + /> + </div> + <div + v-gl-tooltip="{ disabled: listItem.destroy_path }" + class="d-none d-sm-block" + :title="$options.i18n.LIST_DELETE_BUTTON_DISABLED" + > + <gl-deprecated-button + v-gl-tooltip + data-testid="deleteImageButton" + :disabled="!listItem.destroy_path || listItem.deleting" + :title="$options.i18n.REMOVE_REPOSITORY_LABEL" + :aria-label="$options.i18n.REMOVE_REPOSITORY_LABEL" + class="btn-inverted" + variant="danger" + @click="$emit('delete', listItem)" + > + <gl-icon name="remove" /> + </gl-deprecated-button> + </div> + </div> + </div> + <gl-pagination + v-model="currentPage" + :per-page="pagination.perPage" + :total-items="pagination.total" + align="center" + class="w-100 gl-mt-2" + /> + </div> +</template> diff --git a/app/assets/javascripts/registry/explorer/constants.js b/app/assets/javascripts/registry/explorer/constants.js index 4ca4c7088a6..7cbe657bfc0 100644 --- a/app/assets/javascripts/registry/explorer/constants.js +++ b/app/assets/javascripts/registry/explorer/constants.js @@ -37,6 +37,15 @@ export const DELETE_IMAGE_SUCCESS_MESSAGE = s__( 'ContainerRegistry|%{title} was successfully scheduled for deletion', ); +export const IMAGE_REPOSITORY_LIST_LABEL = s__('ContainerRegistry|Image Repositories'); + +export const SEARCH_PLACEHOLDER_TEXT = s__('ContainerRegistry|Filter by name'); + +export const EMPTY_RESULT_TITLE = s__('ContainerRegistry|Sorry, your filter produced no results.'); +export const EMPTY_RESULT_MESSAGE = s__( + 'ContainerRegistry|To widen your search, change or remove the filters above.', +); + // Image details page export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags'); diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue index e932544feb8..4efa6f08d84 100644 --- a/app/assets/javascripts/registry/explorer/pages/list.vue +++ b/app/assets/javascripts/registry/explorer/pages/list.vue @@ -2,53 +2,52 @@ import { mapState, mapActions } from 'vuex'; import { GlEmptyState, - GlPagination, GlTooltipDirective, - GlDeprecatedButton, - GlIcon, GlModal, GlSprintf, GlLink, GlAlert, GlSkeletonLoader, + GlSearchBoxByClick, } from '@gitlab/ui'; import Tracking from '~/tracking'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + import ProjectEmptyState from '../components/project_empty_state.vue'; import GroupEmptyState from '../components/group_empty_state.vue'; import ProjectPolicyAlert from '../components/project_policy_alert.vue'; import QuickstartDropdown from '../components/quickstart_dropdown.vue'; +import ImageList from '../components/image_list.vue'; + import { DELETE_IMAGE_SUCCESS_MESSAGE, DELETE_IMAGE_ERROR_MESSAGE, - ASYNC_DELETE_IMAGE_ERROR_MESSAGE, CONTAINER_REGISTRY_TITLE, CONNECTION_ERROR_TITLE, CONNECTION_ERROR_MESSAGE, LIST_INTRO_TEXT, - LIST_DELETE_BUTTON_DISABLED, - REMOVE_REPOSITORY_LABEL, REMOVE_REPOSITORY_MODAL_TEXT, - ROW_SCHEDULED_FOR_DELETION, + REMOVE_REPOSITORY_LABEL, + SEARCH_PLACEHOLDER_TEXT, + IMAGE_REPOSITORY_LIST_LABEL, + EMPTY_RESULT_TITLE, + EMPTY_RESULT_MESSAGE, } from '../constants'; export default { name: 'RegistryListApp', components: { GlEmptyState, - GlPagination, ProjectEmptyState, GroupEmptyState, ProjectPolicyAlert, - ClipboardButton, QuickstartDropdown, - GlDeprecatedButton, - GlIcon, + ImageList, GlModal, GlSprintf, GlLink, GlAlert, GlSkeletonLoader, + GlSearchBoxByClick, }, directives: { GlTooltip: GlTooltipDirective, @@ -60,20 +59,23 @@ export default { height: 40, }, i18n: { - containerRegistryTitle: CONTAINER_REGISTRY_TITLE, - connectionErrorTitle: CONNECTION_ERROR_TITLE, - connectionErrorMessage: CONNECTION_ERROR_MESSAGE, - introText: LIST_INTRO_TEXT, - deleteButtonDisabled: LIST_DELETE_BUTTON_DISABLED, - removeRepositoryLabel: REMOVE_REPOSITORY_LABEL, - removeRepositoryModalText: REMOVE_REPOSITORY_MODAL_TEXT, - rowScheduledForDeletion: ROW_SCHEDULED_FOR_DELETION, - asyncDeleteErrorMessage: ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + CONTAINER_REGISTRY_TITLE, + CONNECTION_ERROR_TITLE, + CONNECTION_ERROR_MESSAGE, + LIST_INTRO_TEXT, + REMOVE_REPOSITORY_MODAL_TEXT, + REMOVE_REPOSITORY_LABEL, + SEARCH_PLACEHOLDER_TEXT, + IMAGE_REPOSITORY_LIST_LABEL, + EMPTY_RESULT_TITLE, + EMPTY_RESULT_MESSAGE, }, data() { return { itemToDelete: {}, deleteAlertType: null, + search: null, + isEmpty: false, }; }, computed: { @@ -83,14 +85,6 @@ export default { label: 'registry_repository_delete', }; }, - currentPage: { - get() { - return this.pagination.page; - }, - set(page) { - this.requestImagesList({ page }); - }, - }, showQuickStartDropdown() { return Boolean(!this.isLoading && !this.config?.isGroupPage && this.images?.length); }, @@ -110,8 +104,11 @@ export default { ...mapActions(['requestImagesList', 'requestDeleteImage']), loadImageList(fromName) { if (!fromName || !this.images?.length) { - this.requestImagesList(); + return this.requestImagesList().then(() => { + this.isEmpty = this.images.length === 0; + }); } + return Promise.resolve(); }, deleteImage(item) { this.track('click_button'); @@ -128,10 +125,6 @@ export default { this.deleteAlertType = 'danger'; }); }, - encodeListItem(item) { - const params = JSON.stringify({ name: item.path, tags_path: item.tags_path, id: item.id }); - return window.btoa(params); - }, dismissDeleteAlert() { this.deleteAlertType = null; this.itemToDelete = {}; @@ -160,12 +153,12 @@ export default { <gl-empty-state v-if="config.characterError" - :title="$options.i18n.connectionErrorTitle" + :title="$options.i18n.CONNECTION_ERROR_TITLE" :svg-path="config.containersErrorImage" > <template #description> <p> - <gl-sprintf :message="$options.i18n.connectionErrorMessage"> + <gl-sprintf :message="$options.i18n.CONNECTION_ERROR_MESSAGE"> <template #docLink="{content}"> <gl-link :href="`${config.helpPagePath}#docker-connection-error`" target="_blank"> {{ content }} @@ -179,11 +172,11 @@ export default { <template v-else> <div> <div class="d-flex justify-content-between align-items-center"> - <h4>{{ $options.i18n.containerRegistryTitle }}</h4> + <h4>{{ $options.i18n.CONTAINER_REGISTRY_TITLE }}</h4> <quickstart-dropdown v-if="showQuickStartDropdown" class="d-none d-sm-block" /> </div> <p> - <gl-sprintf :message="$options.i18n.introText"> + <gl-sprintf :message="$options.i18n.LIST_INTRO_TEXT"> <template #docLink="{content}"> <gl-link :href="config.helpPagePath" target="_blank"> {{ content }} @@ -207,73 +200,40 @@ export default { </gl-skeleton-loader> </div> <template v-else> - <div v-if="images.length" ref="imagesList" class="d-flex flex-column"> - <div - v-for="(listItem, index) in images" - :key="index" - ref="rowItem" - v-gl-tooltip="{ - placement: 'left', - disabled: !listItem.deleting, - title: $options.i18n.rowScheduledForDeletion, - }" - > - <div - class="d-flex justify-content-between align-items-center py-2 px-1 border-bottom" - :class="{ 'border-top': index === 0, 'disabled-content': listItem.deleting }" - > - <div class="d-felx align-items-center"> - <router-link - ref="detailsLink" - :to="{ name: 'details', params: { id: encodeListItem(listItem) } }" - > - {{ listItem.path }} - </router-link> - <clipboard-button - v-if="listItem.location" - ref="clipboardButton" - :disabled="listItem.deleting" - :text="listItem.location" - :title="listItem.location" - css-class="btn-default btn-transparent btn-clipboard" - /> - <gl-icon - v-if="listItem.failedDelete" - v-gl-tooltip - :title="$options.i18n.asyncDeleteErrorMessage" - name="warning" - class="text-warning align-middle" - /> - </div> - <div - v-gl-tooltip="{ disabled: listItem.destroy_path }" - class="d-none d-sm-block" - :title="$options.i18n.deleteButtonDisabled" - > - <gl-deprecated-button - ref="deleteImageButton" - v-gl-tooltip - :disabled="!listItem.destroy_path || listItem.deleting" - :title="$options.i18n.removeRepositoryLabel" - :aria-label="$options.i18n.removeRepositoryLabel" - class="btn-inverted" - variant="danger" - @click="deleteImage(listItem)" - > - <gl-icon name="remove" /> - </gl-deprecated-button> - </div> + <template v-if="!isEmpty"> + <div class="gl-display-flex gl-p-1" data-testid="listHeader"> + <div class="gl-flex-fill-1"> + <h5>{{ $options.i18n.IMAGE_REPOSITORY_LIST_LABEL }}</h5> + </div> + <div> + <gl-search-box-by-click + v-model="search" + :placeholder="$options.i18n.SEARCH_PLACEHOLDER_TEXT" + @submit="requestImagesList({ name: $event })" + /> </div> </div> - <gl-pagination - v-model="currentPage" - :per-page="pagination.perPage" - :total-items="pagination.total" - align="center" - class="w-100 mt-2" + + <image-list + v-if="images.length" + :images="images" + :pagination="pagination" + @pageChange="requestImagesList({ pagination: { page: $event }, name: search })" + @delete="deleteImage" /> - </div> + <gl-empty-state + v-else + :svg-path="config.noContainersImage" + data-testid="emptySearch" + :title="$options.i18n.EMPTY_RESULT_TITLE" + class="container-message" + > + <template #description> + {{ $options.i18n.EMPTY_RESULT_MESSAGE }} + </template> + </gl-empty-state> + </template> <template v-else> <project-empty-state v-if="!config.isGroupPage" /> <group-empty-state v-else /> @@ -287,9 +247,9 @@ export default { @ok="handleDeleteImage" @cancel="track('cancel_delete')" > - <template #modal-title>{{ $options.i18n.removeRepositoryLabel }}</template> + <template #modal-title>{{ $options.i18n.REMOVE_REPOSITORY_LABEL }}</template> <p> - <gl-sprintf :message="$options.i18n.removeRepositoryModalText"> + <gl-sprintf :message="$options.i18n.REMOVE_REPOSITORY_MODAL_TEXT"> <template #title> <b>{{ itemToDelete.path }}</b> </template> diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js index 6e3cf3f0c80..7f80bc21d6e 100644 --- a/app/assets/javascripts/registry/explorer/stores/actions.js +++ b/app/assets/javascripts/registry/explorer/stores/actions.js @@ -23,12 +23,15 @@ export const receiveTagsListSuccess = ({ commit }, { data, headers }) => { commit(types.SET_TAGS_PAGINATION, headers); }; -export const requestImagesList = ({ commit, dispatch, state }, pagination = {}) => { +export const requestImagesList = ( + { commit, dispatch, state }, + { pagination = {}, name = null } = {}, +) => { commit(types.SET_MAIN_LOADING, true); const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination; return axios - .get(state.config.endpoint, { params: { page, per_page: perPage } }) + .get(state.config.endpoint, { params: { page, per_page: perPage, name } }) .then(({ data, headers }) => { dispatch('receiveImagesListSuccess', { data, headers }); }) diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss index ce1039832d3..e4466b44358 100644 --- a/app/assets/stylesheets/components/related_items_list.scss +++ b/app/assets/stylesheets/components/related_items_list.scss @@ -69,11 +69,6 @@ $item-weight-max-width: 48px; font-weight: $gl-font-weight-bold; } - .issue-token-state-icon-open, - .issue-token-state-icon-closed { - display: none; - } - .sortable-link { color: $gray-900; font-weight: normal; @@ -92,7 +87,8 @@ $item-weight-max-width: 48px; @include media-breakpoint-down(lg) { .issue-count-badge { - padding-left: 0; + padding: 0; + padding-right: 8px; } } } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e24384156c9..3270c7c131f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1317,6 +1317,14 @@ class MergeRequest < ApplicationRecord actual_head_pipeline&.has_reports?(Ci::JobArtifact.terraform_reports) end + def compare_accessibility_reports + unless has_accessibility_reports? + return { status: :error, status_reason: _('This merge request does not have accessibility reports') } + end + + compare_reports(Ci::CompareAccessibilityReportsService) + end + # TODO: this method and compare_test_reports use the same # result type, which is handled by the controller's #reports_response. # we should minimize mistakes by isolating the common parts. diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index 6b1d82e7557..5e669ff2e50 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -21,8 +21,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated can?(current_user, :create_cluster, clusterable) end - def index_path - polymorphic_path([clusterable, :clusters]) + def index_path(options = {}) + polymorphic_path([clusterable, :clusters], options) end def new_path(options = {}) diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb index 0c267fd5735..41071bc7bc7 100644 --- a/app/presenters/instance_clusterable_presenter.rb +++ b/app/presenters/instance_clusterable_presenter.rb @@ -13,8 +13,8 @@ class InstanceClusterablePresenter < ClusterablePresenter end override :index_path - def index_path - admin_clusters_path + def index_path(options = {}) + admin_clusters_path(options) end override :new_path diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 5f3dfdacc14..50431e50110 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -22,7 +22,9 @@ class IssuableBaseService < BaseService params.delete(:milestone_id) params.delete(:labels) params.delete(:add_label_ids) + params.delete(:add_labels) params.delete(:remove_label_ids) + params.delete(:remove_labels) params.delete(:label_ids) params.delete(:assignee_ids) params.delete(:assignee_id) diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml index 28002dbff92..86194842664 100644 --- a/app/views/clusters/clusters/index.html.haml +++ b/app/views/clusters/clusters/index.html.haml @@ -19,7 +19,7 @@ = link_to _('More information'), help_page_path('user/group/clusters/index', anchor: 'cluster-precedence') - if Feature.enabled?(:clusters_list_redesign) - #js-clusters-list-app{ data: { endpoint: 'todo/add/endpoint' } } + #js-clusters-list-app{ data: { endpoint: clusterable.index_path(format: :json) } } - else .clusters-table.js-clusters-list .gl-responsive-table-row.table-row-header{ role: "row" } |