diff --git a/app/assets/javascripts/usage_quotas/storage/init_project_storage.js b/app/assets/javascripts/usage_quotas/storage/init_project_storage.js deleted file mode 100644 index e7378fcde0e..00000000000 --- a/app/assets/javascripts/usage_quotas/storage/init_project_storage.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; -import ProjectStorageApp from './components/project_storage_app.vue'; - -Vue.use(VueApollo); - -export default (containerId = 'js-project-storage-count-app') => { - const el = document.getElementById(containerId); - - if (!el) { - return false; - } - - const { projectPath } = el.dataset; - - const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), - }); - - return new Vue({ - el, - apolloProvider, - name: 'ProjectStorageApp', - provide: { - projectPath, - }, - render(createElement) { - return createElement(ProjectStorageApp); - }, - }); -}; diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.stories.js b/app/assets/javascripts/usage_quotas/storage/project/components/project_storage_app.stories.js similarity index 96% rename from app/assets/javascripts/usage_quotas/storage/components/project_storage_app.stories.js rename to app/assets/javascripts/usage_quotas/storage/project/components/project_storage_app.stories.js index cd990ccc77a..966ccd1f29e 100644 --- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.stories.js +++ b/app/assets/javascripts/usage_quotas/storage/project/components/project_storage_app.stories.js @@ -4,7 +4,7 @@ import getProjectStorageStatisticsQuery from 'ee_else_ce/usage_quotas/storage/qu import ProjectStorageApp from './project_storage_app.vue'; const meta = { - title: 'usage_quotas/storage/project_storage_app', + title: 'usage_quotas/storage/project/project_storage_app', component: ProjectStorageApp, }; diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue b/app/assets/javascripts/usage_quotas/storage/project/components/project_storage_app.vue similarity index 99% rename from app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue rename to app/assets/javascripts/usage_quotas/storage/project/components/project_storage_app.vue index 0ae29014fbe..90fbeed4cab 100644 --- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue +++ b/app/assets/javascripts/usage_quotas/storage/project/components/project_storage_app.vue @@ -20,7 +20,7 @@ import { NAMESPACE_STORAGE_TYPES, usageQuotasHelpPaths, storageTypeHelpPaths, -} from '../constants'; +} from '../../constants'; import { getStorageTypesFromProjectStatistics, descendingStorageUsageSort } from '../utils'; import ProjectStorageDetail from './project_storage_detail.vue'; diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue b/app/assets/javascripts/usage_quotas/storage/project/components/project_storage_detail.vue similarity index 99% rename from app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue rename to app/assets/javascripts/usage_quotas/storage/project/components/project_storage_detail.vue index 35e43c76310..211933e571b 100644 --- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue +++ b/app/assets/javascripts/usage_quotas/storage/project/components/project_storage_detail.vue @@ -7,7 +7,7 @@ import { HELP_LINK_ARIA_LABEL, PROJECT_TABLE_LABEL_STORAGE_TYPE, PROJECT_TABLE_LABEL_USAGE, -} from '../constants'; +} from '../../constants'; import StorageTypeIcon from './storage_type_icon.vue'; export default { diff --git a/app/assets/javascripts/usage_quotas/storage/components/storage_type_icon.vue b/app/assets/javascripts/usage_quotas/storage/project/components/storage_type_icon.vue similarity index 100% rename from app/assets/javascripts/usage_quotas/storage/components/storage_type_icon.vue rename to app/assets/javascripts/usage_quotas/storage/project/components/storage_type_icon.vue diff --git a/app/assets/javascripts/usage_quotas/storage/project/utils.js b/app/assets/javascripts/usage_quotas/storage/project/utils.js new file mode 100644 index 00000000000..445c3efc9e6 --- /dev/null +++ b/app/assets/javascripts/usage_quotas/storage/project/utils.js @@ -0,0 +1,37 @@ +/** + * Populates an array of storage types with usage value and other details + * + * @param {Array} selectedStorageTypes selected storage types that will be populated + * @param {Object} projectStatistics object of storage values, with storage type as keys + * @param {Object} statisticsDetailsPaths object of storage detail paths, with storage type as keys + * @param {Object} helpLinks object of help paths, with storage type as keys + * @returns {Array} + */ +export const getStorageTypesFromProjectStatistics = ( + selectedStorageTypes, + projectStatistics, + statisticsDetailsPaths = {}, + helpLinks = {}, +) => + selectedStorageTypes.reduce((types, currentType) => { + const helpPath = helpLinks[currentType.id]; + const value = projectStatistics[`${currentType.id}Size`]; + const detailsPath = statisticsDetailsPaths[currentType.id]; + + return types.concat({ + ...currentType, + helpPath, + detailsPath, + value, + }); + }, []); + +/** + * Creates a sorting function to sort storage types by usage in the graph and in the table + * + * @param {string} storageUsageKey key storing value of storage usage + * @returns {Function} sorting function + */ +export function descendingStorageUsageSort(storageUsageKey) { + return (a, b) => b[storageUsageKey] - a[storageUsageKey]; +} diff --git a/app/assets/javascripts/usage_quotas/storage/tab_metadata.js b/app/assets/javascripts/usage_quotas/storage/tab_metadata.js index 3e4fbada976..90c9a2dd665 100644 --- a/app/assets/javascripts/usage_quotas/storage/tab_metadata.js +++ b/app/assets/javascripts/usage_quotas/storage/tab_metadata.js @@ -10,7 +10,7 @@ import { STORAGE_TAB_METADATA_EL_SELECTOR, } from '../constants'; import NamespaceStorageApp from './components/namespace_storage_app.vue'; -import ProjectStorageApp from './components/project_storage_app.vue'; +import ProjectStorageApp from './project/components/project_storage_app.vue'; const parseProjectProvideData = (el) => { const { projectPath } = el.dataset; diff --git a/app/assets/javascripts/usage_quotas/storage/utils.js b/app/assets/javascripts/usage_quotas/storage/utils.js index 6800c272a2a..10256a572f2 100644 --- a/app/assets/javascripts/usage_quotas/storage/utils.js +++ b/app/assets/javascripts/usage_quotas/storage/utils.js @@ -3,44 +3,6 @@ import { numberToHumanSize } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; import { storageTypeHelpPaths } from './constants'; -/** - * Populates an array of storage types with usage value and other details - * - * @param {Array} selectedStorageTypes selected storage types that will be populated - * @param {Object} projectStatistics object of storage values, with storage type as keys - * @param {Object} statisticsDetailsPaths object of storage detail paths, with storage type as keys - * @param {Object} helpLinks object of help paths, with storage type as keys - * @returns {Array} - */ -export const getStorageTypesFromProjectStatistics = ( - selectedStorageTypes, - projectStatistics, - statisticsDetailsPaths = {}, - helpLinks = {}, -) => - selectedStorageTypes.reduce((types, currentType) => { - const helpPath = helpLinks[currentType.id]; - const value = projectStatistics[`${currentType.id}Size`]; - const detailsPath = statisticsDetailsPaths[currentType.id]; - - return types.concat({ - ...currentType, - helpPath, - detailsPath, - value, - }); - }, []); - -/** - * Creates a sorting function to sort storage types by usage in the graph and in the table - * - * @param {string} storageUsageKey key storing value of storage usage - * @returns {Function} sorting function - */ -export function descendingStorageUsageSort(storageUsageKey) { - return (a, b) => b[storageUsageKey] - a[storageUsageKey]; -} - /** * This method parses the results from `getNamespaceStorageStatistics` * call. diff --git a/app/controllers/base_action_controller.rb b/app/controllers/base_action_controller.rb index e251c44f63d..d2e766559a9 100644 --- a/app/controllers/base_action_controller.rb +++ b/app/controllers/base_action_controller.rb @@ -26,19 +26,23 @@ class BaseActionController < ActionController::Base next if p.directives.blank? if helpers.vite_enabled? - vite_port = ViteRuby.instance.config.port - vite_origin = "#{Gitlab.config.gitlab.host}:#{vite_port}" - http_origin = "http://#{vite_origin}" - ws_origin = "ws://#{vite_origin}" - wss_origin = "wss://#{vite_origin}" - gitlab_ws_origin = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/') - http_path = Gitlab::Utils.append_path(http_origin, 'vite-dev/') + # Normally all Vite requests are proxied via Vite Ruby's middleware (example: + # https://gdk.test:3000/vite-dev/@fs/path/to/your/gdk), unless the + # skipProxy parameter is used (https://vite-ruby.netlify.app/config/#skipproxy-experimental). + # + # However, HMR requests go directly to another host, and we need to allow that. + # We need both Websocket and HTTP URLs because Vite will attempt to ping + # the HTTP URL if the Websocket isn't available: + # https://github.com/vitejs/vite/blob/899d9b1d272b7057aafc6fa01570d40f288a473b/packages/vite/src/client/client.ts#L320-L327 + hmr_ws_url = Gitlab::Utils.append_path(helpers.vite_hmr_websocket_url, 'vite-dev/') + hmr_http_url = Gitlab::Utils.append_path(helpers.vite_hmr_http_url, 'vite-dev/') + http_path = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/') connect_sources = p.directives['connect-src'] - p.connect_src(*(Array.wrap(connect_sources) | [ws_origin, wss_origin, http_path])) + p.connect_src(*(Array.wrap(connect_sources) | [hmr_ws_url, hmr_http_url])) worker_sources = p.directives['worker-src'] - p.worker_src(*(Array.wrap(worker_sources) | [gitlab_ws_origin, http_path])) + p.worker_src(*(Array.wrap(worker_sources) | [hmr_ws_url, hmr_http_url, http_path])) end next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank? diff --git a/app/helpers/groups/settings_helper.rb b/app/helpers/groups/settings_helper.rb index 38300043dd7..99a0674e9c4 100644 --- a/app/helpers/groups/settings_helper.rb +++ b/app/helpers/groups/settings_helper.rb @@ -11,7 +11,8 @@ module Groups button_testid: 'remove-group-button', disabled: group.prevent_delete?.to_s, confirm_danger_message: remove_group_message(group), - phrase: group.full_path + phrase: group.full_path, + html_confirmation_message: 'true' } end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 43597cfccfb..ad4b3dd36f6 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -86,8 +86,42 @@ module GroupsHelper # Overridden in EE def remove_group_message(group) - _("You are going to remove %{group_name}. This will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % - { group_name: group.name } + content_tag :div do + content = ''.html_safe + content << content_tag(:span, _("You are about to remove the group %{group_name}.") % { group_name: group.name }) + + additional_content = additional_removed_items(group) + content << additional_content if additional_content.present? + + content << remove_group_warning + end + end + + def additional_removed_items(group) + list_content = ''.html_safe + + list_content << content_tag(:li, pluralize(group.subgroup_count, _('subgroup'))) if group.subgroup_count > 0 + list_content << content_tag(:li, pluralize(group.all_projects.non_archived.count, _('active project'))) if group.all_projects.non_archived.count > 0 + list_content << content_tag(:li, pluralize(group.all_projects.archived.count, _('archived project'))) if group.all_projects.archived.count > 0 + + if list_content.present? + content_tag(:span, _(" This action will also remove:")) + + content_tag(:ul) do + list_content + end + else + ''.html_safe + end + end + + def remove_group_warning + message = _('After you remove a group, you %{strongOpen}cannot%{strongClose} restore it or its components.') + content_tag(:p, class: 'gl-mb-0') do + ERB::Util.html_escape(message) % { + strongOpen: ''.html_safe, + strongClose: ''.html_safe + } + end end def share_with_group_lock_help_text(group) diff --git a/app/helpers/vite_helper.rb b/app/helpers/vite_helper.rb index 1d2ff400995..556155f3c85 100644 --- a/app/helpers/vite_helper.rb +++ b/app/helpers/vite_helper.rb @@ -7,4 +7,12 @@ module ViteHelper Gitlab::Utils.to_boolean(ViteRuby.env['VITE_ENABLED'], default: false) end + + def vite_hmr_websocket_url + ViteRuby.env['VITE_HMR_WS_URL'] + end + + def vite_hmr_http_url + ViteRuby.env['VITE_HMR_HTTP_URL'] + end end diff --git a/lib/vite_gdk.rb b/lib/vite_gdk.rb index 16ae4b03f53..9b70fca0146 100644 --- a/lib/vite_gdk.rb +++ b/lib/vite_gdk.rb @@ -21,7 +21,12 @@ module ViteGdk hmr_host = hmr_config['host'] || host hmr_port = hmr_config['clientPort'] || hmr_config['port'] || port hmr_ws_protocol = hmr_config['protocol'] || 'ws' + hmr_http_protocol = hmr_ws_protocol == 'wss' ? 'https' : 'http' ViteRuby.env['VITE_HMR_HOST'] = hmr_host + # If the Websocket connection to the HMR host is not up, Vite will attempt to + # ping the HMR host via HTTP or HTTPS: + # https://github.com/vitejs/vite/blob/899d9b1d272b7057aafc6fa01570d40f288a473b/packages/vite/src/client/client.ts#L320-L327 + ViteRuby.env['VITE_HMR_HTTP_URL'] = "#{hmr_http_protocol}://#{hmr_host}:#{hmr_port}" ViteRuby.env['VITE_HMR_WS_URL'] = "#{hmr_ws_protocol}://#{hmr_host}:#{hmr_port}" ViteRuby.configure( diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 05db439beb9..ebcf00f6a0c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -33,6 +33,9 @@ msgstr "" msgid " Please sign in." msgstr "" +msgid " This action will also remove:" +msgstr "" + msgid " Try to %{action} this file again." msgstr "" @@ -4524,6 +4527,9 @@ msgstr "" msgid "After you enable the integration, the following protected variables are created for CI/CD use:" msgstr "" +msgid "After you remove a group, you %{strongOpen}cannot%{strongClose} restore it or its components." +msgstr "" + msgid "After you've reviewed these contribution guidelines, you'll be all set to" msgstr "" @@ -57863,6 +57869,9 @@ msgstr "" msgid "You are about to incur additional charges" msgstr "" +msgid "You are about to remove the group %{group_name}." +msgstr "" + msgid "You are already a member of this %{member_source}." msgstr "" @@ -57890,9 +57899,6 @@ msgstr "" msgid "You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" -msgid "You are going to remove %{group_name}. This will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" -msgstr "" - msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?" msgstr "" @@ -59053,6 +59059,9 @@ msgstr[1] "" msgid "access:" msgstr "" +msgid "active project" +msgstr "" + msgid "add at least one file to the repository" msgstr "" @@ -59121,6 +59130,9 @@ msgid_plural "approvals" msgstr[0] "" msgstr[1] "" +msgid "archived project" +msgstr "" + msgid "archived:" msgstr "" @@ -61046,6 +61058,9 @@ msgstr "" msgid "stuck" msgstr "" +msgid "subgroup" +msgstr "" + msgid "success" msgstr "" diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js b/spec/frontend/usage_quotas/storage/project/components/project_storage_app_spec.js similarity index 97% rename from spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js rename to spec/frontend/usage_quotas/storage/project/components/project_storage_app_spec.js index 39db5140d0d..97de8dfda3b 100644 --- a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js +++ b/spec/frontend/usage_quotas/storage/project/components/project_storage_app_spec.js @@ -5,12 +5,12 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { getPreferredLocales } from '~/locale'; -import ProjectStorageApp from '~/usage_quotas/storage/components/project_storage_app.vue'; +import ProjectStorageApp from '~/usage_quotas/storage/project/components/project_storage_app.vue'; import SectionedPercentageBar from '~/usage_quotas/components/sectioned_percentage_bar.vue'; import { descendingStorageUsageSort, getStorageTypesFromProjectStatistics, -} from '~/usage_quotas/storage/utils'; +} from '~/usage_quotas/storage/project/utils'; import { storageTypeHelpPaths, PROJECT_STORAGE_TYPES, @@ -24,7 +24,7 @@ import { mockGetProjectStorageStatisticsGraphQLResponse, mockEmptyResponse, defaultProjectProvideValues, -} from '../mock_data'; +} from '../../mock_data'; Vue.use(VueApollo); @@ -34,6 +34,7 @@ jest.mock('~/locale', () => ({ })); describe('ProjectStorageApp', () => { + /** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */ let wrapper; const createMockApolloProvider = ({ reject = false, mockedValue } = {}) => { diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js b/spec/frontend/usage_quotas/storage/project/components/project_storage_detail_spec.js similarity index 93% rename from spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js rename to spec/frontend/usage_quotas/storage/project/components/project_storage_detail_spec.js index 364517a474f..a73528b03db 100644 --- a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js +++ b/spec/frontend/usage_quotas/storage/project/components/project_storage_detail_spec.js @@ -1,11 +1,11 @@ import { GlTableLite } from '@gitlab/ui'; import { mount, Wrapper } from '@vue/test-utils'; // eslint-disable-line no-unused-vars import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import ProjectStorageDetail from '~/usage_quotas/storage/components/project_storage_detail.vue'; +import ProjectStorageDetail from '~/usage_quotas/storage/project/components/project_storage_detail.vue'; import { numberToHumanSize } from '~/lib/utils/number_utils'; describe('ProjectStorageDetail', () => { - /** @type { Wrapper } */ + /** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */ let wrapper; const generateStorageType = (props) => { diff --git a/spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js b/spec/frontend/usage_quotas/storage/project/components/storage_type_icon_spec.js similarity index 87% rename from spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js rename to spec/frontend/usage_quotas/storage/project/components/storage_type_icon_spec.js index 92c24400e76..3ad6750ec43 100644 --- a/spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js +++ b/spec/frontend/usage_quotas/storage/project/components/storage_type_icon_spec.js @@ -1,8 +1,9 @@ import { mount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; -import StorageTypeIcon from '~/usage_quotas/storage/components/storage_type_icon.vue'; +import StorageTypeIcon from '~/usage_quotas/storage/project/components/storage_type_icon.vue'; describe('StorageTypeIcon', () => { + /** @type {import('@vue/test-utils').Wrapper} */ let wrapper; const createComponent = (props = {}) => { diff --git a/spec/frontend/usage_quotas/storage/project/utils_spec.js b/spec/frontend/usage_quotas/storage/project/utils_spec.js new file mode 100644 index 00000000000..cabe42833e1 --- /dev/null +++ b/spec/frontend/usage_quotas/storage/project/utils_spec.js @@ -0,0 +1,72 @@ +import { PROJECT_STORAGE_TYPES } from '~/usage_quotas/storage/constants'; +import { + getStorageTypesFromProjectStatistics, + descendingStorageUsageSort, +} from '~/usage_quotas/storage/project/utils'; +import { mockGetProjectStorageStatisticsGraphQLResponse } from 'jest/usage_quotas/storage/mock_data'; + +describe('getStorageTypesFromProjectStatistics', () => { + const { + statistics: projectStatistics, + statisticsDetailsPaths, + } = mockGetProjectStorageStatisticsGraphQLResponse.data.project; + + describe('matches project statistics value with matching storage type', () => { + const typesWithStats = getStorageTypesFromProjectStatistics( + PROJECT_STORAGE_TYPES, + projectStatistics, + ); + + it.each(PROJECT_STORAGE_TYPES)('storage type: $id', ({ id }) => { + expect(typesWithStats).toContainEqual( + expect.objectContaining({ + id, + value: projectStatistics[`${id}Size`], + }), + ); + }); + }); + + it('adds helpPath to a relevant type', () => { + const helpLinks = PROJECT_STORAGE_TYPES.reduce((acc, { id }) => { + return { + ...acc, + [id]: `url://${id}`, + }; + }, {}); + + const typesWithStats = getStorageTypesFromProjectStatistics( + PROJECT_STORAGE_TYPES, + projectStatistics, + {}, + helpLinks, + ); + + typesWithStats.forEach((type) => { + expect(type.helpPath).toBe(helpLinks[type.id]); + }); + }); + + it('adds details page path', () => { + const typesWithStats = getStorageTypesFromProjectStatistics( + PROJECT_STORAGE_TYPES, + projectStatistics, + statisticsDetailsPaths, + {}, + ); + typesWithStats.forEach((type) => { + expect(type.detailsPath).toBe(statisticsDetailsPaths[type.id]); + }); + }); +}); + +describe('descendingStorageUsageSort', () => { + it('sorts items by a given key in descending order', () => { + const items = [{ k: 1 }, { k: 3 }, { k: 2 }]; + + const sorted = [...items].sort(descendingStorageUsageSort('k')); + + const expectedSorted = [{ k: 3 }, { k: 2 }, { k: 1 }]; + expect(sorted).toEqual(expectedSorted); + }); +}); diff --git a/spec/frontend/usage_quotas/storage/utils_spec.js b/spec/frontend/usage_quotas/storage/utils_spec.js index 2f88017ca50..d392a942e2e 100644 --- a/spec/frontend/usage_quotas/storage/utils_spec.js +++ b/spec/frontend/usage_quotas/storage/utils_spec.js @@ -1,79 +1,5 @@ -import { PROJECT_STORAGE_TYPES } from '~/usage_quotas/storage/constants'; -import { - getStorageTypesFromProjectStatistics, - descendingStorageUsageSort, - parseGetStorageResults, -} from '~/usage_quotas/storage/utils'; -import { - mockGetProjectStorageStatisticsGraphQLResponse, - mockGetNamespaceStorageGraphQLResponse, -} from 'jest/usage_quotas/storage/mock_data'; - -describe('getStorageTypesFromProjectStatistics', () => { - const { - statistics: projectStatistics, - statisticsDetailsPaths, - } = mockGetProjectStorageStatisticsGraphQLResponse.data.project; - - describe('matches project statistics value with matching storage type', () => { - const typesWithStats = getStorageTypesFromProjectStatistics( - PROJECT_STORAGE_TYPES, - projectStatistics, - ); - - it.each(PROJECT_STORAGE_TYPES)('storage type: $id', ({ id }) => { - expect(typesWithStats).toContainEqual( - expect.objectContaining({ - id, - value: projectStatistics[`${id}Size`], - }), - ); - }); - }); - - it('adds helpPath to a relevant type', () => { - const helpLinks = PROJECT_STORAGE_TYPES.reduce((acc, { id }) => { - return { - ...acc, - [id]: `url://${id}`, - }; - }, {}); - - const typesWithStats = getStorageTypesFromProjectStatistics( - PROJECT_STORAGE_TYPES, - projectStatistics, - {}, - helpLinks, - ); - - typesWithStats.forEach((type) => { - expect(type.helpPath).toBe(helpLinks[type.id]); - }); - }); - - it('adds details page path', () => { - const typesWithStats = getStorageTypesFromProjectStatistics( - PROJECT_STORAGE_TYPES, - projectStatistics, - statisticsDetailsPaths, - {}, - ); - typesWithStats.forEach((type) => { - expect(type.detailsPath).toBe(statisticsDetailsPaths[type.id]); - }); - }); -}); - -describe('descendingStorageUsageSort', () => { - it('sorts items by a given key in descending order', () => { - const items = [{ k: 1 }, { k: 3 }, { k: 2 }]; - - const sorted = [...items].sort(descendingStorageUsageSort('k')); - - const expectedSorted = [{ k: 3 }, { k: 2 }, { k: 1 }]; - expect(sorted).toEqual(expectedSorted); - }); -}); +import { parseGetStorageResults } from '~/usage_quotas/storage/utils'; +import { mockGetNamespaceStorageGraphQLResponse } from 'jest/usage_quotas/storage/mock_data'; describe('parseGetStorageResults', () => { it('returns the object keys we use', () => { diff --git a/spec/helpers/groups/settings_helper_spec.rb b/spec/helpers/groups/settings_helper_spec.rb index ed948f5456c..505c6c4815c 100644 --- a/spec/helpers/groups/settings_helper_spec.rb +++ b/spec/helpers/groups/settings_helper_spec.rb @@ -29,7 +29,8 @@ RSpec.describe Groups::SettingsHelper do remove_form_id: form_value_id, phrase: group.full_path, button_testid: "remove-group-button", - disabled: is_button_disabled + disabled: is_button_disabled, + html_confirmation_message: 'true' }) end end diff --git a/spec/lib/vite_gdk_spec.rb b/spec/lib/vite_gdk_spec.rb index 5738337681d..68bbec613ad 100644 --- a/spec/lib/vite_gdk_spec.rb +++ b/spec/lib/vite_gdk_spec.rb @@ -28,6 +28,7 @@ RSpec.describe ViteGdk, feature_category: :tooling do expect(ViteRuby).to receive(:configure).with(host: 'gdk.test', port: 3038) expect(ViteRuby.env).to receive(:[]=).with('VITE_ENABLED', 'true') expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_HOST', 'gdk.test') + expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_HTTP_URL', 'http://gdk.test:3038') expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_WS_URL', 'ws://gdk.test:3038') described_class.load_gdk_vite_config @@ -52,6 +53,7 @@ RSpec.describe ViteGdk, feature_category: :tooling do expect(ViteRuby).to receive(:configure).with(host: 'gdk.test', port: 3038) expect(ViteRuby.env).to receive(:[]=).with('VITE_ENABLED', 'true') expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_HOST', 'hmr.gdk.test') + expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_HTTP_URL', 'https://hmr.gdk.test:9999') expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_WS_URL', 'wss://hmr.gdk.test:9999') described_class.load_gdk_vite_config @@ -76,6 +78,7 @@ RSpec.describe ViteGdk, feature_category: :tooling do expect(ViteRuby).to receive(:configure).with(host: 'gdk.test', port: 3038) expect(ViteRuby.env).to receive(:[]=).with('VITE_ENABLED', 'true') expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_HOST', 'hmr.gdk.test') + expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_HTTP_URL', 'https://hmr.gdk.test:3038') expect(ViteRuby.env).to receive(:[]=).with('VITE_HMR_WS_URL', 'wss://hmr.gdk.test:3038') described_class.load_gdk_vite_config diff --git a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb index 97748fe9e89..48c2490a9bb 100644 --- a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb @@ -47,31 +47,40 @@ RSpec.shared_examples 'Base action controller' do end context 'when configuring vite' do - let(:vite_origin) { "#{ViteRuby.instance.config.host}:#{ViteRuby.instance.config.port}" } + let(:vite_hmr_websocket_url) { "ws://gitlab.example.com:3808" } + let(:vite_hmr_http_url) { "http://gitlab.example.com:3808" } + let(:vite_gitlab_url) { Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/') } context 'when vite enabled during development', skip: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424334' do before do stub_rails_env('development') allow(ViteHelper).to receive(:vite_enabled?).and_return(true) + allow(BaseActionController.helpers).to receive(:vite_enabled?).and_return(true) + allow(BaseActionController.helpers).to receive(:vite_hmr_websocket_url).and_return(vite_hmr_websocket_url) + allow(BaseActionController.helpers).to receive(:vite_hmr_http_url).and_return(vite_hmr_http_url) end it 'adds vite csp' do request - expect(response.headers['Content-Security-Policy']).to include(vite_origin) + expect(response.headers['Content-Security-Policy']).to include("#{vite_hmr_websocket_url}/vite-dev/") + expect(response.headers['Content-Security-Policy']).to include("#{vite_hmr_http_url}/vite-dev/") + expect(response.headers['Content-Security-Policy']).to include(vite_gitlab_url) end end context 'when vite disabled' do before do - allow(ViteHelper).to receive(:vite_enabled?).and_return(false) + allow(BaseActionController.helpers).to receive(:vite_enabled?).and_return(false) end it "doesn't add vite csp" do request - expect(response.headers['Content-Security-Policy']).not_to include(vite_origin) + expect(response.headers['Content-Security-Policy']).not_to include(vite_hmr_websocket_url) + expect(response.headers['Content-Security-Policy']).not_to include(vite_hmr_http_url) + expect(response.headers['Content-Security-Policy']).not_to include(vite_gitlab_url) end end end diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb index abc0ed11a6c..491fc7ea64d 100644 --- a/spec/support/webmock.rb +++ b/spec/support/webmock.rb @@ -26,6 +26,7 @@ def webmock_allowed_hosts if ViteRuby.env['VITE_ENABLED'] == "true" hosts << ViteRuby.instance.config.host + hosts << ViteRuby.env['VITE_HMR_HOST'] end end.compact.uniq end