diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-21 21:16:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-21 21:16:10 +0300 |
commit | d2f2219fd58e572c10d77183e2f65de8fcc8df96 (patch) | |
tree | 2cde75cd3d994b7febe1349ab61b7a94a95c78cf /app/assets | |
parent | 6323146895db2be6f04846b3c98060b7349207b9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
9 files changed, 130 insertions, 76 deletions
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index aacb460a817..8bd55e697fa 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -368,6 +368,7 @@ export default { </div> <div v-if="clusterAgent" :class="$options.kubernetesOverviewClasses"> <kubernetes-overview + :class="{ 'gl-ml-7': inFolder }" :cluster-agent="clusterAgent" :namespace="kubernetesNamespace" :flux-resource-path="fluxResourcePath" diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 0201fb53f77..05a2eba6af3 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo'; import VueRouter from 'vue-router'; import createDefaultClient from '~/lib/graphql'; import Translate from '~/vue_shared/translate'; +import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility'; import { apolloProvider } from '../graphql/client'; import EnvironmentsFolderView from './environments_folder_view.vue'; import EnvironmentsFolderApp from './environments_folder_app.vue'; @@ -20,10 +21,9 @@ export default () => { if (gon.features.environmentsFolderNewLook) { Vue.use(VueRouter); - const folderName = environmentsData.environmentsDataFolderName; - const folderPath = environmentsData.environmentsDataEndpoint.replace('.json', ''); - const projectPath = environmentsData.environmentsDataProjectPath; - const helpPagePath = environmentsData.environmentsDataHelpPagePath; + const folderPath = environmentsData.endpoint.replace('.json', ''); + const kasTunnelUrl = removeLastSlashInUrlPath(environmentsData.kasTunnelUrl); + const { projectPath, folderName, helpPagePath } = environmentsData; const router = new VueRouter({ mode: 'history', @@ -54,6 +54,7 @@ export default () => { provide: { projectPath, helpPagePath, + kasTunnelUrl, }, apolloProvider, router, @@ -74,8 +75,8 @@ export default () => { }, data() { return { - endpoint: environmentsData.environmentsDataEndpoint, - folderName: environmentsData.environmentsDataFolderName, + endpoint: environmentsData.endpoint, + folderName: environmentsData.folderName, cssContainerClass: environmentsData.cssClass, }; }, diff --git a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue index 52a12cc7771..e3fa0ce8073 100644 --- a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue +++ b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue @@ -1,20 +1,20 @@ <script> import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui'; import { debounce } from 'lodash'; +import produce from 'immer'; import { __ } from '~/locale'; import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; import { PROJECTS_PER_PAGE } from '../constants'; import getProjectsQuery from '../graphql/queries/get_projects.query.graphql'; export default { - PROJECTS_PER_PAGE, - projectQueryPageInfo: { - endCursor: '', - }, + name: 'ProjectDropdown', + components: { GlAvatarLabeled, GlCollapsibleListbox, }, + props: { selectedProject: { type: Object, @@ -22,27 +22,29 @@ export default { default: null, }, }, + data() { return { initialProjectsLoading: true, + isLoadingMore: false, projectSearchQuery: '', selectedProjectId: this.selectedProject?.id, }; }, + apollo: { projects: { query: getProjectsQuery, variables() { return { - search: this.projectSearchQuery, - first: this.$options.PROJECTS_PER_PAGE, - after: this.$options.projectQueryPageInfo.endCursor, - searchNamespaces: true, - sort: 'similarity', + ...this.queryVariables, }; }, update(data) { - return data?.projects?.nodes.filter((project) => !project.repository?.empty) ?? []; + return { + nodes: data?.projects?.nodes.filter((project) => !project.repository?.empty) ?? [], + pageInfo: data?.projects?.pageInfo, + }; }, result() { this.initialProjectsLoading = false; @@ -52,24 +54,37 @@ export default { }, }, }, + computed: { - projectsLoading() { - return Boolean(this.$apollo.queries.projects.loading); + queryVariables() { + return { + search: this.projectSearchQuery, + first: PROJECTS_PER_PAGE, + searchNamespaces: true, + sort: 'similarity', + }; + }, + isLoading() { + return this.$apollo.queries.projects.loading && !this.isLoadingMore; }, projectDropdownText() { return this.selectedProject?.nameWithNamespace || this.$options.i18n.selectProjectText; }, projectList() { - return (this.projects || []).map((project) => ({ + return (this.projects?.nodes || []).map((project) => ({ ...project, text: project.nameWithNamespace, value: String(project.id), })); }, + hasNextPage() { + return this.projects?.pageInfo?.hasNextPage; + }, }, + methods: { findProjectById(id) { - return this.projects.find((project) => id === project.id); + return this.projects?.nodes?.find((project) => id === project.id); }, onProjectSelect(projectId) { this.$emit('change', this.findProjectById(projectId)); @@ -77,13 +92,41 @@ export default { onError({ message } = {}) { this.$emit('error', { message }); }, + async onBottomReached() { + if (!this.hasNextPage) return; + + this.isLoadingMore = true; + + try { + await this.$apollo.queries.projects.fetchMore({ + variables: { + ...this.queryVariables, + after: this.projects.pageInfo?.endCursor, + }, + updateQuery: (previousResult, { fetchMoreResult }) => { + return produce(fetchMoreResult, (draftData) => { + draftData.projects.nodes = [ + ...previousResult.projects.nodes, + ...draftData.projects.nodes, + ]; + }); + }, + }); + } catch (error) { + this.onError({ message: __('Failed to load projects') }); + } finally { + this.isLoadingMore = false; + } + }, onSearch: debounce(function debouncedSearch(query) { this.projectSearchQuery = query; }, 250), }, + i18n: { selectProjectText: __('Select a project'), }, + AVATAR_SHAPE_OPTION_RECT, }; </script> @@ -97,8 +140,11 @@ export default { :header-text="$options.i18n.selectProjectText" :loading="initialProjectsLoading" :searchable="true" - :searching="projectsLoading" + :searching="isLoading" fluid-width + infinite-scroll + :infinite-scroll-loading="isLoadingMore" + @bottom-reached="onBottomReached" @search="onSearch" @select="onProjectSelect" > diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js index d9ac0abf7b3..77986539403 100644 --- a/app/assets/javascripts/lib/utils/constants.js +++ b/app/assets/javascripts/lib/utils/constants.js @@ -19,10 +19,5 @@ export const DRAWER_Z_INDEX = 252; export const MIN_USERNAME_LENGTH = 2; -export const BYTES_FORMAT_BYTES = 'B'; -export const BYTES_FORMAT_KIB = 'KiB'; -export const BYTES_FORMAT_MIB = 'MiB'; -export const BYTES_FORMAT_GIB = 'GiB'; - export const DEFAULT_CI_CONFIG_PATH = '.gitlab-ci.yml'; export const CI_CONFIG_PATH_EXTENSION = /(\.gitlab-ci\.yml)/; diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index d17719c0bc0..01c5bc1f1fc 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -1,12 +1,5 @@ import { sprintf, __ } from '~/locale'; -import { - BYTES_IN_KIB, - THOUSAND, - BYTES_FORMAT_BYTES, - BYTES_FORMAT_KIB, - BYTES_FORMAT_MIB, - BYTES_FORMAT_GIB, -} from './constants'; +import { BYTES_IN_KIB, THOUSAND } from './constants'; /** * Function that allows a number with an X amount of decimals @@ -73,47 +66,47 @@ export function bytesToGiB(number) { /** * Formats the bytes in number into a more understandable * representation. Returns an array with the first value being the human size - * and the second value being the format (e.g., [1.5, 'KiB']). + * and the second value being the label (e.g., [1.5, 'KiB']). * - * @param {Number} size - * @param {Number} digits - The number of digits to appear after the decimal point - * @returns {String} + * @param {number} size + * @param {number} [digits=2] - The number of digits to appear after the decimal point + * @returns {string[]} */ export function numberToHumanSizeSplit(size, digits = 2) { const abs = Math.abs(size); if (abs < BYTES_IN_KIB) { - return [size.toString(), BYTES_FORMAT_BYTES]; + return [size.toString(), __('B')]; } if (abs < BYTES_IN_KIB ** 2) { - return [bytesToKiB(size).toFixed(digits), BYTES_FORMAT_KIB]; + return [bytesToKiB(size).toFixed(digits), __('KiB')]; } if (abs < BYTES_IN_KIB ** 3) { - return [bytesToMiB(size).toFixed(digits), BYTES_FORMAT_MIB]; + return [bytesToMiB(size).toFixed(digits), __('MiB')]; } - return [bytesToGiB(size).toFixed(digits), BYTES_FORMAT_GIB]; + return [bytesToGiB(size).toFixed(digits), __('GiB')]; } /** * Port of rails number_to_human_size * Formats the bytes in number into a more understandable - * representation (e.g., giving it 1500 yields 1.5 KB). + * representation (e.g., giving it 1536 yields 1.5 KiB). * - * @param {Number} size - * @param {Number} digits - The number of digits to appear after the decimal point - * @returns {String} + * @param {number} size + * @param {number} [digits=2] - The number of digits to appear after the decimal point + * @returns {string} */ export function numberToHumanSize(size, digits = 2) { - const [humanSize, format] = numberToHumanSizeSplit(size, digits); + const [humanSize, label] = numberToHumanSizeSplit(size, digits); - switch (format) { - case BYTES_FORMAT_BYTES: + switch (label) { + case __('B'): return sprintf(__('%{size} B'), { size: humanSize }); - case BYTES_FORMAT_KIB: + case __('KiB'): return sprintf(__('%{size} KiB'), { size: humanSize }); - case BYTES_FORMAT_MIB: + case __('MiB'): return sprintf(__('%{size} MiB'), { size: humanSize }); - case BYTES_FORMAT_GIB: + case __('GiB'): return sprintf(__('%{size} GiB'), { size: humanSize }); default: return ''; diff --git a/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue index 874803a720d..eb56113a4dd 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue @@ -1,6 +1,6 @@ <script> // eslint-disable-next-line no-restricted-imports -import { mapActions, mapState, mapGetters } from 'vuex'; +import { mapActions, mapGetters } from 'vuex'; import { s__ } from '~/locale'; import eventHub from '~/super_sidebar/event_hub'; import NavItem from '~/super_sidebar/components/nav_item.vue'; @@ -15,15 +15,11 @@ export default { NavItem, }, computed: { - ...mapState(['navigation', 'urlQuery']), ...mapGetters(['navigationItems']), }, created() { eventHub.$emit('toggle-menu-header', false); - - if (this.urlQuery?.search) { - this.fetchSidebarCount(); - } + this.fetchSidebarCount(); }, methods: { ...mapActions(['fetchSidebarCount']), diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index 211bbaf82cd..d5e275b8a19 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -1,7 +1,13 @@ import Api from '~/api'; import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; -import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; +import { + visitUrl, + setUrlParams, + getBaseURL, + queryToObject, + objectToQuery, +} from '~/lib/utils/url_utility'; import { logError } from '~/lib/logger'; import { __ } from '~/locale'; import { labelFilterData } from '~/search/sidebar/components/label_filter/data'; @@ -135,19 +141,38 @@ export const setLabelFilterSearch = ({ commit }, { value }) => { commit(types.SET_LABEL_SEARCH_STRING, value); }; +const injectWildCardSearch = (state, link) => { + const urlObject = new URL(`${getBaseURL()}${link}`); + if (!state.urlQuery.search) { + const queryObject = queryToObject(urlObject.search); + urlObject.search = objectToQuery({ ...queryObject, search: '*' }); + } + + return urlObject.href; +}; + export const fetchSidebarCount = ({ commit, state }) => { - const promises = Object.values(state.navigation).map((navItem) => { - // active nav item has count already so we skip it - if (!navItem.active && navItem.count_link) { - return axios - .get(navItem.count_link) - .then(({ data: { count } }) => { - commit(types.RECEIVE_NAVIGATION_COUNT, { key: navItem.scope, count }); - }) - .catch((e) => logError(e)); - } - return Promise.resolve(); - }); + const items = Object.values(state.navigation) + .filter((navigationItem) => !navigationItem.active && navigationItem.count_link) + .map((navItem) => { + const navigationItem = { ...navItem }; + + if (navigationItem.count_link) { + navigationItem.count_link = injectWildCardSearch(state, navigationItem.count_link); + } + + return navigationItem; + }); + + const promises = items.map((navigationItem) => + axios + .get(navigationItem.count_link) + .then(({ data: { count } }) => { + commit(types.RECEIVE_NAVIGATION_COUNT, { key: navigationItem.scope, count }); + }) + .catch((e) => logError(e)), + ); + return Promise.all(promises); }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index a4afdee4d49..1aed3362c42 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -48,7 +48,7 @@ export default { variables() { return this.mergeRequestQueryVariables; }, - update: (data) => data.project.mergeRequest, + update: (data) => data.project?.mergeRequest || {}, }, }, components: { @@ -90,7 +90,7 @@ export default { return this.state.rebaseInProgress; }, canPushToSourceBranch() { - return this.state.userPermissions.pushToSourceBranch; + return this.state.userPermissions?.pushToSourceBranch || false; }, targetBranch() { return this.state.targetBranch; diff --git a/app/assets/stylesheets/page_bundles/login.scss b/app/assets/stylesheets/page_bundles/login.scss index b63f199f7b9..4cc44b01e60 100644 --- a/app/assets/stylesheets/page_bundles/login.scss +++ b/app/assets/stylesheets/page_bundles/login.scss @@ -230,11 +230,8 @@ height: 100%; body { - padding-top: 48px; // Remove this line when the restyle_login_page feature flag is deleted. Instead, add self-align `center` to container, and maybe a top margin. - &.with-system-header { padding-top: $system-header-height; - padding-top: calc(#{$system-header-height} + 48px); // Remove this line when the restyle_login_page feature flag is deleted } &.with-system-footer { |