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
831dadd8
Commit
831dadd8
authored
Oct 17, 2020
by
Andrei Stoicescu
Committed by
Vitaly Slobodin
Oct 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add "seats in use" table to billings page
- add html, css and state
parent
56daee5b
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
507 additions
and
80 deletions
+507
-80
app/assets/stylesheets/framework/tables.scss
app/assets/stylesheets/framework/tables.scss
+7
-3
ee/app/assets/javascripts/billings/components/app.vue
ee/app/assets/javascripts/billings/components/app.vue
+30
-7
ee/app/assets/javascripts/billings/components/subscription_seats.vue
...ts/javascripts/billings/components/subscription_seats.vue
+120
-0
ee/app/assets/javascripts/billings/components/subscription_table.vue
...ts/javascripts/billings/components/subscription_table.vue
+9
-3
ee/app/assets/javascripts/billings/stores/modules/subscription/actions.js
...vascripts/billings/stores/modules/subscription/actions.js
+35
-8
ee/app/assets/javascripts/billings/stores/modules/subscription/mutation_types.js
...ts/billings/stores/modules/subscription/mutation_types.js
+5
-0
ee/app/assets/javascripts/billings/stores/modules/subscription/mutations.js
...scripts/billings/stores/modules/subscription/mutations.js
+30
-6
ee/app/assets/javascripts/billings/stores/modules/subscription/state.js
...javascripts/billings/stores/modules/subscription/state.js
+5
-2
ee/app/assets/stylesheets/pages/billings.scss
ee/app/assets/stylesheets/pages/billings.scss
+34
-0
ee/app/controllers/groups/billings_controller.rb
ee/app/controllers/groups/billings_controller.rb
+4
-0
ee/app/helpers/billing_plans_helper.rb
ee/app/helpers/billing_plans_helper.rb
+1
-1
ee/app/views/shared/billings/_billing_plans.html.haml
ee/app/views/shared/billings/_billing_plans.html.haml
+1
-1
ee/spec/frontend/billings/components/__snapshots__/app_spec.js.snap
...ontend/billings/components/__snapshots__/app_spec.js.snap
+0
-9
ee/spec/frontend/billings/components/app_spec.js
ee/spec/frontend/billings/components/app_spec.js
+51
-7
ee/spec/frontend/billings/components/subscription_seats_spec.js
...c/frontend/billings/components/subscription_seats_spec.js
+112
-0
ee/spec/frontend/billings/components/subscription_table_spec.js
...c/frontend/billings/components/subscription_table_spec.js
+3
-3
ee/spec/frontend/billings/mock_data.js
ee/spec/frontend/billings/mock_data.js
+33
-15
ee/spec/frontend/billings/store/modules/subscription/mutations_spec.js
...end/billings/store/modules/subscription/mutations_spec.js
+11
-11
locale/gitlab.pot
locale/gitlab.pot
+16
-4
No files found.
app/assets/stylesheets/framework/tables.scss
View file @
831dadd8
...
...
@@ -9,11 +9,15 @@ table {
* This is a temporary workaround until we fix the neutral
* color palette in https://gitlab.com/gitlab-org/gitlab/-/issues/213570
*
* The overwrites here affected the security dashboard tables, when removing
* this code, table-th-transparent and original-text-color classes should
* be removed there.
* The overwrites here affected the following areas:
* - The security dashboard tables. When removing
* this code, table-th-transparent and original-text-color classes should
* be removed there.
* - The subscription seats table. When removing this code, the .seats-table
* <th> and margin overrides should be removed there.
*
* Remove this code as soon as this happens
*
*/
&
.gl-table
{
@include
gl-text-gray-500
;
...
...
ee/app/assets/javascripts/billings/components/app.vue
View file @
831dadd8
<
script
>
import
{
mapActions
}
from
'
vuex
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
SubscriptionTable
from
'
./subscription_table.vue
'
;
import
SubscriptionSeats
from
'
./subscription_seats.vue
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
export
default
{
name
:
'
SubscriptionApp
'
,
components
:
{
SubscriptionTable
,
SubscriptionSeats
,
},
mixins
:
[
glFeatureFlagsMixin
()],
props
:
{
planUpgradeHref
:
{
type
:
String
,
...
...
@@ -28,19 +32,38 @@ export default {
required
:
true
,
},
},
computed
:
{
...
mapState
(
'
subscription
'
,
[
'
hasBillableGroupMembers
'
]),
isFeatureFlagEnabled
()
{
return
this
.
glFeatures
?.
apiBillableMemberList
;
},
},
created
()
{
this
.
setNamespaceId
(
this
.
namespaceId
);
if
(
this
.
isFeatureFlagEnabled
)
{
this
.
fetchHasBillableGroupMembers
();
}
},
methods
:
{
...
mapActions
(
'
subscription
'
,
[
'
setNamespaceId
'
]),
...
mapActions
(
'
subscription
'
,
[
'
setNamespaceId
'
,
'
fetchHasBillableGroupMembers
'
]),
},
};
</
script
>
<
template
>
<subscription-table
:namespace-name=
"namespaceName"
:plan-upgrade-href=
"planUpgradeHref"
:customer-portal-url=
"customerPortalUrl"
/>
<div>
<subscription-table
:namespace-name=
"namespaceName"
:plan-upgrade-href=
"planUpgradeHref"
:customer-portal-url=
"customerPortalUrl"
/>
<subscription-seats
v-if=
"isFeatureFlagEnabled && hasBillableGroupMembers"
:namespace-name=
"namespaceName"
:namespace-id=
"namespaceId"
class=
"gl-mt-7"
/>
</div>
</
template
>
ee/app/assets/javascripts/billings/components/subscription_seats.vue
0 → 100644
View file @
831dadd8
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
GlTable
,
GlAvatarLabeled
,
GlAvatarLink
,
GlPagination
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
parseInt
}
from
'
lodash
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
const
AVATAR_SIZE
=
32
;
export
default
{
components
:
{
GlTable
,
GlAvatarLabeled
,
GlAvatarLink
,
GlPagination
,
GlLoadingIcon
,
},
props
:
{
namespaceName
:
{
type
:
String
,
required
:
true
,
},
namespaceId
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
fields
:
[
'
user
'
],
};
},
computed
:
{
...
mapState
(
'
seats
'
,
[
'
members
'
,
'
isLoading
'
,
'
page
'
,
'
perPage
'
,
'
total
'
]),
items
()
{
return
this
.
members
.
map
(({
name
,
username
,
avatar_url
,
web_url
})
=>
{
const
formattedUserName
=
`@
${
username
}
`
;
return
{
user
:
{
name
,
username
:
formattedUserName
,
avatar_url
,
web_url
}
};
});
},
headingText
()
{
return
sprintf
(
s__
(
'
Billing|Users occupying seats in %{namespaceName} Group (%{total})
'
),
{
total
:
this
.
total
,
namespaceName
:
this
.
namespaceName
,
});
},
subHeadingText
()
{
return
s__
(
'
Billing|Updated live
'
);
},
currentPage
:
{
get
()
{
return
parseInt
(
this
.
page
,
10
);
},
set
(
val
)
{
this
.
fetchBillableMembersList
(
val
);
},
},
perPageFormatted
()
{
return
parseInt
(
this
.
perPage
,
10
);
},
totalFormatted
()
{
return
parseInt
(
this
.
total
,
10
);
},
},
created
()
{
this
.
setNamespaceId
(
this
.
namespaceId
);
this
.
fetchBillableMembersList
(
1
);
},
methods
:
{
...
mapActions
(
'
seats
'
,
[
'
setNamespaceId
'
,
'
fetchBillableMembersList
'
]),
inputHandler
(
val
)
{
this
.
fetchBillableMembersList
(
val
);
},
},
avatarSize
:
AVATAR_SIZE
,
};
</
script
>
<
template
>
<div>
<h4
data-testid=
"heading"
>
{{
headingText
}}
</h4>
<p>
{{
subHeadingText
}}
</p>
<gl-table
data-testid=
"seats-table"
class=
"seats-table"
:items=
"items"
:fields=
"fields"
:busy=
"isLoading"
:show-empty=
"true"
>
<template
#cell(user)=
"data"
>
<gl-avatar-link
target=
"blank"
:href=
"data.value.web_url"
:alt=
"data.value.name"
>
<gl-avatar-labeled
:src=
"data.value.avatar_url"
:size=
"$options.avatarSize"
:label=
"data.value.name"
:sub-label=
"data.value.username"
/>
</gl-avatar-link>
</
template
>
<
template
#empty
>
{{
s__
(
'
Billing|No users to display.
'
)
}}
</
template
>
<
template
#table-busy
>
<gl-loading-icon
size=
"lg"
color=
"dark"
class=
"gl-mt-5"
/>
</
template
>
</gl-table>
<gl-pagination
v-if=
"currentPage"
v-model=
"currentPage"
:per-page=
"perPageFormatted"
:total-items=
"totalFormatted"
align=
"center"
class=
"gl-mt-5"
/>
</div>
</template>
ee/app/assets/javascripts/billings/components/subscription_table.vue
View file @
831dadd8
...
...
@@ -29,7 +29,13 @@ export default {
},
},
computed
:
{
...
mapState
(
'
subscription
'
,
[
'
isLoading
'
,
'
hasError
'
,
'
plan
'
,
'
tables
'
,
'
endpoint
'
]),
...
mapState
(
'
subscription
'
,
[
'
isLoadingSubscription
'
,
'
hasErrorSubscription
'
,
'
plan
'
,
'
tables
'
,
'
endpoint
'
,
]),
...
mapGetters
(
'
subscription
'
,
[
'
isFreePlan
'
]),
subscriptionHeader
()
{
const
planName
=
this
.
isFreePlan
?
s__
(
'
SubscriptionTable|Free
'
)
:
escape
(
this
.
plan
.
name
);
...
...
@@ -85,7 +91,7 @@ export default {
<
template
>
<div>
<div
v-if=
"!isLoading
&& !hasError
"
v-if=
"!isLoading
Subscription && !hasErrorSubscription
"
class=
"card gl-mt-3 subscription-table js-subscription-table"
>
<div
class=
"js-subscription-header card-header"
>
...
...
@@ -115,7 +121,7 @@ export default {
</div>
<gl-loading-icon
v-else-if=
"isLoading
&& !hasError
"
v-else-if=
"isLoading
Subscription && !hasErrorSubscription
"
:label=
"s__('SubscriptionTable|Loading subscriptions')"
size=
"lg"
class=
"gl-mt-3 gl-mb-3"
...
...
ee/app/assets/javascripts/billings/stores/modules/subscription/actions.js
View file @
831dadd8
import
API
from
'
ee/api
'
;
import
ApiEe
from
'
ee/api
'
;
import
Api
from
'
~/api
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
deprecatedCreateFlash
as
createFlash
}
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
{
s
__
}
from
'
~/locale
'
;
/**
* SUBSCRIPTION TABLE
*/
export
const
setNamespaceId
=
({
commit
},
namespaceId
)
=>
{
commit
(
types
.
SET_NAMESPACE_ID
,
namespaceId
);
};
/**
* Subscription Table
*/
export
const
fetchSubscription
=
({
dispatch
,
state
})
=>
{
dispatch
(
'
requestSubscription
'
);
return
A
PI
.
userSubscription
(
state
.
namespaceId
)
return
A
piEe
.
userSubscription
(
state
.
namespaceId
)
.
then
(({
data
})
=>
dispatch
(
'
receiveSubscriptionSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
receiveSubscriptionError
'
));
};
...
...
@@ -24,6 +25,32 @@ export const receiveSubscriptionSuccess = ({ commit }, response) =>
commit
(
types
.
RECEIVE_SUBSCRIPTION_SUCCESS
,
response
);
export
const
receiveSubscriptionError
=
({
commit
})
=>
{
createFlash
(
__
(
'
An error occurred while loading the subscription details.
'
));
createFlash
({
message
:
s__
(
'
SubscriptionTable|An error occurred while loading the subscription details.
'
),
});
commit
(
types
.
RECEIVE_SUBSCRIPTION_ERROR
);
};
/**
* Billable Members
*/
export
const
fetchHasBillableGroupMembers
=
({
dispatch
,
state
})
=>
{
dispatch
(
'
requestHasBillableGroupMembers
'
);
return
Api
.
fetchBillableGroupMembersList
(
state
.
namespaceId
,
{
per_page
:
1
,
page
:
1
})
.
then
(
data
=>
dispatch
(
'
receiveHasBillableGroupMembersSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
receiveHasBillableGroupMembersError
'
));
};
export
const
requestHasBillableGroupMembers
=
({
commit
})
=>
commit
(
types
.
REQUEST_HAS_BILLABLE_MEMBERS
);
export
const
receiveHasBillableGroupMembersSuccess
=
({
commit
},
response
)
=>
commit
(
types
.
RECEIVE_HAS_BILLABLE_MEMBERS_SUCCESS
,
response
);
export
const
receiveHasBillableGroupMembersError
=
({
commit
})
=>
{
createFlash
({
message
:
s__
(
'
SubscriptionTable|An error occurred while loading billable members list
'
),
});
commit
(
types
.
RECEIVE_HAS_BILLABLE_MEMBERS_ERROR
);
};
ee/app/assets/javascripts/billings/stores/modules/subscription/mutation_types.js
View file @
831dadd8
export
const
SET_NAMESPACE_ID
=
'
SET_NAMESPACE_ID
'
;
export
const
REQUEST_SUBSCRIPTION
=
'
REQUEST_SUBSCRIPTION
'
;
export
const
RECEIVE_SUBSCRIPTION_SUCCESS
=
'
RECEIVE_SUBSCRIPTION_SUCCESS
'
;
export
const
RECEIVE_SUBSCRIPTION_ERROR
=
'
RECEIVE_SUBSCRIPTION_ERROR
'
;
export
const
REQUEST_HAS_BILLABLE_MEMBERS
=
'
REQUEST_HAS_BILLABLE_MEMBERS
'
;
export
const
RECEIVE_HAS_BILLABLE_MEMBERS_SUCCESS
=
'
RECEIVE_HAS_BILLABLE_MEMBERS_SUCCESS
'
;
export
const
RECEIVE_HAS_BILLABLE_MEMBERS_ERROR
=
'
RECEIVE_HAS_BILLABLE_MEMBERS_ERROR
'
;
ee/app/assets/javascripts/billings/stores/modules/subscription/mutations.js
View file @
831dadd8
import
Vue
from
'
vue
'
;
import
{
TABLE_TYPE_DEFAULT
,
TABLE_TYPE_FREE
,
TABLE_TYPE_TRIAL
}
from
'
../../../constants
'
;
import
{
parseInt
}
from
'
lodash
'
;
import
{
TABLE_TYPE_DEFAULT
,
TABLE_TYPE_FREE
,
TABLE_TYPE_TRIAL
,
HEADER_TOTAL_ENTRIES
,
}
from
'
ee/billings/constants
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
...
...
@@ -9,8 +15,8 @@ export default {
},
[
types
.
REQUEST_SUBSCRIPTION
](
state
)
{
state
.
isLoading
=
true
;
state
.
hasError
=
false
;
state
.
isLoading
Subscription
=
true
;
state
.
hasError
Subscription
=
false
;
},
[
types
.
RECEIVE_SUBSCRIPTION_SUCCESS
](
state
,
payload
)
{
...
...
@@ -36,11 +42,29 @@ export default {
});
});
state
.
isLoading
=
false
;
state
.
isLoading
Subscription
=
false
;
},
[
types
.
RECEIVE_SUBSCRIPTION_ERROR
](
state
)
{
state
.
isLoading
=
false
;
state
.
hasError
=
true
;
state
.
isLoadingSubscription
=
false
;
state
.
hasErrorSubscription
=
true
;
},
[
types
.
REQUEST_HAS_BILLABLE_MEMBERS
](
state
)
{
state
.
isLoadingHasBillableMembers
=
true
;
state
.
hasErrorHasBillableMembers
=
false
;
},
[
types
.
RECEIVE_HAS_BILLABLE_MEMBERS_SUCCESS
](
state
,
payload
)
{
const
{
headers
}
=
payload
;
const
hasBillableGroupMembers
=
parseInt
(
headers
[
HEADER_TOTAL_ENTRIES
],
10
)
>
0
;
state
.
hasBillableGroupMembers
=
hasBillableGroupMembers
;
state
.
isLoadingHasBillableMembers
=
false
;
},
[
types
.
RECEIVE_HAS_BILLABLE_MEMBERS_ERROR
](
state
)
{
state
.
isLoadinggHasBillableMembers
=
false
;
state
.
hasErrorHasBillableMembers
=
true
;
},
};
ee/app/assets/javascripts/billings/stores/modules/subscription/state.js
View file @
831dadd8
import
{
s__
}
from
'
~/locale
'
;
export
default
()
=>
({
isLoading
:
false
,
hasError
:
false
,
isLoadingSubscription
:
false
,
hasErrorSubscription
:
false
,
isLoadingBillableMembers
:
false
,
hasErrorBillableMembers
:
false
,
namespaceId
:
null
,
plan
:
{
code
:
null
,
...
...
@@ -167,4 +169,5 @@ export default () => ({
],
},
},
hasBillableGroupMembers
:
false
,
});
ee/app/assets/stylesheets/pages/billings.scss
View file @
831dadd8
...
...
@@ -107,3 +107,37 @@
}
}
}
.seats-table
{
/*
* TODO
* Remove these overrides when the ones inside of
* app/assets/stylesheets/framework/tables.scss
* will be removed.
*
* Read more about this in the comments of that file.
*/
&
.table.gl-table
{
@include
gl-mb-0
;
@include
gl-text-gray-500
;
tr
{
th
,
td
{
@include
gl-display-flex
;
@include
gl-border-b-solid
;
@include
gl-border-b-1
;
@include
gl-p-5
;
}
th
{
@include
gl-border-gray-200
;
@include
gl-bg-transparent
;
}
td
{
@include
gl-border-gray-100
;
}
}
}
}
ee/app/controllers/groups/billings_controller.rb
View file @
831dadd8
...
...
@@ -4,6 +4,10 @@ class Groups::BillingsController < Groups::ApplicationController
before_action
:authorize_admin_group!
before_action
:verify_namespace_plan_check_enabled
before_action
only:
[
:index
]
do
push_frontend_feature_flag
(
:api_billable_member_list
)
end
layout
'group_settings'
feature_category
:purchase
...
...
ee/app/helpers/billing_plans_helper.rb
View file @
831dadd8
...
...
@@ -73,7 +73,7 @@ module BillingPlansHelper
def
seats_data_last_update_info
last_enqueue_time
=
UpdateMaxSeatsUsedForGitlabComSubscriptionsWorker
.
last_enqueue_time
&
.
utc
return
_
(
"Seats usage data as of %{last_enqueue_time}"
%
{
last_enqueue_time:
last_enqueue_time
})
if
last_enqueue_time
return
_
(
"Seats usage data as of %{last_enqueue_time}
(Updated daily)
"
%
{
last_enqueue_time:
last_enqueue_time
})
if
last_enqueue_time
_
(
'Seats usage data is updated every day at 12:00pm UTC'
)
end
...
...
ee/app/views/shared/billings/_billing_plans.html.haml
View file @
831dadd8
...
...
@@ -10,7 +10,7 @@
=
render
'shared/billings/billing_plan'
,
namespace:
namespace
,
plan:
plan
,
current_plan:
current_plan
-
if
namespace
.
actual_plan
&
.
paid?
.center
.center
.gl-mb-7
&=
s_
(
'BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}.'
).
html_safe
%
{
support_link_start:
support_link_start
,
support_link_end:
support_link_end
}
%p
=
seats_data_last_update_info
ee/spec/frontend/billings/components/__snapshots__/app_spec.js.snap
deleted
100644 → 0
View file @
56daee5b
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SubscriptionApp component on creation matches the snapshot 1`] = `
<subscription-table-stub
customerportalurl="https://customers.gitlab.com/subscriptions"
namespacename="bronze"
planupgradehref="/url"
/>
`;
ee/spec/frontend/billings/components/app_spec.js
View file @
831dadd8
...
...
@@ -2,6 +2,9 @@ import { shallowMount } from '@vue/test-utils';
import
createStore
from
'
ee/billings/stores
'
;
import
SubscriptionApp
from
'
ee/billings/components/app.vue
'
;
import
SubscriptionTable
from
'
ee/billings/components/subscription_table.vue
'
;
import
SubscriptionSeats
from
'
ee/billings/components/subscription_seats.vue
'
;
import
*
as
types
from
'
ee/billings/stores/modules/subscription/mutation_types
'
;
import
{
mockDataSeats
}
from
'
../mock_data
'
;
describe
(
'
SubscriptionApp component
'
,
()
=>
{
let
store
;
...
...
@@ -14,13 +17,16 @@ describe('SubscriptionApp component', () => {
customerPortalUrl
:
'
https://customers.gitlab.com/subscriptions
'
,
};
const
factory
=
(
props
=
appProps
)
=>
{
const
factory
=
(
props
=
appProps
,
isFeatureEnabledApiBillableMemberList
=
true
)
=>
{
store
=
createStore
();
jest
.
spyOn
(
store
,
'
dispatch
'
).
mockImplementation
();
wrapper
=
shallowMount
(
SubscriptionApp
,
{
store
,
propsData
:
{
...
props
},
provide
:
{
glFeatures
:
{
apiBillableMemberList
:
isFeatureEnabledApiBillableMemberList
},
},
});
};
...
...
@@ -31,6 +37,8 @@ describe('SubscriptionApp component', () => {
expect
(
componentWrapper
.
props
()).
toEqual
(
expect
.
objectContaining
(
props
));
};
const
findSubscriptionSeatsTable
=
()
=>
wrapper
.
find
(
SubscriptionSeats
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
...
...
@@ -38,18 +46,16 @@ describe('SubscriptionApp component', () => {
describe
(
'
on creation
'
,
()
=>
{
beforeEach
(()
=>
{
factory
();
store
.
commit
(
`subscription/
${
types
.
RECEIVE_HAS_BILLABLE_MEMBERS_SUCCESS
}
`
,
mockDataSeats
);
});
it
(
'
dispatches
the setNamespaceId on moun
ted
'
,
()
=>
{
it
(
'
dispatches
expected actions on crea
ted
'
,
()
=>
{
expect
(
store
.
dispatch
.
mock
.
calls
).
toEqual
([
[
'
subscription/setNamespaceId
'
,
appProps
.
namespaceId
],
[
'
subscription/setNamespaceId
'
,
'
42
'
],
[
'
subscription/fetchHasBillableGroupMembers
'
,
undefined
],
]);
});
it
(
'
matches the snapshot
'
,
()
=>
{
expect
(
wrapper
.
element
).
toMatchSnapshot
();
});
it
(
'
passes the correct props to the subscriptions table
'
,
()
=>
{
expectComponentWithProps
(
SubscriptionTable
,
{
namespaceName
:
appProps
.
namespaceName
,
...
...
@@ -57,5 +63,43 @@ describe('SubscriptionApp component', () => {
customerPortalUrl
:
appProps
.
customerPortalUrl
,
});
});
it
(
'
passes the correct props to the subscriptions seats component
'
,
()
=>
{
expectComponentWithProps
(
SubscriptionSeats
,
{
namespaceName
:
appProps
.
namespaceName
,
namespaceId
:
appProps
.
namespaceId
,
});
});
});
describe
(
'
when there are no billable members
'
,
()
=>
{
beforeEach
(()
=>
{
factory
();
store
.
commit
(
`subscription/
${
types
.
RECEIVE_HAS_BILLABLE_MEMBERS_SUCCESS
}
`
,
{
data
:
[],
headers
:
{},
});
});
it
(
'
does not render the subscription seats table
'
,
()
=>
{
expect
(
findSubscriptionSeatsTable
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when feature flag is disabled
'
,
()
=>
{
beforeEach
(()
=>
{
factory
(
appProps
,
false
);
});
it
(
'
does not dispatch fetchBillableGroupMembers action on created
'
,
()
=>
{
expect
(
store
.
dispatch
.
mock
.
calls
).
not
.
toContainEqual
([
'
subscription/fetchBillableGroupMembers
'
,
undefined
,
]);
});
it
(
'
does not render the subscription seats table
'
,
()
=>
{
expect
(
findSubscriptionSeatsTable
().
exists
()).
toBe
(
false
);
});
});
});
ee/spec/frontend/billings/components/subscription_seats_spec.js
0 → 100644
View file @
831dadd8
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
{
GlPagination
}
from
'
@gitlab/ui
'
;
import
Vuex
from
'
vuex
'
;
import
SubscriptionSeats
from
'
ee/billings/components/subscription_seats.vue
'
;
import
{
mockDataSeats
,
seatsTableItems
}
from
'
../mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
const
actionSpies
=
{
setNamespaceId
:
jest
.
fn
(),
fetchBillableMembersList
:
jest
.
fn
(),
};
const
tableProps
=
{
namespaceName
:
'
Test Group Name
'
,
namespaceId
:
'
1000
'
,
};
const
fakeStore
=
({
initialState
})
=>
new
Vuex
.
Store
({
modules
:
{
seats
:
{
namespaced
:
true
,
actions
:
actionSpies
,
state
:
{
isLoading
:
false
,
hasError
:
false
,
...
initialState
,
},
},
},
});
const
createComponent
=
({
props
=
{},
options
=
{},
initialState
=
{}
}
=
{})
=>
{
return
shallowMount
(
SubscriptionSeats
,
{
propsData
:
{
...
tableProps
,
...
props
},
store
:
fakeStore
({
initialState
}),
localVue
,
...
options
,
stubs
:
{
GlTable
:
{
template
:
'
<div></div>
'
,
props
:
{
items
:
Array
,
fields
:
Array
,
busy
:
Boolean
}
},
},
});
};
describe
(
'
Subscription Seats
'
,
()
=>
{
let
wrapper
;
const
findTable
=
()
=>
wrapper
.
find
(
'
[data-testid="seats-table"]
'
);
const
findHeading
=
()
=>
wrapper
.
find
(
'
[data-testid="heading"]
'
);
const
findPagination
=
()
=>
wrapper
.
find
(
GlPagination
);
beforeEach
(()
=>
{
wrapper
=
createComponent
({
initialState
:
{
namespaceId
:
null
,
members
:
[...
mockDataSeats
.
data
],
total
:
300
,
page
:
1
,
perPage
:
5
,
},
});
});
it
(
'
correct actions are called on create
'
,
()
=>
{
expect
(
actionSpies
.
setNamespaceId
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
tableProps
.
namespaceId
,
);
expect
(
actionSpies
.
fetchBillableMembersList
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
1
);
});
describe
(
'
heading text
'
,
()
=>
{
it
(
'
contains the group name and total seats number
'
,
()
=>
{
expect
(
findHeading
().
text
()).
toMatch
(
tableProps
.
namespaceName
);
expect
(
findHeading
().
text
()).
toMatch
(
'
300
'
);
});
});
describe
(
'
table
'
,
()
=>
{
it
(
'
is rendered and passed correct values
'
,
()
=>
{
expect
(
findTable
().
props
(
'
fields
'
)).
toEqual
([
'
user
'
]);
expect
(
findTable
().
props
(
'
busy
'
)).
toBe
(
false
);
expect
(
findTable
().
props
(
'
items
'
)).
toEqual
(
seatsTableItems
);
});
});
describe
(
'
pagination
'
,
()
=>
{
it
(
'
is rendered and passed correct values
'
,
()
=>
{
expect
(
findPagination
().
vm
.
value
).
toBe
(
1
);
expect
(
findPagination
().
props
(
'
perPage
'
)).
toBe
(
5
);
expect
(
findPagination
().
props
(
'
totalItems
'
)).
toBe
(
300
);
});
it
.
each
([
null
,
NaN
,
undefined
,
'
a string
'
,
false
])(
'
will not render given %s for currentPage
'
,
value
=>
{
wrapper
=
createComponent
({
initialState
:
{
namespaceId
:
null
,
members
:
[...
mockDataSeats
.
data
],
total
:
300
,
page
:
value
,
perPage
:
5
,
},
});
expect
(
findPagination
().
exists
()).
toBe
(
false
);
},
);
});
});
ee/spec/frontend/billings/components/subscription_table_spec.js
View file @
831dadd8
...
...
@@ -41,7 +41,7 @@ describe('SubscriptionTable component', () => {
},
});
Object
.
assign
(
store
.
state
.
subscription
,
{
isLoading
:
true
});
Object
.
assign
(
store
.
state
.
subscription
,
{
isLoading
Subscription
:
true
});
return
wrapper
.
vm
.
$nextTick
();
});
...
...
@@ -63,7 +63,7 @@ describe('SubscriptionTable component', () => {
beforeEach
(()
=>
{
factory
({
propsData
:
{
namespaceName
:
TEST_NAMESPACE_NAME
}
});
store
.
state
.
subscription
.
isLoading
=
false
;
store
.
state
.
subscription
.
isLoading
Subscription
=
false
;
store
.
commit
(
`subscription/
${
types
.
RECEIVE_SUBSCRIPTION_SUCCESS
}
`
,
mockDataSubscription
.
gold
);
return
wrapper
.
vm
.
$nextTick
();
...
...
@@ -104,7 +104,7 @@ describe('SubscriptionTable component', () => {
});
Object
.
assign
(
store
.
state
.
subscription
,
{
isLoading
:
false
,
isLoading
Subscription
:
false
,
isFreePlan
,
plan
:
{
code
:
planName
,
...
...
ee/spec/frontend/billings/mock_data.js
View file @
831dadd8
...
...
@@ -68,31 +68,22 @@ export const mockDataSubscription = {
export
const
mockDataSeats
=
{
data
:
[
{
id
:
1
,
name
:
'
Administrator
'
,
username
:
'
root
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://192.168.1.209:3001/root
'
,
avatar_url
:
'
path/to/img
'
,
web_url
:
'
path/to/user
'
,
},
{
id
:
3
,
name
:
'
Agustin Walker
'
,
username
:
'
lester.orn
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/772352aed294c4b3e6f236b0624764b6?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://192.168.1.209:3001/lester.orn
'
,
avatar_url
:
'
path/to/img
'
,
web_url
:
'
path/to/user
'
,
},
{
id
:
5
,
name
:
'
Joella Miller
'
,
username
:
'
era
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/8b306a0c173657865f6a5a6c7120b408?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://192.168.1.209:3001/era
'
,
avatar_url
:
'
path/to/img
'
,
web_url
:
'
path/to/user
'
,
},
],
headers
:
{
...
...
@@ -101,3 +92,30 @@ export const mockDataSeats = {
[
HEADER_ITEMS_PER_PAGE
]:
'
1
'
,
},
};
export
const
seatsTableItems
=
[
{
user
:
{
name
:
'
Administrator
'
,
username
:
'
@root
'
,
avatar_url
:
'
path/to/img
'
,
web_url
:
'
path/to/user
'
,
},
},
{
user
:
{
name
:
'
Agustin Walker
'
,
username
:
'
@lester.orn
'
,
avatar_url
:
'
path/to/img
'
,
web_url
:
'
path/to/user
'
,
},
},
{
user
:
{
name
:
'
Joella Miller
'
,
username
:
'
@era
'
,
avatar_url
:
'
path/to/img
'
,
web_url
:
'
path/to/user
'
,
},
},
];
ee/spec/frontend/billings/store/modules/subscription/mutations_spec.js
View file @
831dadd8
...
...
@@ -29,12 +29,12 @@ describe('EE billings subscription module mutations', () => {
mutations
[
types
.
REQUEST_SUBSCRIPTION
](
state
);
});
it
(
'
sets isLoading to true
'
,
()
=>
{
expect
(
state
.
isLoading
).
toBeTruthy
();
it
(
'
sets isLoading
Subscription
to true
'
,
()
=>
{
expect
(
state
.
isLoading
Subscription
).
toBeTruthy
();
});
it
(
'
sets hasError to false
'
,
()
=>
{
expect
(
state
.
hasError
).
toBeFalsy
();
it
(
'
sets hasError
Subscription
to false
'
,
()
=>
{
expect
(
state
.
hasError
Subscription
).
toBeFalsy
();
});
});
...
...
@@ -57,12 +57,12 @@ describe('EE billings subscription module mutations', () => {
${
'
with Gold trial
'
}
|
${
mockDataSubscription
.
trial
}
|
${
TABLE_TYPE_TRIAL
}
`
(
'
$desc
'
,
({
subscription
,
tableKey
})
=>
{
beforeEach
(()
=>
{
state
.
isLoading
=
true
;
state
.
isLoading
Subscription
=
true
;
mutations
[
types
.
RECEIVE_SUBSCRIPTION_SUCCESS
](
state
,
subscription
);
});
it
(
'
sets isLoading to false
'
,
()
=>
{
expect
(
state
.
isLoading
).
toBeFalsy
();
it
(
'
sets isLoading
Subscription
to false
'
,
()
=>
{
expect
(
state
.
isLoading
Subscription
).
toBeFalsy
();
});
it
(
'
sets plan
'
,
()
=>
{
...
...
@@ -82,12 +82,12 @@ describe('EE billings subscription module mutations', () => {
mutations
[
types
.
RECEIVE_SUBSCRIPTION_ERROR
](
state
);
});
it
(
'
sets isLoading to false
'
,
()
=>
{
expect
(
state
.
isLoading
).
toBeFalsy
();
it
(
'
sets isLoading
Subscription
to false
'
,
()
=>
{
expect
(
state
.
isLoading
Subscription
).
toBeFalsy
();
});
it
(
'
sets hasError to true
'
,
()
=>
{
expect
(
state
.
hasError
).
toBeTruthy
();
it
(
'
sets hasError
Subscription
to true
'
,
()
=>
{
expect
(
state
.
hasError
Subscription
).
toBeTruthy
();
});
});
});
locale/gitlab.pot
View file @
831dadd8
...
...
@@ -3024,9 +3024,6 @@ msgstr ""
msgid "An error occurred while loading the pipelines jobs."
msgstr ""
msgid "An error occurred while loading the subscription details."
msgstr ""
msgid "An error occurred while making the request."
msgstr ""
...
...
@@ -4202,6 +4199,15 @@ msgstr ""
msgid "Billing|An error occurred while loading billable members list"
msgstr ""
msgid "Billing|No users to display."
msgstr ""
msgid "Billing|Updated live"
msgstr ""
msgid "Billing|Users occupying seats in %{namespaceName} Group (%{total})"
msgstr ""
msgid "Bitbucket Server Import"
msgstr ""
...
...
@@ -23182,7 +23188,7 @@ msgstr ""
msgid "Seat Link is disabled, and cannot be configured through this form."
msgstr ""
msgid "Seats usage data as of %{last_enqueue_time}"
msgid "Seats usage data as of %{last_enqueue_time}
(Updated daily)
"
msgstr ""
msgid "Seats usage data is updated every day at 12:00pm UTC"
...
...
@@ -25366,6 +25372,12 @@ msgstr ""
msgid "Subscription successfully deleted."
msgstr ""
msgid "SubscriptionTable|An error occurred while loading billable members list"
msgstr ""
msgid "SubscriptionTable|An error occurred while loading the subscription details."
msgstr ""
msgid "SubscriptionTable|Billing"
msgstr ""
...
...
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