diff options
Diffstat (limited to 'app/assets/javascripts/projects')
19 files changed, 306 insertions, 496 deletions
diff --git a/app/assets/javascripts/projects/commit/constants.js b/app/assets/javascripts/projects/commit/constants.js index d553bca360e..eb3673461bd 100644 --- a/app/assets/javascripts/projects/commit/constants.js +++ b/app/assets/javascripts/projects/commit/constants.js @@ -11,7 +11,7 @@ export const I18N_MODAL = { 'ChangeTypeAction|Your changes will be committed to %{branchName} because a merge request is open.', ), branchInFork: s__( - 'ChangeTypeAction|A new branch will be created in your fork and a new merge request will be started.', + 'ChangeTypeAction|GitLab will create a branch in your fork and start a merge request.', ), newMergeRequest: __('new merge request'), actionCancelText: __('Cancel'), diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql index ee18c70b6fd..c6a0d48626a 100644 --- a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql @@ -1,15 +1,19 @@ query getLinkedPipelines($fullPath: ID!, $iid: ID!) { project(fullPath: $fullPath) { + id pipeline(iid: $iid) { + id path downstream { nodes { id path project { + id name } detailedStatus { + id group icon label @@ -20,9 +24,11 @@ query getLinkedPipelines($fullPath: ID!, $iid: ID!) { id path project { + id name } detailedStatus { + id group icon label diff --git a/app/assets/javascripts/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/projects/new/components/new_project_url_select.vue index e0ba60074af..f4a21c6057c 100644 --- a/app/assets/javascripts/projects/new/components/new_project_url_select.vue +++ b/app/assets/javascripts/projects/new/components/new_project_url_select.vue @@ -8,7 +8,7 @@ import { GlDropdownSectionHeader, GlSearchBoxByType, } from '@gitlab/ui'; -import { joinPaths } from '~/lib/utils/url_utility'; +import { joinPaths, PATH_SEPARATOR } from '~/lib/utils/url_utility'; import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import Tracking from '~/tracking'; @@ -36,7 +36,9 @@ export default { }; }, skip() { - return this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH; + const hasNotEnoughSearchCharacters = + this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH; + return this.shouldSkipQuery || hasNotEnoughSearchCharacters; }, debounce: DEBOUNCE_DELAY, }, @@ -52,7 +54,7 @@ export default { data() { return { currentUser: {}, - groupToFilterBy: undefined, + groupPathToFilterBy: undefined, search: '', selectedNamespace: this.namespaceId ? { @@ -63,6 +65,7 @@ export default { id: this.userNamespaceId, fullPath: this.userNamespaceFullPath, }, + shouldSkipQuery: true, }; }, computed: { @@ -73,10 +76,8 @@ export default { return this.currentUser.namespace || {}; }, filteredGroups() { - return this.groupToFilterBy - ? this.userGroups.filter((group) => - group.fullPath.startsWith(this.groupToFilterBy.fullPath), - ) + return this.groupPathToFilterBy + ? this.userGroups.filter((group) => group.fullPath.startsWith(this.groupPathToFilterBy)) : this.userGroups; }, hasGroupMatches() { @@ -85,7 +86,7 @@ export default { hasNamespaceMatches() { return ( this.userNamespace.fullPath?.toLowerCase().includes(this.search.toLowerCase()) && - !this.groupToFilterBy + !this.groupPathToFilterBy ); }, hasNoMatches() { @@ -99,7 +100,10 @@ export default { eventHub.$off('select-template', this.handleSelectTemplate); }, methods: { - focusInput() { + handleDropdownShown() { + if (this.shouldSkipQuery) { + this.shouldSkipQuery = false; + } this.$refs.search.focusInput(); }, handleDropdownItemClick(namespace) { @@ -111,13 +115,9 @@ export default { }); this.setNamespace(namespace); }, - handleSelectTemplate(groupId) { - this.groupToFilterBy = this.userGroups.find( - (group) => getIdFromGraphQLId(group.id) === groupId, - ); - if (this.groupToFilterBy) { - this.setNamespace(this.groupToFilterBy); - } + handleSelectTemplate(id, fullPath) { + this.groupPathToFilterBy = fullPath.split(PATH_SEPARATOR).shift(); + this.setNamespace({ id, fullPath }); }, setNamespace({ id, fullPath }) { this.selectedNamespace = { @@ -137,7 +137,7 @@ export default { toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20" data-qa-selector="select_namespace_dropdown" @show="track('activate_form_input', { label: trackLabel, property: 'project_path' })" - @shown="focusInput" + @shown="handleDropdownShown" > <gl-search-box-by-type ref="search" diff --git a/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql b/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql index 74febec5a51..568e05d1966 100644 --- a/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql +++ b/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql @@ -1,5 +1,6 @@ query searchNamespacesWhereUserCanCreateProjects($search: String) { currentUser { + id groups(permissionScope: CREATE_PROJECTS, search: $search) { nodes { id diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue index 7379d5caed7..d4b1f7e57d8 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue @@ -1,5 +1,6 @@ <script> import { GlTabs, GlTab } from '@gitlab/ui'; +import API from '~/api'; import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility'; import PipelineCharts from './pipeline_charts.vue'; @@ -13,6 +14,9 @@ export default { LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'), ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'), }, + piplelinesTabEvent: 'p_analytics_ci_cd_pipelines', + deploymentFrequencyTabEvent: 'p_analytics_ci_cd_deployment_frequency', + leadTimeTabEvent: 'p_analytics_ci_cd_lead_time', inject: { shouldRenderDoraCharts: { type: Boolean, @@ -60,20 +64,35 @@ export default { updateHistory({ url: path, title: window.title }); } }, + trackTabClick(tab) { + API.trackRedisHllUserEvent(tab); + }, }, }; </script> <template> <div> <gl-tabs v-if="charts.length > 1" :value="selectedTab" @input="onTabChange"> - <gl-tab :title="__('Pipelines')"> + <gl-tab + :title="__('Pipelines')" + data-testid="pipelines-tab" + @click="trackTabClick($options.piplelinesTabEvent)" + > <pipeline-charts /> </gl-tab> <template v-if="shouldRenderDoraCharts"> - <gl-tab :title="__('Deployment frequency')"> + <gl-tab + :title="__('Deployment frequency')" + data-testid="deployment-frequency-tab" + @click="trackTabClick($options.deploymentFrequencyTabEvent)" + > <deployment-frequency-charts /> </gl-tab> - <gl-tab :title="__('Lead time')"> + <gl-tab + :title="__('Lead time')" + data-testid="lead-time-tab" + @click="trackTabClick($options.leadTimeTabEvent)" + > <lead-time-charts /> </gl-tab> </template> diff --git a/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue b/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue index 7bc3b787f75..5383a6cdddf 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue @@ -1,10 +1,19 @@ <script> +import { GlLink } from '@gitlab/ui'; import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; import { s__, n__ } from '~/locale'; const defaultPrecision = 2; export default { + components: { + GlLink, + }, + inject: { + failedPipelinesLink: { + default: '', + }, + }, props: { counts: { type: Object, @@ -27,6 +36,7 @@ export default { { title: s__('PipelineCharts|Failed:'), value: n__('1 pipeline', '%d pipelines', this.counts.failed), + link: this.failedPipelinesLink, }, { title: s__('PipelineCharts|Success ratio:'), @@ -39,10 +49,13 @@ export default { </script> <template> <ul> - <template v-for="({ title, value }, index) in statistics"> + <template v-for="({ title, value, link }, index) in statistics"> <li :key="index"> <span>{{ title }}</span> - <strong>{{ value }}</strong> + <gl-link v-if="link" :href="link"> + {{ value }} + </gl-link> + <strong v-else>{{ value }}</strong> </li> </template> </ul> diff --git a/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql index d68df689f5f..ac7fe51384c 100644 --- a/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql +++ b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql @@ -1,5 +1,6 @@ query getPipelineCountByStatus($projectPath: ID!) { project(fullPath: $projectPath) { + id totalPipelines: pipelines { count } diff --git a/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql index 18b645f8831..46e8a6dc87d 100644 --- a/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql +++ b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql @@ -1,5 +1,6 @@ query getProjectPipelineStatistics($projectPath: ID!) { project(fullPath: $projectPath) { + id pipelineAnalytics { weekPipelinesTotals weekPipelinesLabels diff --git a/app/assets/javascripts/projects/pipelines/charts/index.js b/app/assets/javascripts/projects/pipelines/charts/index.js index 003b61d94b1..94d32609e5d 100644 --- a/app/assets/javascripts/projects/pipelines/charts/index.js +++ b/app/assets/javascripts/projects/pipelines/charts/index.js @@ -11,7 +11,7 @@ const apolloProvider = new VueApollo({ }); const mountPipelineChartsApp = (el) => { - const { projectPath } = el.dataset; + const { projectPath, failedPipelinesLink, coverageChartPath, defaultBranch } = el.dataset; const shouldRenderDoraCharts = parseBoolean(el.dataset.shouldRenderDoraCharts); const shouldRenderQualitySummary = parseBoolean(el.dataset.shouldRenderQualitySummary); @@ -25,8 +25,11 @@ const mountPipelineChartsApp = (el) => { apolloProvider, provide: { projectPath, + failedPipelinesLink, shouldRenderDoraCharts, shouldRenderQualitySummary, + coverageChartPath, + defaultBranch, }, render: (createElement) => createElement(ProjectPipelinesCharts, {}), }); diff --git a/app/assets/javascripts/projects/settings/components/transfer_project_form.vue b/app/assets/javascripts/projects/settings/components/transfer_project_form.vue new file mode 100644 index 00000000000..b98e1101884 --- /dev/null +++ b/app/assets/javascripts/projects/settings/components/transfer_project_form.vue @@ -0,0 +1,63 @@ +<script> +import { GlFormGroup } from '@gitlab/ui'; +import NamespaceSelect from '~/vue_shared/components/namespace_select/namespace_select.vue'; +import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue'; + +export default { + name: 'TransferProjectForm', + components: { + GlFormGroup, + NamespaceSelect, + ConfirmDanger, + }, + props: { + namespaces: { + type: Object, + required: true, + }, + confirmationPhrase: { + type: String, + required: true, + }, + confirmButtonText: { + type: String, + required: true, + }, + }, + data() { + return { selectedNamespace: null }; + }, + computed: { + hasSelectedNamespace() { + return Boolean(this.selectedNamespace?.id); + }, + }, + methods: { + handleSelect(selectedNamespace) { + this.selectedNamespace = selectedNamespace; + this.$emit('selectNamespace', selectedNamespace.id); + }, + }, +}; +</script> +<template> + <div> + <gl-form-group> + <namespace-select + class="qa-namespaces-list" + data-testid="transfer-project-namespace" + :full-width="true" + :data="namespaces" + :selected-namespace="selectedNamespace" + @select="handleSelect" + /> + </gl-form-group> + <confirm-danger + button-class="qa-transfer-button" + :disabled="!hasSelectedNamespace" + :phrase="confirmationPhrase" + :button-text="confirmButtonText" + @confirm="$emit('confirm')" + /> + </div> +</template> diff --git a/app/assets/javascripts/projects/settings/init_transfer_project_form.js b/app/assets/javascripts/projects/settings/init_transfer_project_form.js new file mode 100644 index 00000000000..47b49031dc9 --- /dev/null +++ b/app/assets/javascripts/projects/settings/init_transfer_project_form.js @@ -0,0 +1,53 @@ +import Vue from 'vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import TransferProjectForm from './components/transfer_project_form.vue'; + +const prepareNamespaces = (rawNamespaces = '') => { + const data = JSON.parse(rawNamespaces); + return { + group: data?.group.map(convertObjectPropsToCamelCase), + user: data?.user.map(convertObjectPropsToCamelCase), + }; +}; + +export default () => { + const el = document.querySelector('.js-transfer-project-form'); + if (!el) { + return false; + } + + const { + targetFormId = null, + targetHiddenInputId = null, + buttonText: confirmButtonText = '', + phrase: confirmationPhrase = '', + confirmDangerMessage = '', + namespaces = '', + } = el.dataset; + + return new Vue({ + el, + provide: { + confirmDangerMessage, + }, + render(createElement) { + return createElement(TransferProjectForm, { + props: { + confirmButtonText, + confirmationPhrase, + namespaces: prepareNamespaces(namespaces), + }, + on: { + selectNamespace: (id) => { + if (targetHiddenInputId && document.getElementById(targetHiddenInputId)) { + document.getElementById(targetHiddenInputId).value = id; + } + }, + confirm: () => { + if (targetFormId) document.getElementById(targetFormId)?.submit(); + }, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue index b8053bf9ab5..e5ddfe82e3b 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -1,5 +1,15 @@ <script> -import { GlButton, GlToggle, GlLoadingIcon, GlSprintf, GlFormInput, GlLink } from '@gitlab/ui'; +import { + GlButton, + GlToggle, + GlLoadingIcon, + GlSprintf, + GlFormInputGroup, + GlFormGroup, + GlFormInput, + GlLink, +} from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { __ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ServiceDeskTemplateDropdown from './service_desk_template_dropdown.vue'; @@ -15,6 +25,8 @@ export default { GlLoadingIcon, GlSprintf, GlFormInput, + GlFormGroup, + GlFormInputGroup, GlLink, ServiceDeskTemplateDropdown, }, @@ -88,6 +100,16 @@ export default { hasCustomEmail() { return this.customEmail && this.customEmail !== this.incomingEmail; }, + emailSuffixHelpUrl() { + return helpPagePath('user/project/service_desk.html', { + anchor: 'configuring-a-custom-email-address-suffix', + }); + }, + customEmailAddressHelpUrl() { + return helpPagePath('user/project/service_desk.html', { + anchor: 'using-a-custom-email-address', + }); + }, }, methods: { onCheckboxToggle(isChecked) { @@ -132,101 +154,122 @@ export default { </label> <div v-if="isEnabled" class="row mt-3"> <div class="col-md-9 mb-0"> - <strong - id="incoming-email-describer" - class="gl-display-block gl-mb-1" - data-testid="incoming-email-describer" + <gl-form-group + :label="__('Email address to use for Support Desk')" + label-for="incoming-email" + data-testid="incoming-email-label" > - {{ __('Email address to use for Support Desk') }} - </strong> - <template v-if="email"> - <div class="input-group"> - <input + <gl-form-input-group v-if="email"> + <gl-form-input + id="incoming-email" ref="service-desk-incoming-email" type="text" - class="form-control" data-testid="incoming-email" :placeholder="__('Incoming email')" :aria-label="__('Incoming email')" aria-describedby="incoming-email-describer" :value="email" - disabled="true" + :disabled="true" /> - <div class="input-group-append"> + <template #append> <clipboard-button :title="__('Copy')" :text="email" css-class="input-group-text" /> - </div> - </div> - <span v-if="hasCustomEmail" class="form-text text-muted"> - <gl-sprintf :message="__('Emails sent to %{email} are also supported.')"> - <template #email> - <code>{{ incomingEmail }}</code> + </template> + </gl-form-input-group> + <template v-if="email && hasCustomEmail" #description> + <span class="gl-mt-2 d-inline-block"> + <gl-sprintf :message="__('Emails sent to %{email} are also supported.')"> + <template #email> + <code>{{ incomingEmail }}</code> + </template> + </gl-sprintf> + </span> + </template> + <template v-if="!email"> + <gl-loading-icon size="sm" :inline="true" /> + <span class="sr-only">{{ __('Fetching incoming email') }}</span> + </template> + </gl-form-group> + + <gl-form-group :label="__('Email address suffix')" :state="!projectKeyError"> + <gl-form-input + v-if="hasProjectKeySupport" + id="service-desk-project-suffix" + v-model.trim="projectKey" + data-testid="project-suffix" + @blur="validateProjectKey" + /> + + <template v-if="hasProjectKeySupport" #description> + <gl-sprintf + :message=" + __('Add a suffix to Service Desk email address. %{linkStart}Learn more.%{linkEnd}') + " + > + <template #link="{ content }"> + <gl-link + :href="emailSuffixHelpUrl" + target="_blank" + class="gl-text-blue-600 font-size-inherit" + >{{ content }} + </gl-link> </template> </gl-sprintf> - </span> - </template> - <template v-else> - <gl-loading-icon size="sm" :inline="true" /> - <span class="sr-only">{{ __('Fetching incoming email') }}</span> - </template> + </template> + <template v-else #description> + <gl-sprintf + :message=" + __( + 'To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}', + ) + " + > + <template #link="{ content }"> + <gl-link + :href="customEmailAddressHelpUrl" + target="_blank" + class="gl-text-blue-600 font-size-inherit" + >{{ content }} + </gl-link> + </template> + </gl-sprintf> + </template> + + <template v-if="hasProjectKeySupport && projectKeyError" #invalid-feedback> + {{ projectKeyError }} + </template> + </gl-form-group> - <label for="service-desk-project-suffix" class="mt-3"> - {{ __('Project name suffix') }} - </label> - <gl-form-input - v-if="hasProjectKeySupport" - id="service-desk-project-suffix" - v-model.trim="projectKey" - data-testid="project-suffix" - class="form-control" + <gl-form-group + :label="__('Template to append to all Service Desk issues')" :state="!projectKeyError" - @blur="validateProjectKey" - /> - <span v-if="hasProjectKeySupport && projectKeyError" class="form-text text-danger"> - {{ projectKeyError }} - </span> - <span - v-if="hasProjectKeySupport" - class="form-text text-muted" - :class="{ 'gl-mt-2!': hasProjectKeySupport && projectKeyError }" + class="mt-3" > - {{ __('A string appended to the project path to form the Service Desk email address.') }} - </span> - <span v-else class="form-text text-muted"> - <gl-sprintf - :message=" - __( - 'To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}', - ) - " - > - <template #link="{ content }"> - <gl-link - href="https://docs.gitlab.com/ee/user/project/service_desk.html#using-a-custom-email-address" - target="_blank" - class="gl-text-blue-600 font-size-inherit" - >{{ content }} - </gl-link> - </template> - </gl-sprintf> - </span> + <service-desk-template-dropdown + :selected-template="selectedTemplate" + :selected-file-template-project-id="selectedFileTemplateProjectId" + :templates="templates" + @change="templateChange" + /> + </gl-form-group> + + <gl-form-group + :label="__('Email display name')" + label-for="service-desk-email-from-name" + :state="!projectKeyError" + class="mt-3" + > + <gl-form-input + v-if="hasProjectKeySupport" + id="service-desk-email-from-name" + v-model.trim="outgoingName" + data-testid="email-from-name" + /> - <label for="service-desk-template-select" class="mt-3"> - {{ __('Template to append to all Service Desk issues') }} - </label> - <service-desk-template-dropdown - :selected-template="selectedTemplate" - :selected-file-template-project-id="selectedFileTemplateProjectId" - :templates="templates" - @change="templateChange" - /> + <template v-if="hasProjectKeySupport" #description> + {{ __('Emails sent from Service Desk have this name.') }} + </template> + </gl-form-group> - <label for="service-desk-email-from-name" class="mt-3"> - {{ __('Email display name') }} - </label> - <input id="service-desk-email-from-name" v-model.trim="outgoingName" class="form-control" /> - <span class="form-text text-muted"> - {{ __('Emails sent from Service Desk have this name.') }} - </span> <div class="gl-display-flex gl-justify-content-end"> <gl-button variant="success" diff --git a/app/assets/javascripts/projects/storage_counter/components/app.vue b/app/assets/javascripts/projects/storage_counter/components/app.vue deleted file mode 100644 index 1a911ea3d9b..00000000000 --- a/app/assets/javascripts/projects/storage_counter/components/app.vue +++ /dev/null @@ -1,106 +0,0 @@ -<script> -import { GlAlert, GlLink, GlLoadingIcon } from '@gitlab/ui'; -import { sprintf } from '~/locale'; -import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue'; -import { - ERROR_MESSAGE, - LEARN_MORE_LABEL, - USAGE_QUOTAS_LABEL, - TOTAL_USAGE_TITLE, - TOTAL_USAGE_SUBTITLE, - TOTAL_USAGE_DEFAULT_TEXT, - HELP_LINK_ARIA_LABEL, -} from '../constants'; -import getProjectStorageCount from '../queries/project_storage.query.graphql'; -import { parseGetProjectStorageResults } from '../utils'; -import StorageTable from './storage_table.vue'; - -export default { - name: 'StorageCounterApp', - components: { - GlAlert, - GlLink, - GlLoadingIcon, - StorageTable, - UsageGraph, - }, - inject: ['projectPath', 'helpLinks'], - apollo: { - project: { - query: getProjectStorageCount, - variables() { - return { - fullPath: this.projectPath, - }; - }, - update(data) { - return parseGetProjectStorageResults(data, this.helpLinks); - }, - error() { - this.error = ERROR_MESSAGE; - }, - }, - }, - data() { - return { - project: {}, - error: '', - }; - }, - computed: { - totalUsage() { - return this.project?.storage?.totalUsage || TOTAL_USAGE_DEFAULT_TEXT; - }, - storageTypes() { - return this.project?.storage?.storageTypes || []; - }, - }, - methods: { - clearError() { - this.error = ''; - }, - helpLinkAriaLabel(linkTitle) { - return sprintf(HELP_LINK_ARIA_LABEL, { - linkTitle, - }); - }, - }, - LEARN_MORE_LABEL, - USAGE_QUOTAS_LABEL, - TOTAL_USAGE_TITLE, - TOTAL_USAGE_SUBTITLE, -}; -</script> -<template> - <gl-loading-icon v-if="$apollo.queries.project.loading" class="gl-mt-5" size="md" /> - <gl-alert v-else-if="error" variant="danger" @dismiss="clearError"> - {{ error }} - </gl-alert> - <div v-else> - <div class="gl-pt-5 gl-px-3"> - <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center"> - <div> - <p class="gl-m-0 gl-font-lg gl-font-weight-bold">{{ $options.TOTAL_USAGE_TITLE }}</p> - <p class="gl-m-0 gl-text-gray-400"> - {{ $options.TOTAL_USAGE_SUBTITLE }} - <gl-link - :href="helpLinks.usageQuotasHelpPagePath" - target="_blank" - :aria-label="helpLinkAriaLabel($options.USAGE_QUOTAS_LABEL)" - data-testid="usage-quotas-help-link" - > - {{ $options.LEARN_MORE_LABEL }} - </gl-link> - </p> - </div> - <p class="gl-m-0 gl-font-size-h-display gl-font-weight-bold" data-testid="total-usage"> - {{ totalUsage }} - </p> - </div> - </div> - <div v-if="project.statistics" class="gl-w-full"> - <usage-graph :root-storage-statistics="project.statistics" :limit="0" /> - </div> - <storage-table :storage-types="storageTypes" /> - </div> -</template> diff --git a/app/assets/javascripts/projects/storage_counter/components/storage_table.vue b/app/assets/javascripts/projects/storage_counter/components/storage_table.vue deleted file mode 100644 index a42a9711572..00000000000 --- a/app/assets/javascripts/projects/storage_counter/components/storage_table.vue +++ /dev/null @@ -1,88 +0,0 @@ -<script> -import { GlLink, GlIcon, GlTableLite as GlTable, GlSprintf } from '@gitlab/ui'; -import { numberToHumanSize } from '~/lib/utils/number_utils'; -import { thWidthClass } from '~/lib/utils/table_utility'; -import { sprintf } from '~/locale'; -import { PROJECT_TABLE_LABELS, HELP_LINK_ARIA_LABEL } from '../constants'; -import StorageTypeIcon from './storage_type_icon.vue'; - -export default { - name: 'StorageTable', - components: { - GlLink, - GlIcon, - GlTable, - GlSprintf, - StorageTypeIcon, - }, - props: { - storageTypes: { - type: Array, - required: true, - }, - }, - methods: { - helpLinkAriaLabel(linkTitle) { - return sprintf(HELP_LINK_ARIA_LABEL, { - linkTitle, - }); - }, - }, - projectTableFields: [ - { - key: 'storageType', - label: PROJECT_TABLE_LABELS.STORAGE_TYPE, - thClass: thWidthClass(90), - sortable: true, - }, - { - key: 'value', - label: PROJECT_TABLE_LABELS.VALUE, - thClass: thWidthClass(10), - sortable: true, - formatter: (value) => { - return numberToHumanSize(value, 1); - }, - }, - ], -}; -</script> -<template> - <gl-table :items="storageTypes" :fields="$options.projectTableFields"> - <template #cell(storageType)="{ item }"> - <div class="gl-display-flex gl-flex-direction-row"> - <storage-type-icon - :name="item.storageType.id" - :data-testid="`${item.storageType.id}-icon`" - /> - <div> - <p class="gl-font-weight-bold gl-mb-0" :data-testid="`${item.storageType.id}-name`"> - {{ item.storageType.name }} - <gl-link - v-if="item.storageType.helpPath" - :href="item.storageType.helpPath" - target="_blank" - :aria-label="helpLinkAriaLabel(item.storageType.name)" - :data-testid="`${item.storageType.id}-help-link`" - > - <gl-icon name="question" :size="12" /> - </gl-link> - </p> - <p class="gl-mb-0" :data-testid="`${item.storageType.id}-description`"> - {{ item.storageType.description }} - </p> - <p v-if="item.storageType.warningMessage" class="gl-mb-0 gl-font-sm"> - <gl-icon name="warning" :size="12" /> - <gl-sprintf :message="item.storageType.warningMessage"> - <template #warningLink="{ content }"> - <gl-link :href="item.storageType.warningLink" target="_blank" class="gl-font-sm">{{ - content - }}</gl-link> - </template> - </gl-sprintf> - </p> - </div> - </div> - </template> - </gl-table> -</template> diff --git a/app/assets/javascripts/projects/storage_counter/components/storage_type_icon.vue b/app/assets/javascripts/projects/storage_counter/components/storage_type_icon.vue deleted file mode 100644 index bc7cd42df1e..00000000000 --- a/app/assets/javascripts/projects/storage_counter/components/storage_type_icon.vue +++ /dev/null @@ -1,35 +0,0 @@ -<script> -import { GlIcon } from '@gitlab/ui'; - -export default { - components: { GlIcon }, - props: { - name: { - type: String, - required: false, - default: '', - }, - }, - methods: { - iconName(storageTypeName) { - const defaultStorageTypeIcon = 'disk'; - const storageTypeIconMap = { - lfsObjectsSize: 'doc-image', - snippetsSize: 'snippet', - uploadsSize: 'upload', - repositorySize: 'infrastructure-registry', - packagesSize: 'package', - }; - - return storageTypeIconMap[`${storageTypeName}`] ?? defaultStorageTypeIcon; - }, - }, -}; -</script> -<template> - <span - class="gl-display-inline-flex gl-align-items-flex-start gl-justify-content-center gl-min-w-8 gl-pr-2 gl-pt-1" - > - <gl-icon :name="iconName(name)" :size="16" class="gl-mt-1" /> - </span> -</template> diff --git a/app/assets/javascripts/projects/storage_counter/constants.js b/app/assets/javascripts/projects/storage_counter/constants.js deleted file mode 100644 index df4b1800dff..00000000000 --- a/app/assets/javascripts/projects/storage_counter/constants.js +++ /dev/null @@ -1,61 +0,0 @@ -import { s__, __ } from '~/locale'; - -export const PROJECT_STORAGE_TYPES = [ - { - id: 'buildArtifactsSize', - name: s__('UsageQuota|Artifacts'), - description: s__('UsageQuota|Pipeline artifacts and job artifacts, created with CI/CD.'), - warningMessage: s__( - 'UsageQuota|Because of a known issue, the artifact total for some projects may be incorrect. For more details, read %{warningLinkStart}the epic%{warningLinkEnd}.', - ), - warningLink: 'https://gitlab.com/groups/gitlab-org/-/epics/5380', - }, - { - id: 'lfsObjectsSize', - name: s__('UsageQuota|LFS storage'), - description: s__('UsageQuota|Audio samples, videos, datasets, and graphics.'), - }, - { - id: 'packagesSize', - name: s__('UsageQuota|Packages'), - description: s__('UsageQuota|Code packages and container images.'), - }, - { - id: 'repositorySize', - name: s__('UsageQuota|Repository'), - description: s__('UsageQuota|Git repository.'), - }, - { - id: 'snippetsSize', - name: s__('UsageQuota|Snippets'), - description: s__('UsageQuota|Shared bits of code and text.'), - }, - { - id: 'uploadsSize', - name: s__('UsageQuota|Uploads'), - description: s__('UsageQuota|File attachments and smaller design graphics.'), - }, - { - id: 'wikiSize', - name: s__('UsageQuota|Wiki'), - description: s__('UsageQuota|Wiki content.'), - }, -]; - -export const PROJECT_TABLE_LABELS = { - STORAGE_TYPE: s__('UsageQuota|Storage type'), - VALUE: s__('UsageQuota|Usage'), -}; - -export const ERROR_MESSAGE = s__( - 'UsageQuota|Something went wrong while fetching project storage statistics', -); - -export const LEARN_MORE_LABEL = __('Learn more.'); -export const USAGE_QUOTAS_LABEL = s__('UsageQuota|Usage Quotas'); -export const HELP_LINK_ARIA_LABEL = s__('UsageQuota|%{linkTitle} help link'); -export const TOTAL_USAGE_DEFAULT_TEXT = __('N/A'); -export const TOTAL_USAGE_TITLE = s__('UsageQuota|Usage breakdown'); -export const TOTAL_USAGE_SUBTITLE = s__( - 'UsageQuota|Includes artifacts, repositories, wiki, uploads, and other items.', -); diff --git a/app/assets/javascripts/projects/storage_counter/index.js b/app/assets/javascripts/projects/storage_counter/index.js deleted file mode 100644 index 15796bc1870..00000000000 --- a/app/assets/javascripts/projects/storage_counter/index.js +++ /dev/null @@ -1,51 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; -import StorageCounterApp from './components/app.vue'; - -Vue.use(VueApollo); - -export default (containerId = 'js-project-storage-count-app') => { - const el = document.getElementById(containerId); - - if (!el) { - return false; - } - - const { - projectPath, - usageQuotasHelpPagePath, - buildArtifactsHelpPagePath, - lfsObjectsHelpPagePath, - packagesHelpPagePath, - repositoryHelpPagePath, - snippetsHelpPagePath, - uploadsHelpPagePath, - wikiHelpPagePath, - } = el.dataset; - - const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), - }); - - return new Vue({ - el, - apolloProvider, - provide: { - projectPath, - helpLinks: { - usageQuotasHelpPagePath, - buildArtifactsHelpPagePath, - lfsObjectsHelpPagePath, - packagesHelpPagePath, - repositoryHelpPagePath, - snippetsHelpPagePath, - uploadsHelpPagePath, - wikiHelpPagePath, - }, - }, - render(createElement) { - return createElement(StorageCounterApp); - }, - }); -}; diff --git a/app/assets/javascripts/projects/storage_counter/queries/project_storage.query.graphql b/app/assets/javascripts/projects/storage_counter/queries/project_storage.query.graphql deleted file mode 100644 index a4f2c529522..00000000000 --- a/app/assets/javascripts/projects/storage_counter/queries/project_storage.query.graphql +++ /dev/null @@ -1,16 +0,0 @@ -query getProjectStorageCount($fullPath: ID!) { - project(fullPath: $fullPath) { - id - statistics { - buildArtifactsSize - pipelineArtifactsSize - lfsObjectsSize - packagesSize - repositorySize - snippetsSize - storageSize - uploadsSize - wikiSize - } - } -} diff --git a/app/assets/javascripts/projects/storage_counter/utils.js b/app/assets/javascripts/projects/storage_counter/utils.js deleted file mode 100644 index 9fca9d88f46..00000000000 --- a/app/assets/javascripts/projects/storage_counter/utils.js +++ /dev/null @@ -1,36 +0,0 @@ -import { numberToHumanSize } from '~/lib/utils/number_utils'; -import { PROJECT_STORAGE_TYPES } from './constants'; - -/** - * This method parses the results from `getProjectStorageCount` call. - * - * @param {Object} data graphql result - * @returns {Object} - */ -export const parseGetProjectStorageResults = (data, helpLinks) => { - const projectStatistics = data?.project?.statistics; - if (!projectStatistics) { - return {}; - } - const { storageSize, ...storageStatistics } = projectStatistics; - const storageTypes = PROJECT_STORAGE_TYPES.reduce((types, currentType) => { - const helpPathKey = currentType.id.replace(`Size`, `HelpPagePath`); - const helpPath = helpLinks[helpPathKey]; - - return types.concat({ - storageType: { - ...currentType, - helpPath, - }, - value: storageStatistics[currentType.id], - }); - }, []); - - return { - storage: { - totalUsage: numberToHumanSize(storageSize, 1), - storageTypes, - }, - statistics: projectStatistics, - }; -}; |