Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
2c029fcf
Commit
2c029fcf
authored
Oct 13, 2020
by
Dhiraj Bodicherla
Committed by
Vitaly Slobodin
Oct 13, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add usage stats to quotas page
This MR adds more info about usage statistics in the Usage Quotas page
parent
83fe18a3
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
343 additions
and
20 deletions
+343
-20
app/assets/javascripts/lib/utils/constants.js
app/assets/javascripts/lib/utils/constants.js
+1
-0
app/assets/javascripts/lib/utils/number_utils.js
app/assets/javascripts/lib/utils/number_utils.js
+13
-1
ee/app/assets/javascripts/storage_counter/components/app.vue
ee/app/assets/javascripts/storage_counter/components/app.vue
+19
-12
ee/app/assets/javascripts/storage_counter/components/usage_statistics.vue
...vascripts/storage_counter/components/usage_statistics.vue
+115
-0
ee/app/assets/javascripts/storage_counter/components/usage_statistics_card.vue
...ipts/storage_counter/components/usage_statistics_card.vue
+56
-0
ee/spec/frontend/storage_counter/components/app_spec.js
ee/spec/frontend/storage_counter/components/app_spec.js
+47
-6
ee/spec/frontend/storage_counter/components/usage_statistics_spec.js
...ntend/storage_counter/components/usage_statistics_spec.js
+49
-0
ee/spec/frontend/storage_counter/mock_data.js
ee/spec/frontend/storage_counter/mock_data.js
+14
-1
locale/gitlab.pot
locale/gitlab.pot
+18
-0
spec/frontend/lib/utils/number_utility_spec.js
spec/frontend/lib/utils/number_utility_spec.js
+11
-0
No files found.
app/assets/javascripts/lib/utils/constants.js
View file @
2c029fcf
export
const
BYTES_IN_KIB
=
1024
;
export
const
BYTES_IN_KB
=
1000
;
export
const
HIDDEN_CLASS
=
'
hidden
'
;
export
const
TRUNCATE_WIDTH_DEFAULT_WIDTH
=
80
;
export
const
TRUNCATE_WIDTH_DEFAULT_FONT_SIZE
=
12
;
...
...
app/assets/javascripts/lib/utils/number_utils.js
View file @
2c029fcf
import
{
BYTES_IN_KIB
}
from
'
./constants
'
;
import
{
BYTES_IN_KIB
,
BYTES_IN_KB
}
from
'
./constants
'
;
import
{
sprintf
,
__
}
from
'
~/locale
'
;
/**
...
...
@@ -34,6 +34,18 @@ export function formatRelevantDigits(number) {
return
formattedNumber
;
}
/**
* Utility function that calculates KB of the given bytes.
* Note: This method calculates KiloBytes as opposed to
* Kibibytes. For Kibibytes, bytesToKiB should be used.
*
* @param {Number} number bytes
* @return {Number} KiB
*/
export
function
bytesToKB
(
number
)
{
return
number
/
BYTES_IN_KB
;
}
/**
* Utility function that calculates KiB of the given bytes.
*
...
...
ee/app/assets/javascripts/storage_counter/components/app.vue
View file @
2c029fcf
<
script
>
import
{
GlLink
,
GlSprintf
,
GlModalDirective
,
GlButton
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
ProjectsTable
from
'
./projects_table.vue
'
;
import
UsageGraph
from
'
./usage_graph.vue
'
;
import
UsageStatistics
from
'
./usage_statistics.vue
'
;
import
query
from
'
../queries/storage.query.graphql
'
;
import
TemporaryStorageIncreaseModal
from
'
./temporary_storage_increase_modal.vue
'
;
import
{
numberToHumanSize
}
from
'
~/lib/utils/number_utils
'
;
...
...
@@ -16,11 +18,13 @@ export default {
GlSprintf
,
GlIcon
,
UsageGraph
,
UsageStatistics
,
TemporaryStorageIncreaseModal
,
},
directives
:
{
GlModalDirective
,
},
mixins
:
[
glFeatureFlagsMixin
()],
props
:
{
namespacePath
:
{
type
:
String
,
...
...
@@ -78,6 +82,9 @@ export default {
isStorageIncreaseModalVisible
()
{
return
parseBoolean
(
this
.
isTemporaryStorageIncreaseVisible
);
},
isAdditionalStorageFlagEnabled
()
{
return
this
.
glFeatures
.
additionalRepoStorageByNamespace
;
},
},
methods
:
{
formatSize
(
size
)
{
...
...
@@ -89,9 +96,12 @@ export default {
</
script
>
<
template
>
<div>
<div
class=
"pipeline-quota container-fluid py-4 px-2 m-0"
>
<div
class=
"row py-0 d-flex align-items-center"
>
<div
class=
"col-lg-6"
>
<div
v-if=
"isAdditionalStorageFlagEnabled && namespace.rootStorageStatistics"
>
<usage-statistics
:root-storage-statistics=
"namespace.rootStorageStatistics"
/>
</div>
<div
v-else
class=
"gl-py-4 gl-px-2 gl-m-0"
>
<div
class=
"gl-display-flex gl-align-items-center"
>
<div
class=
"gl-w-half"
>
<gl-sprintf
:message=
"s__('UsageQuota|You used: %
{usage} %{limit}')">
<template
#usage
>
<span
class=
"gl-font-weight-bold"
data-testid=
"total-usage"
>
...
...
@@ -117,7 +127,7 @@ export default {
<gl-icon
name=
"question"
:size=
"12"
/>
</gl-link>
</div>
<div
class=
"
col-lg-6 text-lg
-right"
>
<div
class=
"
gl-w-half gl-text
-right"
>
<gl-button
v-if=
"isStorageIncreaseModalVisible"
v-gl-modal-directive=
"$options.modalId"
...
...
@@ -136,16 +146,13 @@ export default {
>
</div>
</div>
<div
class=
"row py-0"
>
<div
class=
"col-sm-12"
>
<div
v-if=
"namespace.rootStorageStatistics"
class=
"gl-w-full"
>
<usage-graph
v-if=
"namespace.rootStorageStatistics"
:root-storage-statistics=
"namespace.rootStorageStatistics"
:limit=
"namespace.limit"
/>
</div>
</div>
</div>
<projects-table
:projects=
"namespaceProjects"
/>
<temporary-storage-increase-modal
v-if=
"isStorageIncreaseModalVisible"
...
...
ee/app/assets/javascripts/storage_counter/components/usage_statistics.vue
0 → 100644
View file @
2c029fcf
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
UsageStatisticsCard
from
'
./usage_statistics_card.vue
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
bytesToKB
}
from
'
~/lib/utils/number_utils
'
;
import
{
getFormatter
,
SUPPORTED_FORMATS
}
from
'
~/lib/utils/unit_format
'
;
export
default
{
components
:
{
GlButton
,
UsageStatisticsCard
,
},
props
:
{
rootStorageStatistics
:
{
required
:
true
,
type
:
Object
,
},
},
computed
:
{
totalUsage
()
{
const
{
repositorySize
=
0
,
lfsObjectsSize
=
0
}
=
this
.
rootStorageStatistics
;
return
{
usage
:
this
.
formatSize
(
repositorySize
+
lfsObjectsSize
),
description
:
s__
(
'
UsageQuota|Total namespace storage used
'
),
link
:
{
text
:
s__
(
'
UsageQuota|Learn more about usage quotas
'
),
url
:
'
#
'
,
},
};
},
excessUsage
()
{
return
{
usage
:
this
.
formatSize
(
0
),
description
:
s__
(
'
UsageQuota|Total excess storage used
'
),
link
:
{
text
:
s__
(
'
UsageQuota|Learn more about excess storage usage
'
),
url
:
'
#
'
,
},
};
},
purchasedUsage
()
{
return
{
usage
:
this
.
formatSize
(
0
),
description
:
s__
(
'
UsageQuota|Purchased storage available
'
),
link
:
{
text
:
s__
(
'
UsageQuota|Purchase more storage
'
),
url
:
'
#
'
,
},
};
},
},
methods
:
{
/**
* The formatDecimalBytes method returns
* value along with the unit. However, the unit
* and the value needs to be separated so that
* they can have different styles. The method
* splits the value into value and unit.
*
* We want to display all units above bytes. Hence
* converting bytesToKB before passing it to
* `getFormatter`
*
* @params {Number} size size in bytes
* @returns {Object} value and unit of formatted size
*/
formatSize
(
size
)
{
const
formatDecimalBytes
=
getFormatter
(
SUPPORTED_FORMATS
.
kilobytes
);
const
formattedSize
=
formatDecimalBytes
(
bytesToKB
(
size
),
1
);
return
{
value
:
formattedSize
.
slice
(
0
,
-
2
),
unit
:
formattedSize
.
slice
(
-
2
),
};
},
},
};
</
script
>
<
template
>
<div
class=
"gl-display-flex gl-sm-flex-direction-column"
>
<usage-statistics-card
data-testid=
"totalUsage"
:usage=
"totalUsage.usage"
:link=
"totalUsage.link"
:description=
"totalUsage.description"
css-class=
"gl-mr-4"
/>
<usage-statistics-card
data-testid=
"excessUsage"
:usage=
"excessUsage.usage"
:link=
"excessUsage.link"
:description=
"excessUsage.description"
css-class=
"gl-mx-4"
/>
<usage-statistics-card
data-testid=
"purchasedUsage"
:usage=
"purchasedUsage.usage"
:link=
"purchasedUsage.link"
:description=
"purchasedUsage.description"
css-class=
"gl-ml-4"
>
<template
#link
="
{link}">
<gl-button
target=
"_blank"
:href=
"link.url"
class=
"mb-0"
variant=
"success"
category=
"primary"
block
>
{{
link
.
text
}}
</gl-button>
</
template
>
</usage-statistics-card>
</div>
</template>
ee/app/assets/javascripts/storage_counter/components/usage_statistics_card.vue
0 → 100644
View file @
2c029fcf
<
script
>
import
{
GlLink
,
GlIcon
,
GlSprintf
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlIcon
,
GlLink
,
GlSprintf
,
},
props
:
{
link
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({
text
:
''
,
url
:
''
}),
},
description
:
{
type
:
String
,
required
:
true
,
},
usage
:
{
type
:
Object
,
required
:
true
,
},
cssClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
};
</
script
>
<
template
>
<div
class=
"gl-p-5 gl-my-5 gl-bg-gray-10 gl-flex-fill-1 gl-white-space-nowrap"
:class=
"cssClass"
>
<p
class=
"mb-2"
>
<gl-sprintf
:message=
"__('%
{size} %{unit}')">
<template
#size
>
<span
class=
"gl-font-size-h-display gl-font-weight-bold"
>
{{
usage
.
value
}}
</span>
</
template
>
<
template
#unit
>
<span
class=
"gl-font-lg gl-font-weight-bold"
>
{{
usage
.
unit
}}
</span>
</
template
>
</gl-sprintf>
</p>
<p
class=
"gl-border-b-2 gl-border-b-solid gl-border-b-gray-100 gl-font-weight-bold gl-pb-3"
>
{{ description }}
</p>
<p
class=
"gl-mb-0"
>
<slot
v-bind=
"{ link }"
name=
"link"
>
<gl-link
target=
"_blank"
:href=
"link.url"
>
<span
class=
"text-truncate"
>
{{ link.text }}
</span>
<gl-icon
name=
"external-link"
class=
"gl-ml-2 gl-flex-shrink-0 gl-text-black-normal"
/>
</gl-link>
</slot>
</p>
</div>
</template>
ee/spec/frontend/storage_counter/components/app_spec.js
View file @
2c029fcf
import
{
mount
}
from
'
@vue/test-utils
'
;
import
StorageApp
from
'
ee/storage_counter/components/app.vue
'
;
import
Project
from
'
ee/storage_counter/components/project.vue
'
;
import
UsageGraph
from
'
ee/storage_counter/components/usage_graph.vue
'
;
import
UsageStatistics
from
'
ee/storage_counter/components/usage_statistics.vue
'
;
import
TemporaryStorageIncreaseModal
from
'
ee/storage_counter/components/temporary_storage_increase_modal.vue
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
namespaceData
,
withRootStorageStatistics
}
from
'
../mock_data
'
;
...
...
@@ -15,8 +17,14 @@ describe('Storage counter app', () => {
const
findPurchaseStorageLink
=
()
=>
wrapper
.
find
(
"
[data-testid='purchase-storage-link']
"
);
const
findTemporaryStorageIncreaseButton
=
()
=>
wrapper
.
find
(
"
[data-testid='temporary-storage-increase-button']
"
);
function
createComponent
(
props
=
{},
loading
=
false
)
{
const
findUsageGraph
=
()
=>
wrapper
.
find
(
UsageGraph
);
const
findUsageStatistics
=
()
=>
wrapper
.
find
(
UsageStatistics
);
const
createComponent
=
({
props
=
{},
loading
=
false
,
additionalRepoStorageByNamespace
=
false
,
}
=
{})
=>
{
const
$apollo
=
{
queries
:
{
namespace
:
{
...
...
@@ -31,8 +39,13 @@ describe('Storage counter app', () => {
directives
:
{
GlModalDirective
:
createMockDirective
(),
},
provide
:
{
glFeatures
:
{
additionalRepoStorageByNamespace
,
},
},
});
}
}
;
beforeEach
(()
=>
{
createComponent
();
...
...
@@ -86,6 +99,34 @@ describe('Storage counter app', () => {
});
});
describe
(
'
with additional_repo_storage_by_namespace feature flag
'
,
()
=>
{
it
(
'
usage_graph component hidden is when flag is false
'
,
async
()
=>
{
wrapper
.
setData
({
namespace
:
withRootStorageStatistics
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
findUsageGraph
().
exists
()).
toBe
(
true
);
expect
(
findUsageStatistics
().
exists
()).
toBe
(
false
);
});
it
(
'
usage_statistics component is rendered when flag is true
'
,
async
()
=>
{
createComponent
({
additionalRepoStorageByNamespace
:
true
,
});
wrapper
.
setData
({
namespace
:
withRootStorageStatistics
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
findUsageStatistics
().
exists
()).
toBe
(
true
);
expect
(
findUsageGraph
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
without rootStorageStatistics information
'
,
()
=>
{
it
(
'
renders N/A
'
,
async
()
=>
{
wrapper
.
setData
({
...
...
@@ -107,7 +148,7 @@ describe('Storage counter app', () => {
describe
(
'
when purchaseStorageUrl is set
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
p
urchaseStorageUrl
:
'
customers.gitlab.com
'
});
createComponent
({
p
rops
:
{
purchaseStorageUrl
:
'
customers.gitlab.com
'
}
});
});
it
(
'
does render link
'
,
()
=>
{
...
...
@@ -127,7 +168,7 @@ describe('Storage counter app', () => {
${{
isTemporaryStorageIncreaseVisible
:
'
true
'
}
} |
${
true
}
`
(
'
with $props
'
,
({
props
,
isVisible
})
=>
{
beforeEach
(()
=>
{
createComponent
(
props
);
createComponent
(
{
props
}
);
});
it
(
`renders button =
${
isVisible
}
`
,
()
=>
{
...
...
@@ -137,7 +178,7 @@ describe('Storage counter app', () => {
describe
(
'
when temporary storage increase is visible
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
isTemporaryStorageIncreaseVisible
:
'
true
'
});
createComponent
({
props
:
{
isTemporaryStorageIncreaseVisible
:
'
true
'
}
});
wrapper
.
setData
({
namespace
:
{
...
namespaceData
,
...
...
ee/spec/frontend/storage_counter/components/usage_statistics_spec.js
0 → 100644
View file @
2c029fcf
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlButton
,
GlLink
}
from
'
@gitlab/ui
'
;
import
UsageStatistics
from
'
ee/storage_counter/components/usage_statistics.vue
'
;
import
UsageStatisticsCard
from
'
ee/storage_counter/components/usage_statistics_card.vue
'
;
import
{
withRootStorageStatistics
}
from
'
../mock_data
'
;
describe
(
'
Usage Statistics component
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
()
=>
{
wrapper
=
shallowMount
(
UsageStatistics
,
{
propsData
:
{
rootStorageStatistics
:
withRootStorageStatistics
.
rootStorageStatistics
,
},
stubs
:
{
UsageStatisticsCard
,
GlLink
,
},
});
};
const
getStatisticsCards
=
()
=>
wrapper
.
findAll
(
UsageStatisticsCard
);
const
getStatisticsCard
=
testId
=>
wrapper
.
find
(
`[data-testid="
${
testId
}
"]`
);
beforeEach
(()
=>
{
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders three statistics cards
'
,
()
=>
{
expect
(
getStatisticsCards
()).
toHaveLength
(
3
);
});
it
.
each
`
cardName | componentName | componentType
${
'
totalUsage
'
}
|
${
'
GlLink
'
}
|
${
GlLink
}
${
'
excessUsage
'
}
|
${
'
GlLink
'
}
|
${
GlLink
}
${
'
purchasedUsage
'
}
|
${
'
GlButton
'
}
|
${
GlButton
}
`
(
'
renders $componentName in $cardName
'
,
({
cardName
,
componentType
})
=>
{
expect
(
getStatisticsCard
(
cardName
)
.
find
(
componentType
)
.
exists
(),
).
toBe
(
true
);
});
});
ee/spec/frontend/storage_counter/mock_data.js
View file @
2c029fcf
...
...
@@ -55,4 +55,17 @@ export const namespaceData = {
projects
,
};
export
const
withRootStorageStatistics
=
{
...
projects
,
totalUsage
:
3261070
};
export
const
withRootStorageStatistics
=
{
projects
,
limit
:
10000000
,
totalUsage
:
129334601
,
rootStorageStatistics
:
{
storageSize
:
129334601
,
repositorySize
:
46012030
,
lfsObjectsSize
:
4329334601203
,
buildArtifactsSize
:
1272375
,
packagesSize
:
123123120
,
wikiSize
:
1000
,
snippetsSize
:
10000
,
},
};
locale/gitlab.pot
View file @
2c029fcf
...
...
@@ -746,6 +746,9 @@ msgid_plural "%{securityScanner} results are not available because a pipeline ha
msgstr[0] ""
msgstr[1] ""
msgid "%{size} %{unit}"
msgstr ""
msgid "%{size} GiB"
msgstr ""
...
...
@@ -28072,6 +28075,12 @@ msgstr ""
msgid "UsageQuota|LFS Storage"
msgstr ""
msgid "UsageQuota|Learn more about excess storage usage"
msgstr ""
msgid "UsageQuota|Learn more about usage quotas"
msgstr ""
msgid "UsageQuota|Packages"
msgstr ""
...
...
@@ -28081,6 +28090,9 @@ msgstr ""
msgid "UsageQuota|Purchase more storage"
msgstr ""
msgid "UsageQuota|Purchased storage available"
msgstr ""
msgid "UsageQuota|Repositories"
msgstr ""
...
...
@@ -28102,6 +28114,12 @@ msgstr ""
msgid "UsageQuota|This project is locked."
msgstr ""
msgid "UsageQuota|Total excess storage used"
msgstr ""
msgid "UsageQuota|Total namespace storage used"
msgstr ""
msgid "UsageQuota|Unlimited"
msgstr ""
...
...
spec/frontend/lib/utils/number_utility_spec.js
View file @
2c029fcf
import
{
formatRelevantDigits
,
bytesToKB
,
bytesToKiB
,
bytesToMiB
,
bytesToGiB
,
...
...
@@ -54,6 +55,16 @@ describe('Number Utils', () => {
});
});
describe
(
'
bytesToKB
'
,
()
=>
{
it
.
each
`
input | output
${
1000
}
|
${
1
}
${
1024
}
|
${
1.024
}
`
(
'
returns $output KB for $input bytes
'
,
({
input
,
output
})
=>
{
expect
(
bytesToKB
(
input
)).
toBe
(
output
);
});
});
describe
(
'
bytesToKiB
'
,
()
=>
{
it
(
'
calculates KiB for the given bytes
'
,
()
=>
{
expect
(
bytesToKiB
(
1024
)).
toEqual
(
1
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment