Commit 6d27b228 authored by Sheldon Led's avatar Sheldon Led Committed by Brandon Labuschagne

Move project related usage quotas code to a shared folder

parent fb66f975
import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
import storageCounter from '~/projects/storage_counter'; import initProjectStorage from 'ee/usage_quotas/storage/init_project_storage';
import initSearchSettings from '~/search_settings'; import initSearchSettings from '~/search_settings';
const initLinkedTabs = () => { const initLinkedTabs = () => {
...@@ -15,7 +15,7 @@ const initLinkedTabs = () => { ...@@ -15,7 +15,7 @@ const initLinkedTabs = () => {
}; };
const initVueApp = () => { const initVueApp = () => {
storageCounter('js-project-storage-count-app'); initProjectStorage('js-project-storage-count-app');
}; };
initVueApp(); initVueApp();
......
...@@ -10,10 +10,10 @@ import { ...@@ -10,10 +10,10 @@ import {
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue'; import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProjectList from 'ee/usage_quotas/storage/components/project_list.vue';
import { PROJECTS_PER_PAGE } from '../constants'; import { PROJECTS_PER_PAGE } from '../constants';
import query from '../queries/storage.query.graphql'; import query from '../queries/storage.query.graphql';
import { formatUsageSize, parseGetStorageResults } from '../utils'; import { formatUsageSize, parseGetStorageResults } from '../utils';
import ProjectsTable from './projects_table.vue';
import StorageInlineAlert from './storage_inline_alert.vue'; import StorageInlineAlert from './storage_inline_alert.vue';
import TemporaryStorageIncreaseModal from './temporary_storage_increase_modal.vue'; import TemporaryStorageIncreaseModal from './temporary_storage_increase_modal.vue';
import UsageStatistics from './usage_statistics.vue'; import UsageStatistics from './usage_statistics.vue';
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
GlButton, GlButton,
GlSprintf, GlSprintf,
UsageGraph, UsageGraph,
ProjectsTable, ProjectList,
UsageStatistics, UsageStatistics,
StorageInlineAlert, StorageInlineAlert,
GlKeysetPagination, GlKeysetPagination,
...@@ -230,7 +230,7 @@ export default { ...@@ -230,7 +230,7 @@ export default {
/> />
</div> </div>
</div> </div>
<projects-table <project-list
:projects="namespaceProjects" :projects="namespaceProjects"
:is-loading="isQueryLoading" :is-loading="isQueryLoading"
:additional-purchased-storage-size="namespace.additionalPurchasedStorageSize || 0" :additional-purchased-storage-size="namespace.additionalPurchasedStorageSize || 0"
......
<script>
import { numberToHumanSize } from '~/lib/utils/number_utils';
export default {
props: {
name: {
type: String,
required: true,
},
value: {
type: Number,
required: true,
},
},
computed: {
formattedValue() {
return numberToHumanSize(this.value);
},
},
};
</script>
<template>
<div class="gl-responsive-table-row gl-line-height-normal" role="row">
<div class="table-section section-wrap section-70 text-truncate pl-2 ml-3" role="gridcell">
<div class="table-mobile-header" role="rowheader"></div>
<div class="table-mobile-content ml-1">{{ name }}</div>
</div>
<div class="table-section section-wrap section-30 text-truncate" role="gridcell">
<div class="table-mobile-header" role="rowheader"></div>
<div class="table-mobile-content">{{ formattedValue }}</div>
</div>
</div>
</template>
...@@ -13,5 +13,3 @@ export const STORAGE_USAGE_THRESHOLDS = { ...@@ -13,5 +13,3 @@ export const STORAGE_USAGE_THRESHOLDS = {
}; };
export const PROJECTS_PER_PAGE = 20; export const PROJECTS_PER_PAGE = 20;
export const SKELETON_LOADER_ROWS = 5;
<script> <script>
import { GlLink, GlIcon } from '@gitlab/ui'; import { GlLink, GlIcon } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import { numberToHumanSize, isOdd } from '~/lib/utils/number_utils'; import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { s__ } from '~/locale'; import { getStorageTypesFromProjectStatistics } from '../utils';
import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue'; import { PROJECT_TABLE_LABEL_PROJECT, PROJECT_TABLE_LABEL_USAGE } from '../constants';
import StorageRow from './storage_row.vue'; import ProjectStorageDetail from './project_storage_detail.vue';
export default { export default {
name: 'CollapsibleProjectStorageDetail',
components: { components: {
GlIcon, GlIcon,
GlLink, GlLink,
ProjectAvatar, ProjectAvatar,
StorageRow, ProjectStorageDetail,
}, },
props: { props: {
project: { project: {
...@@ -25,15 +26,6 @@ export default { ...@@ -25,15 +26,6 @@ export default {
}; };
}, },
computed: { computed: {
projectAvatar() {
const { name, id, avatarUrl, webUrl } = this.project;
return {
name,
id: Number(getIdFromGraphQLId(id)),
avatar_url: avatarUrl,
path: webUrl,
};
},
name() { name() {
return this.project.nameWithNamespace; return this.project.nameWithNamespace;
}, },
...@@ -43,14 +35,8 @@ export default { ...@@ -43,14 +35,8 @@ export default {
iconName() { iconName() {
return this.isOpen ? 'angle-down' : 'angle-right'; return this.isOpen ? 'angle-down' : 'angle-right';
}, },
statistics() { projectStorageTypes() {
const statisticsCopy = { ...this.project.statistics }; return getStorageTypesFromProjectStatistics(this.project.statistics);
delete statisticsCopy.storageSize;
// eslint-disable-next-line no-underscore-dangle
delete statisticsCopy.__typename;
delete statisticsCopy.commitCount;
return statisticsCopy;
}, },
}, },
methods: { methods: {
...@@ -63,28 +49,10 @@ export default { ...@@ -63,28 +49,10 @@ export default {
} }
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
}, },
getFormattedName(name) {
return this.$options.i18nStatisticsMap[name];
},
isOdd(num) {
return isOdd(num);
},
/**
* Some values can be `nil`
* for those, we send 0 instead
*/
getValue(val) {
return val || 0;
}, },
}, i18n: {
i18nStatisticsMap: { PROJECT_TABLE_LABEL_PROJECT,
repositorySize: s__('UsageQuota|Repository'), PROJECT_TABLE_LABEL_USAGE,
lfsObjectsSize: s__('UsageQuota|LFS Storage'),
buildArtifactsSize: s__('UsageQuota|Artifacts'),
packagesSize: s__('UsageQuota|Packages'),
wikiSize: s__('UsageQuota|Wiki'),
snippetsSize: s__('UsageQuota|Snippets'),
uploadsSize: s__('UsageQuota|Uploads'),
}, },
}; };
</script> </script>
...@@ -101,16 +69,20 @@ export default { ...@@ -101,16 +69,20 @@ export default {
role="gridcell" role="gridcell"
> >
<div class="table-mobile-header gl-font-weight-bold" role="rowheader"> <div class="table-mobile-header gl-font-weight-bold" role="rowheader">
{{ __('Project') }} {{ $options.i18n.PROJECT_TABLE_LABEL_PROJECT }}
</div> </div>
<div class="table-mobile-content gl-display-flex gl-align-items-center"> <div class="table-mobile-content gl-display-flex gl-align-items-center">
<div class="gl-display-flex gl-mr-3 gl-align-items-center"> <div class="gl-display-flex gl-mr-3 gl-align-items-center">
<gl-icon :size="12" :name="iconName" class="gl-mr-2" /> <gl-icon :size="12" :name="iconName" />
<gl-icon name="bookmark" /> <gl-icon name="bookmark" />
</div> </div>
<div> <project-avatar
<project-avatar :project="projectAvatar" :size="32" /> :project-name="project.name"
</div> :project-avatar-url="project.avatarUrl"
:size="32"
:alt="project.name"
class="gl-mr-3"
/>
<gl-link <gl-link
:href="project.webUrl" :href="project.webUrl"
class="js-project-link gl-font-weight-bold gl-text-gray-900!" class="js-project-link gl-font-weight-bold gl-text-gray-900!"
...@@ -123,20 +95,11 @@ export default { ...@@ -123,20 +95,11 @@ export default {
role="gridcell" role="gridcell"
> >
<div class="table-mobile-header gl-font-weight-bold" role="rowheader"> <div class="table-mobile-header gl-font-weight-bold" role="rowheader">
{{ __('Usage') }} {{ $options.i18n.PROJECT_TABLE_LABEL_USAGE }}
</div> </div>
<div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</div> <div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</div>
</div> </div>
</div> </div>
<project-storage-detail v-if="isOpen" :storage-types="projectStorageTypes" />
<template v-if="isOpen">
<storage-row
v-for="(value, statisticsName, index) in statistics"
:key="index"
:name="getFormattedName(statisticsName)"
:value="getValue(value)"
:class="{ 'gl-bg-gray-10': isOdd(index) }"
/>
</template>
</div> </div>
</template> </template>
<script> <script>
import { SEARCH_DEBOUNCE_MS } from '~/ref/constants'; import { PROJECT_TABLE_LABEL_PROJECT, PROJECT_TABLE_LABEL_USAGE } from '../constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import CollapsibleProjectStorageDetail from './collapsible_project_storage_detail.vue';
import Project from './project.vue';
import ProjectsSkeletonLoader from './projects_skeleton_loader.vue'; import ProjectsSkeletonLoader from './projects_skeleton_loader.vue';
export default { export default {
name: 'ProjectList',
components: { components: {
Project, CollapsibleProjectStorageDetail,
ProjectsSkeletonLoader, ProjectsSkeletonLoader,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
projects: { projects: {
type: Array, type: Array,
...@@ -25,7 +24,10 @@ export default { ...@@ -25,7 +24,10 @@ export default {
default: false, default: false,
}, },
}, },
searchDebounceValue: SEARCH_DEBOUNCE_MS, i18n: {
PROJECT_TABLE_LABEL_PROJECT,
PROJECT_TABLE_LABEL_USAGE,
},
}; };
</script> </script>
...@@ -36,20 +38,19 @@ export default { ...@@ -36,20 +38,19 @@ export default {
role="row" role="row"
> >
<div class="table-section section-70 gl-font-weight-bold" role="columnheader"> <div class="table-section section-70 gl-font-weight-bold" role="columnheader">
{{ __('Project') }} {{ $options.i18n.PROJECT_TABLE_LABEL_PROJECT }}
</div> </div>
<div class="table-section section-30 gl-font-weight-bold" role="columnheader"> <div class="table-section section-30 gl-font-weight-bold" role="columnheader">
{{ __('Usage') }} {{ $options.i18n.PROJECT_TABLE_LABEL_USAGE }}
</div> </div>
</div> </div>
<projects-skeleton-loader v-if="isLoading" /> <projects-skeleton-loader v-if="isLoading" />
<template v-else> <collapsible-project-storage-detail
<project
v-for="project in projects" v-for="project in projects"
v-else
:key="project.id" :key="project.id"
:project="project" :project="project"
:additional-purchased-storage-size="additionalPurchasedStorageSize" :additional-purchased-storage-size="additionalPurchasedStorageSize"
/> />
</template>
</div> </div>
</template> </template>
...@@ -11,23 +11,23 @@ import { ...@@ -11,23 +11,23 @@ import {
TOTAL_USAGE_DEFAULT_TEXT, TOTAL_USAGE_DEFAULT_TEXT,
HELP_LINK_ARIA_LABEL, HELP_LINK_ARIA_LABEL,
} from '../constants'; } from '../constants';
import getProjectStorageCount from '../queries/project_storage.query.graphql'; import getProjectStorageStatistics from '../queries/project_storage.query.graphql';
import { parseGetProjectStorageResults } from '../utils'; import { parseGetProjectStorageResults } from '../utils';
import StorageTable from './storage_table.vue'; import ProjectStorageDetail from './project_storage_detail.vue';
export default { export default {
name: 'StorageCounterApp', name: 'ProjectStorageApp',
components: { components: {
GlAlert, GlAlert,
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
StorageTable,
UsageGraph, UsageGraph,
ProjectStorageDetail,
}, },
inject: ['projectPath', 'helpLinks'], inject: ['projectPath', 'helpLinks'],
apollo: { apollo: {
project: { project: {
query: getProjectStorageCount, query: getProjectStorageStatistics,
variables() { variables() {
return { return {
fullPath: this.projectPath, fullPath: this.projectPath,
...@@ -101,6 +101,6 @@ export default { ...@@ -101,6 +101,6 @@ export default {
<div v-if="project.statistics" class="gl-w-full"> <div v-if="project.statistics" class="gl-w-full">
<usage-graph :root-storage-statistics="project.statistics" :limit="0" /> <usage-graph :root-storage-statistics="project.statistics" :limit="0" />
</div> </div>
<storage-table :storage-types="storageTypes" /> <project-storage-detail :storage-types="storageTypes" />
</div> </div>
</template> </template>
...@@ -3,11 +3,15 @@ import { GlLink, GlIcon, GlTableLite as GlTable, GlSprintf } from '@gitlab/ui'; ...@@ -3,11 +3,15 @@ import { GlLink, GlIcon, GlTableLite as GlTable, GlSprintf } from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import { thWidthClass } from '~/lib/utils/table_utility'; import { thWidthClass } from '~/lib/utils/table_utility';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import { PROJECT_TABLE_LABELS, HELP_LINK_ARIA_LABEL } from '../constants'; import {
PROJECT_TABLE_LABEL_STORAGE_TYPE,
PROJECT_TABLE_LABEL_USAGE,
HELP_LINK_ARIA_LABEL,
} from '../constants';
import StorageTypeIcon from './storage_type_icon.vue'; import StorageTypeIcon from './storage_type_icon.vue';
export default { export default {
name: 'StorageTable', name: 'ProjectStorageDetail',
components: { components: {
GlLink, GlLink,
GlIcon, GlIcon,
...@@ -31,13 +35,13 @@ export default { ...@@ -31,13 +35,13 @@ export default {
projectTableFields: [ projectTableFields: [
{ {
key: 'storageType', key: 'storageType',
label: PROJECT_TABLE_LABELS.STORAGE_TYPE, label: PROJECT_TABLE_LABEL_STORAGE_TYPE,
thClass: thWidthClass(90), thClass: thWidthClass(90),
sortable: true, sortable: true,
}, },
{ {
key: 'value', key: 'value',
label: PROJECT_TABLE_LABELS.VALUE, label: PROJECT_TABLE_LABEL_USAGE,
thClass: thWidthClass(10), thClass: thWidthClass(10),
sortable: true, sortable: true,
formatter: (value) => { formatter: (value) => {
......
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
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.',
);
export const PROJECT_STORAGE_TYPES = [ export const PROJECT_STORAGE_TYPES = [
{ {
id: 'buildArtifactsSize', id: 'buildArtifactsSize',
...@@ -42,20 +55,8 @@ export const PROJECT_STORAGE_TYPES = [ ...@@ -42,20 +55,8 @@ export const PROJECT_STORAGE_TYPES = [
}, },
]; ];
export const PROJECT_TABLE_LABELS = { export const PROJECT_TABLE_LABEL_PROJECT = __('Project');
STORAGE_TYPE: s__('UsageQuota|Storage type'), export const PROJECT_TABLE_LABEL_STORAGE_TYPE = s__('UsageQuota|Storage type');
VALUE: s__('UsageQuota|Usage'), export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
};
export const ERROR_MESSAGE = s__( export const SKELETON_LOADER_ROWS = 5;
'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.',
);
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import StorageCounterApp from './components/app.vue'; import ProjectStorageApp from './components/project_storage_app.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -45,7 +45,7 @@ export default (containerId = 'js-project-storage-count-app') => { ...@@ -45,7 +45,7 @@ export default (containerId = 'js-project-storage-count-app') => {
}, },
}, },
render(createElement) { render(createElement) {
return createElement(StorageCounterApp); return createElement(ProjectStorageApp);
}, },
}); });
}; };
query getProjectStorageCount($fullPath: ID!) { query getProjectStorageStatistics($fullPath: ID!) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {
id id
statistics { statistics {
......
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import { PROJECT_STORAGE_TYPES } from './constants'; import { PROJECT_STORAGE_TYPES } from './constants';
/** export const getStorageTypesFromProjectStatistics = (projectStatistics, helpLinks = {}) =>
* This method parses the results from `getProjectStorageCount` call. PROJECT_STORAGE_TYPES.reduce((types, currentType) => {
*
* @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 helpPathKey = currentType.id.replace(`Size`, `HelpPagePath`);
const helpPath = helpLinks[helpPathKey]; const helpPath = helpLinks[helpPathKey];
...@@ -22,10 +11,24 @@ export const parseGetProjectStorageResults = (data, helpLinks) => { ...@@ -22,10 +11,24 @@ export const parseGetProjectStorageResults = (data, helpLinks) => {
...currentType, ...currentType,
helpPath, helpPath,
}, },
value: storageStatistics[currentType.id], value: projectStatistics[currentType.id],
}); });
}, []); }, []);
/**
* This method parses the results from `getProjectStorageStatistics` call.
*
* @param {Object} data graphql result
* @returns {Object}
*/
export const parseGetProjectStorageResults = (data, helpLinks) => {
const projectStatistics = data?.project?.statistics;
if (!projectStatistics) {
return {};
}
const { storageSize } = projectStatistics;
const storageTypes = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
return { return {
storage: { storage: {
totalUsage: numberToHumanSize(storageSize, 1), totalUsage: numberToHumanSize(storageSize, 1),
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
include ApiHelpers
include JavaScriptFixturesHelpers
runners_token = 'runnerstoken:intabulasreferre'
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:user) { project.owner }
describe GraphQL::Query, type: :request do
include GraphqlHelpers
context 'project storage statistics query' do
before do
project.statistics.update!(
repository_size: 3900000,
lfs_objects_size: 4800000,
build_artifacts_size: 400000,
pipeline_artifacts_size: 400000,
wiki_size: 300000,
packages_size: 3800000,
uploads_size: 900000
)
end
base_input_path = 'usage_quotas/storage/queries/'
base_output_path = 'graphql/usage_quotas/storage/'
query_name = 'project_storage.query.graphql'
it "#{base_output_path}#{query_name}.json" do
query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: true)
post_graphql(query, current_user: user, variables: { fullPath: project.full_path })
expect_graphql_errors_to_be_empty
end
end
end
end
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import StorageApp from 'ee/storage_counter/components/app.vue'; import StorageApp from 'ee/storage_counter/components/app.vue';
import Project from 'ee/storage_counter/components/project.vue'; import CollapsibleProjectStorageDetail from 'ee/usage_quotas/storage/components/collapsible_project_storage_detail.vue';
import ProjectsTable from 'ee/storage_counter/components/projects_table.vue'; import ProjectList from 'ee/usage_quotas/storage/components/project_list.vue';
import StorageInlineAlert from 'ee/storage_counter/components/storage_inline_alert.vue'; import StorageInlineAlert from 'ee/storage_counter/components/storage_inline_alert.vue';
import TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue'; import TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue';
import UsageStatistics from 'ee/storage_counter/components/usage_statistics.vue'; import UsageStatistics from 'ee/storage_counter/components/usage_statistics.vue';
...@@ -22,7 +22,7 @@ describe('Storage counter app', () => { ...@@ -22,7 +22,7 @@ describe('Storage counter app', () => {
const findUsageGraph = () => wrapper.find(UsageGraph); const findUsageGraph = () => wrapper.find(UsageGraph);
const findUsageStatistics = () => wrapper.find(UsageStatistics); const findUsageStatistics = () => wrapper.find(UsageStatistics);
const findStorageInlineAlert = () => wrapper.find(StorageInlineAlert); const findStorageInlineAlert = () => wrapper.find(StorageInlineAlert);
const findProjectsTable = () => wrapper.find(ProjectsTable); const findProjectList = () => wrapper.find(ProjectList);
const findPrevButton = () => wrapper.find('[data-testid="prevButton"]'); const findPrevButton = () => wrapper.find('[data-testid="prevButton"]');
const findNextButton = () => wrapper.find('[data-testid="nextButton"]'); const findNextButton = () => wrapper.find('[data-testid="nextButton"]');
...@@ -74,7 +74,7 @@ describe('Storage counter app', () => { ...@@ -74,7 +74,7 @@ describe('Storage counter app', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.findAll(Project)).toHaveLength(3); expect(wrapper.findAll(CollapsibleProjectStorageDetail)).toHaveLength(3);
}); });
describe('limit', () => { describe('limit', () => {
...@@ -232,21 +232,21 @@ describe('Storage counter app', () => { ...@@ -232,21 +232,21 @@ describe('Storage counter app', () => {
it('triggers search if user enters search input', () => { it('triggers search if user enters search input', () => {
expect(wrapper.vm.searchTerm).toBe(''); expect(wrapper.vm.searchTerm).toBe('');
findProjectsTable().vm.$emit('search', sampleSearchTerm); findProjectList().vm.$emit('search', sampleSearchTerm);
expect(wrapper.vm.searchTerm).toBe(sampleSearchTerm); expect(wrapper.vm.searchTerm).toBe(sampleSearchTerm);
}); });
it('triggers search if user clears the entered search input', () => { it('triggers search if user clears the entered search input', () => {
const projectsTable = findProjectsTable(); const projectList = findProjectList();
expect(wrapper.vm.searchTerm).toBe(''); expect(wrapper.vm.searchTerm).toBe('');
projectsTable.vm.$emit('search', sampleSearchTerm); projectList.vm.$emit('search', sampleSearchTerm);
expect(wrapper.vm.searchTerm).toBe(sampleSearchTerm); expect(wrapper.vm.searchTerm).toBe(sampleSearchTerm);
projectsTable.vm.$emit('search', ''); projectList.vm.$emit('search', '');
expect(wrapper.vm.searchTerm).toBe(''); expect(wrapper.vm.searchTerm).toBe('');
}); });
...@@ -254,7 +254,7 @@ describe('Storage counter app', () => { ...@@ -254,7 +254,7 @@ describe('Storage counter app', () => {
it('does not trigger search if user enters short search input', () => { it('does not trigger search if user enters short search input', () => {
expect(wrapper.vm.searchTerm).toBe(''); expect(wrapper.vm.searchTerm).toBe('');
findProjectsTable().vm.$emit('search', sampleShortSearchTerm); findProjectList().vm.$emit('search', sampleShortSearchTerm);
expect(wrapper.vm.searchTerm).toBe(''); expect(wrapper.vm.searchTerm).toBe('');
}); });
......
import { shallowMount } from '@vue/test-utils';
import StorageRow from 'ee/storage_counter/components/storage_row.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
let wrapper;
const data = {
name: 'LFS Package',
value: 1293346,
};
function factory({ name, value }) {
wrapper = shallowMount(StorageRow, {
propsData: {
name,
value,
},
});
}
describe('Storage Counter row component', () => {
beforeEach(() => {
factory(data);
});
it('renders provided name', () => {
expect(wrapper.text()).toContain(data.name);
});
it('renders formatted value', () => {
expect(wrapper.text()).toContain(numberToHumanSize(data.value));
});
});
export const projects = [ import { projects } from 'ee_jest/usage_quotas/storage/mock_data';
{
id: '24',
fullPath: 'h5bp/dummy-project',
nameWithNamespace: 'H5bp / dummy project',
avatarUrl: null,
webUrl: 'http://localhost:3001/h5bp/dummy-project',
name: 'dummy project',
statistics: {
commitCount: 1,
storageSize: 41943,
repositorySize: 41943,
lfsObjectsSize: 0,
buildArtifactsSize: 0,
packagesSize: 0,
},
actualRepositorySizeLimit: 100000,
totalCalculatedUsedStorage: 41943,
totalCalculatedStorageLimit: 41943000,
},
{
id: '8',
fullPath: 'h5bp/html5-boilerplate',
nameWithNamespace: 'H5bp / Html5 Boilerplate',
avatarUrl: null,
webUrl: 'http://localhost:3001/h5bp/html5-boilerplate',
name: 'Html5 Boilerplate',
statistics: {
commitCount: 0,
storageSize: 99000,
repositorySize: 0,
lfsObjectsSize: 0,
buildArtifactsSize: 1272375,
packagesSize: 0,
},
actualRepositorySizeLimit: 100000,
totalCalculatedUsedStorage: 89000,
totalCalculatedStorageLimit: 99430,
},
{
id: '80',
fullPath: 'twit/twitter',
nameWithNamespace: 'Twitter',
avatarUrl: null,
webUrl: 'http://localhost:3001/twit/twitter',
name: 'Twitter',
statistics: {
commitCount: 0,
storageSize: 12933460,
repositorySize: 209710,
lfsObjectsSize: 209720,
buildArtifactsSize: 1272375,
packagesSize: 0,
},
actualRepositorySizeLimit: 100000,
totalCalculatedUsedStorage: 13143170,
totalCalculatedStorageLimit: 12143170,
},
];
export const namespaceData = { export const namespaceData = {
totalUsage: 'N/A', totalUsage: 'N/A',
......
...@@ -4,7 +4,8 @@ import { ...@@ -4,7 +4,8 @@ import {
parseProjects, parseProjects,
calculateUsedAndRemStorage, calculateUsedAndRemStorage,
} from 'ee/storage_counter/utils'; } from 'ee/storage_counter/utils';
import { projects as mockProjectsData, mockGetStorageCounterGraphQLResponse } from './mock_data'; import { projects as mockProjectsData } from 'ee_jest/usage_quotas/storage/mock_data';
import { mockGetStorageCounterGraphQLResponse } from './mock_data';
describe('UsageThreshold', () => { describe('UsageThreshold', () => {
it.each` it.each`
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Project from 'ee/storage_counter/components/project.vue'; import CollapsibleProjectStorageDetail from 'ee/usage_quotas/storage/components/collapsible_project_storage_detail.vue';
import StorageRow from 'ee/storage_counter/components/storage_row.vue'; import ProjectStorageDetail from 'ee/usage_quotas/storage/components/project_storage_detail.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue'; import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { projects } from '../mock_data'; import { projects } from '../mock_data';
let wrapper; let wrapper;
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(Project, { wrapper = shallowMount(CollapsibleProjectStorageDetail, {
propsData: { propsData: {
project: projects[1], project: projects[1],
}, },
...@@ -15,9 +15,9 @@ const createComponent = () => { ...@@ -15,9 +15,9 @@ const createComponent = () => {
}; };
const findTableRow = () => wrapper.find('[data-testid="projectTableRow"]'); const findTableRow = () => wrapper.find('[data-testid="projectTableRow"]');
const findStorageRow = () => wrapper.find(StorageRow); const findProjectStorageDetail = () => wrapper.find(ProjectStorageDetail);
describe('Storage Counter project component', () => { describe('CollapsibleProjectStorageDetail', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
...@@ -37,16 +37,16 @@ describe('Storage Counter project component', () => { ...@@ -37,16 +37,16 @@ describe('Storage Counter project component', () => {
describe('toggle row', () => { describe('toggle row', () => {
describe('on click', () => { describe('on click', () => {
it('toggles isOpen', () => { it('toggles isOpen', () => {
expect(findStorageRow().exists()).toBe(false); expect(findProjectStorageDetail().exists()).toBe(false);
findTableRow().trigger('click'); findTableRow().trigger('click');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(findStorageRow().exists()).toBe(true); expect(findProjectStorageDetail().exists()).toBe(true);
findTableRow().trigger('click'); findTableRow().trigger('click');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(findStorageRow().exists()).toBe(false); expect(findProjectStorageDetail().exists()).toBe(false);
}); });
}); });
}); });
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Project from 'ee/storage_counter/components/project.vue'; import CollapsibleProjectStorageDetail from 'ee/usage_quotas/storage/components/collapsible_project_storage_detail.vue';
import ProjectsTable from 'ee/storage_counter/components/projects_table.vue'; import ProjectList from 'ee/usage_quotas/storage/components/project_list.vue';
import { projects } from '../mock_data'; import { projects } from '../mock_data';
let wrapper; let wrapper;
const createComponent = ({ additionalRepoStorageByNamespace = false } = {}) => { const createComponent = ({ additionalRepoStorageByNamespace = false } = {}) => {
wrapper = shallowMount(ProjectsTable, { wrapper = shallowMount(ProjectList, {
propsData: { propsData: {
projects, projects,
additionalPurchasedStorageSize: 0, additionalPurchasedStorageSize: 0,
...@@ -19,9 +19,9 @@ const createComponent = ({ additionalRepoStorageByNamespace = false } = {}) => { ...@@ -19,9 +19,9 @@ const createComponent = ({ additionalRepoStorageByNamespace = false } = {}) => {
}); });
}; };
const findTableRows = () => wrapper.findAll(Project); const findTableRows = () => wrapper.findAll(CollapsibleProjectStorageDetail);
describe('Usage Quotas project table component', () => { describe('ProjectList', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
......
...@@ -4,21 +4,21 @@ import VueApollo from 'vue-apollo'; ...@@ -4,21 +4,21 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import StorageCounterApp from '~/projects/storage_counter/components/app.vue'; import ProjectStorageApp from 'ee/usage_quotas/storage/components/project_storage_app.vue';
import { TOTAL_USAGE_DEFAULT_TEXT } from '~/projects/storage_counter/constants'; import { TOTAL_USAGE_DEFAULT_TEXT } from 'ee/usage_quotas/storage/constants';
import getProjectStorageCount from '~/projects/storage_counter/queries/project_storage.query.graphql'; import getProjectStorageStatistics from 'ee/usage_quotas/storage/queries/project_storage.query.graphql';
import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue'; import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue';
import { import {
mockGetProjectStorageCountGraphQLResponse,
mockEmptyResponse,
projectData, projectData,
defaultProvideValues, mockGetProjectStorageStatisticsGraphQLResponse,
mockEmptyResponse,
defaultProjectProvideValues,
} from '../mock_data'; } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
describe('Storage counter app', () => { describe('ProjectStorageApp', () => {
let wrapper; let wrapper;
const createMockApolloProvider = ({ reject = false, mockedValue } = {}) => { const createMockApolloProvider = ({ reject = false, mockedValue } = {}) => {
...@@ -30,18 +30,18 @@ describe('Storage counter app', () => { ...@@ -30,18 +30,18 @@ describe('Storage counter app', () => {
response = jest.fn().mockResolvedValue(mockedValue); response = jest.fn().mockResolvedValue(mockedValue);
} }
const requestHandlers = [[getProjectStorageCount, response]]; const requestHandlers = [[getProjectStorageStatistics, response]];
return createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
}; };
const createComponent = ({ provide = {}, mockApollo } = {}) => { const createComponent = ({ provide = {}, mockApollo } = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(StorageCounterApp, { shallowMount(ProjectStorageApp, {
localVue, localVue,
apolloProvider: mockApollo, apolloProvider: mockApollo,
provide: { provide: {
...defaultProvideValues, ...defaultProjectProvideValues,
...provide, ...provide,
}, },
}), }),
...@@ -63,7 +63,7 @@ describe('Storage counter app', () => { ...@@ -63,7 +63,7 @@ describe('Storage counter app', () => {
beforeEach(async () => { beforeEach(async () => {
mockApollo = createMockApolloProvider({ mockApollo = createMockApolloProvider({
mockedValue: mockGetProjectStorageCountGraphQLResponse, mockedValue: mockGetProjectStorageStatisticsGraphQLResponse,
}); });
createComponent({ mockApollo }); createComponent({ mockApollo });
await waitForPromises(); await waitForPromises();
...@@ -75,7 +75,7 @@ describe('Storage counter app', () => { ...@@ -75,7 +75,7 @@ describe('Storage counter app', () => {
it('renders correct usage quotas help link', () => { it('renders correct usage quotas help link', () => {
expect(findUsageQuotasHelpLink().attributes('href')).toBe( expect(findUsageQuotasHelpLink().attributes('href')).toBe(
defaultProvideValues.helpLinks.usageQuotasHelpPagePath, defaultProjectProvideValues.helpLinks.usageQuotasHelpPagePath,
); );
}); });
}); });
...@@ -129,7 +129,7 @@ describe('Storage counter app', () => { ...@@ -129,7 +129,7 @@ describe('Storage counter app', () => {
beforeEach(async () => { beforeEach(async () => {
mockApollo = createMockApolloProvider({ mockApollo = createMockApolloProvider({
mockedValue: mockGetProjectStorageCountGraphQLResponse, mockedValue: mockGetProjectStorageStatisticsGraphQLResponse,
}); });
createComponent({ mockApollo }); createComponent({ mockApollo });
await waitForPromises(); await waitForPromises();
...@@ -143,7 +143,7 @@ describe('Storage counter app', () => { ...@@ -143,7 +143,7 @@ describe('Storage counter app', () => {
const { const {
__typename, __typename,
...statistics ...statistics
} = mockGetProjectStorageCountGraphQLResponse.data.project.statistics; } = mockGetProjectStorageStatisticsGraphQLResponse.data.project.statistics;
expect(findUsageGraph().props('rootStorageStatistics')).toMatchObject(statistics); expect(findUsageGraph().props('rootStorageStatistics')).toMatchObject(statistics);
}); });
}); });
......
import { GlTableLite } from '@gitlab/ui'; import { GlTableLite } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import StorageTable from '~/projects/storage_counter/components/storage_table.vue'; import ProjectStorageDetail from 'ee/usage_quotas/storage/components/project_storage_detail.vue';
import { projectData, defaultProvideValues } from '../mock_data'; import { projectData, projectHelpLinks } from '../mock_data';
describe('StorageTable', () => { describe('ProjectStorageDetail', () => {
let wrapper; let wrapper;
const defaultProps = { const defaultProps = {
...@@ -13,7 +13,7 @@ describe('StorageTable', () => { ...@@ -13,7 +13,7 @@ describe('StorageTable', () => {
const createComponent = (props = {}) => { const createComponent = (props = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(StorageTable, { mount(ProjectStorageDetail, {
propsData: { propsData: {
...defaultProps, ...defaultProps,
...props, ...props,
...@@ -39,7 +39,7 @@ describe('StorageTable', () => { ...@@ -39,7 +39,7 @@ describe('StorageTable', () => {
expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description); expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description);
expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id); expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id);
expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe( expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(
defaultProvideValues.helpLinks[id.replace(`Size`, `HelpPagePath`)] projectHelpLinks[id.replace(`Size`, `HelpPagePath`)]
.replace(`Size`, ``) .replace(`Size`, ``)
.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`), .replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`),
); );
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import ProjectsSkeletonLoader from 'ee/storage_counter/components/projects_skeleton_loader.vue'; import ProjectsSkeletonLoader from 'ee/usage_quotas/storage/components/projects_skeleton_loader.vue';
describe('ProjectsSkeletonLoader', () => { describe('ProjectsSkeletonLoader', () => {
let wrapper; let wrapper;
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import StorageTypeIcon from '~/projects/storage_counter/components/storage_type_icon.vue'; import StorageTypeIcon from 'ee/usage_quotas/storage/components/storage_type_icon.vue';
describe('StorageTypeIcon', () => { describe('StorageTypeIcon', () => {
let wrapper; let wrapper;
......
import mockGetProjectStorageCountGraphQLResponse from 'test_fixtures/graphql/projects/storage_counter/project_storage.query.graphql.json'; import mockGetProjectStorageStatisticsGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/project_storage.query.graphql.json';
export { mockGetProjectStorageCountGraphQLResponse }; export { mockGetProjectStorageStatisticsGraphQLResponse };
export const mockEmptyResponse = { data: { project: null } }; export const mockEmptyResponse = { data: { project: null } };
export const defaultProvideValues = { export const projects = [
projectPath: '/project-path', {
helpLinks: { id: '24',
usageQuotasHelpPagePath: '/usage-quotas', fullPath: 'h5bp/dummy-project',
buildArtifactsHelpPagePath: '/build-artifacts', nameWithNamespace: 'H5bp / dummy project',
lfsObjectsHelpPagePath: '/lsf-objects', avatarUrl: null,
packagesHelpPagePath: '/packages', webUrl: 'http://localhost:3001/h5bp/dummy-project',
repositoryHelpPagePath: '/repository', name: 'dummy project',
snippetsHelpPagePath: '/snippets', statistics: {
uploadsHelpPagePath: '/uploads', commitCount: 1,
wikiHelpPagePath: '/wiki', storageSize: 41943,
repositorySize: 41943,
lfsObjectsSize: 0,
buildArtifactsSize: 0,
packagesSize: 0,
},
actualRepositorySizeLimit: 100000,
totalCalculatedUsedStorage: 41943,
totalCalculatedStorageLimit: 41943000,
}, },
}; {
id: '8',
fullPath: 'h5bp/html5-boilerplate',
nameWithNamespace: 'H5bp / Html5 Boilerplate',
avatarUrl: null,
webUrl: 'http://localhost:3001/h5bp/html5-boilerplate',
name: 'Html5 Boilerplate',
statistics: {
commitCount: 0,
storageSize: 99000,
repositorySize: 0,
lfsObjectsSize: 0,
buildArtifactsSize: 1272375,
packagesSize: 0,
},
actualRepositorySizeLimit: 100000,
totalCalculatedUsedStorage: 89000,
totalCalculatedStorageLimit: 99430,
},
{
id: '80',
fullPath: 'twit/twitter',
nameWithNamespace: 'Twitter',
avatarUrl: null,
webUrl: 'http://localhost:3001/twit/twitter',
name: 'Twitter',
statistics: {
commitCount: 0,
storageSize: 12933460,
repositorySize: 209710,
lfsObjectsSize: 209720,
buildArtifactsSize: 1272375,
packagesSize: 0,
},
actualRepositorySizeLimit: 100000,
totalCalculatedUsedStorage: 13143170,
totalCalculatedStorageLimit: 12143170,
},
];
export const projectData = { export const projectData = {
storage: { storage: {
...@@ -90,3 +136,19 @@ export const projectData = { ...@@ -90,3 +136,19 @@ export const projectData = {
], ],
}, },
}; };
export const projectHelpLinks = {
usageQuotasHelpPagePath: '/usage-quotas',
buildArtifactsHelpPagePath: '/build-artifacts',
lfsObjectsHelpPagePath: '/lsf-objects',
packagesHelpPagePath: '/packages',
repositoryHelpPagePath: '/repository',
snippetsHelpPagePath: '/snippets',
uploadsHelpPagePath: '/uploads',
wikiHelpPagePath: '/wiki',
};
export const defaultProjectProvideValues = {
projectPath: '/project-path',
helpLinks: projectHelpLinks,
};
import { parseGetProjectStorageResults } from '~/projects/storage_counter/utils'; import { parseGetProjectStorageResults } from 'ee/usage_quotas/storage/utils';
import { import {
mockGetProjectStorageCountGraphQLResponse,
projectData, projectData,
defaultProvideValues, mockGetProjectStorageStatisticsGraphQLResponse,
defaultProjectProvideValues,
} from './mock_data'; } from './mock_data';
describe('parseGetProjectStorageResults', () => { describe('parseGetProjectStorageResults', () => {
it('parses project statistics correctly', () => { it('parses project statistics correctly', () => {
expect( expect(
parseGetProjectStorageResults( parseGetProjectStorageResults(
mockGetProjectStorageCountGraphQLResponse.data, mockGetProjectStorageStatisticsGraphQLResponse.data,
defaultProvideValues.helpLinks, defaultProjectProvideValues.helpLinks,
), ),
).toMatchObject(projectData); ).toMatchObject(projectData);
}); });
it('includes storage type with size of 0 in returned value', () => { it('includes storage type with size of 0 in returned value', () => {
const mockedResponse = mockGetProjectStorageCountGraphQLResponse.data; const mockedResponse = mockGetProjectStorageStatisticsGraphQLResponse.data;
// ensuring a specific storage type item has size of 0 // ensuring a specific storage type item has size of 0
mockedResponse.project.statistics.repositorySize = 0; mockedResponse.project.statistics.repositorySize = 0;
const response = parseGetProjectStorageResults(mockedResponse, defaultProvideValues.helpLinks); const response = parseGetProjectStorageResults(
mockedResponse,
defaultProjectProvideValues.helpLinks,
);
expect(response.storage.storageTypes).toEqual( expect(response.storage.storageTypes).toEqual(
expect.arrayContaining([ expect.arrayContaining([
......
...@@ -37424,9 +37424,6 @@ msgstr "" ...@@ -37424,9 +37424,6 @@ msgstr ""
msgid "Upvotes" msgid "Upvotes"
msgstr "" msgstr ""
msgid "Usage"
msgstr ""
msgid "Usage Trends" msgid "Usage Trends"
msgstr "" msgstr ""
...@@ -37484,9 +37481,6 @@ msgstr "" ...@@ -37484,9 +37481,6 @@ msgstr ""
msgid "UsageQuota|LFS Objects" msgid "UsageQuota|LFS Objects"
msgstr "" msgstr ""
msgid "UsageQuota|LFS Storage"
msgstr ""
msgid "UsageQuota|LFS storage" msgid "UsageQuota|LFS storage"
msgstr "" msgstr ""
......
...@@ -65,31 +65,5 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do ...@@ -65,31 +65,5 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
expect_graphql_errors_to_be_empty expect_graphql_errors_to_be_empty
end end
end end
context 'project storage count query' do
before do
project.statistics.update!(
repository_size: 3900000,
lfs_objects_size: 4800000,
build_artifacts_size: 400000,
pipeline_artifacts_size: 400000,
wiki_size: 300000,
packages_size: 3800000,
uploads_size: 900000
)
end
base_input_path = 'projects/storage_counter/queries/'
base_output_path = 'graphql/projects/storage_counter/'
query_name = 'project_storage.query.graphql'
it "#{base_output_path}#{query_name}.json" do
query = get_graphql_query_as_string("#{base_input_path}#{query_name}")
post_graphql(query, current_user: user, variables: { fullPath: project.full_path })
expect_graphql_errors_to_be_empty
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment