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
ca76d547
Commit
ca76d547
authored
Sep 23, 2020
by
Andrew Fontaine
Committed by
Jose Ivan Vargas
Sep 23, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrate to GlTabs for Feature Flags Page
This uses the tabs from GitLab UI!
parent
a5c32b12
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
444 additions
and
184 deletions
+444
-184
ee/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue
...eature_flags/components/configure_feature_flags_modal.vue
+4
-6
ee/app/assets/javascripts/feature_flags/components/feature_flags.vue
...ts/javascripts/feature_flags/components/feature_flags.vue
+107
-136
ee/app/assets/javascripts/feature_flags/components/feature_flags_tab.vue
...avascripts/feature_flags/components/feature_flags_tab.vue
+108
-0
ee/app/assets/javascripts/feature_flags/index.js
ee/app/assets/javascripts/feature_flags/index.js
+2
-2
ee/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
...re_flags/components/configure_feature_flags_modal_spec.js
+13
-9
ee/spec/frontend/feature_flags/components/feature_flags_spec.js
...c/frontend/feature_flags/components/feature_flags_spec.js
+32
-27
ee/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
...ontend/feature_flags/components/feature_flags_tab_spec.js
+168
-0
locale/gitlab.pot
locale/gitlab.pot
+10
-4
No files found.
ee/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue
View file @
ca76d547
...
...
@@ -42,10 +42,6 @@ export default {
},
props
:
{
helpPath
:
{
type
:
String
,
required
:
true
,
},
helpClientLibrariesPath
:
{
type
:
String
,
required
:
true
,
...
...
@@ -80,7 +76,7 @@ export default {
required
:
true
,
},
},
inject
:
[
'
projectName
'
],
inject
:
[
'
projectName
'
,
'
featureFlagsHelpPagePath
'
],
data
()
{
return
{
enteredProjectName
:
''
,
...
...
@@ -149,7 +145,9 @@ export default {
</gl-link>
</
template
>
<
template
#docsLink=
"{ content }"
>
<gl-link
:href=
"helpPath"
target=
"_blank"
data-testid=
"help-link"
>
{{
content
}}
</gl-link>
<gl-link
:href=
"featureFlagsHelpPagePath"
target=
"_blank"
data-testid=
"help-link"
>
{{
content
}}
</gl-link>
</
template
>
</gl-sprintf>
</p>
...
...
ee/app/assets/javascripts/feature_flags/components/feature_flags.vue
View file @
ca76d547
<
script
>
import
{
createNamespacedHelpers
}
from
'
vuex
'
;
import
{
isEmpty
}
from
'
lodash
'
;
import
{
GlAlert
,
GlButton
,
GlEmptyState
,
GlLoadingIcon
,
GlModalDirective
,
GlLink
,
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlModalDirective
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
{
FEATURE_FLAG_SCOPE
,
USER_LIST_SCOPE
}
from
'
../constants
'
;
import
FeatureFlagsTab
from
'
./feature_flags_tab.vue
'
;
import
FeatureFlagsTable
from
'
./feature_flags_table.vue
'
;
import
UserListsTable
from
'
./user_lists_table.vue
'
;
import
store
from
'
../store
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
NavigationTabs
from
'
~/vue_shared/components/navigation_tabs.vue
'
;
import
{
s__
}
from
'
~/locale
'
;
import
TablePagination
from
'
~/vue_shared/components/pagination/table_pagination.vue
'
;
import
{
getParameterByName
,
...
...
@@ -26,18 +19,17 @@ import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue';
const
{
mapState
,
mapActions
}
=
createNamespacedHelpers
(
'
index
'
);
const
SCOPES
=
{
FEATURE_FLAG_SCOPE
,
USER_LIST_SCOPE
};
export
default
{
store
,
components
:
{
FeatureFlagsTable
,
UserListsTable
,
NavigationTabs
,
TablePagination
,
GlAlert
,
GlButton
,
GlEmptyState
,
GlLoadingIcon
,
GlLink
,
GlTabs
,
FeatureFlagsTab
,
ConfigureFeatureFlagsModal
,
},
directives
:
{
...
...
@@ -56,14 +48,6 @@ export default {
type
:
String
,
required
:
true
,
},
errorStateSvgPath
:
{
type
:
String
,
required
:
true
,
},
featureFlagsHelpPagePath
:
{
type
:
String
,
required
:
true
,
},
featureFlagsClientLibrariesHelpPagePath
:
{
type
:
String
,
required
:
true
,
...
...
@@ -101,16 +85,14 @@ export default {
},
},
data
()
{
const
scope
=
getParameterByName
(
'
scope
'
)
||
SCOPES
.
FEATURE_FLAG_SCOPE
;
return
{
scope
:
getParameterByName
(
'
scope
'
)
||
this
.
$options
.
scopes
.
featureFlags
,
scope
,
page
:
getParameterByName
(
'
page
'
)
||
'
1
'
,
isUserListAlertDismissed
:
false
,
selectedTab
:
Object
.
values
(
SCOPES
).
indexOf
(
scope
),
};
},
scopes
:
{
[
FEATURE_FLAG_SCOPE
]:
FEATURE_FLAG_SCOPE
,
[
USER_LIST_SCOPE
]:
USER_LIST_SCOPE
,
},
computed
:
{
...
mapState
([
FEATURE_FLAG_SCOPE
,
...
...
@@ -125,31 +107,8 @@ export default {
'
isRotating
'
,
'
hasRotateError
'
,
]),
secondaryButtonClasses
()
{
return
[
'
gl-mb-3
'
,
'
gl-lg-mr-3
'
,
'
gl-lg-mb-0
'
];
},
navigationControlsClasses
()
{
return
[
'
gl-display-flex
'
,
'
gl-flex-direction-column
'
,
'
gl-mt-3
'
,
'
gl-lg-flex-direction-row
'
,
'
gl-lg-flex-fill-1
'
,
'
gl-lg-justify-content-end
'
,
'
gl-lg-mt-0
'
,
];
},
topAreaBaseClasses
()
{
return
[
'
gl-border-1
'
,
'
gl-border-b-gray-100
'
,
'
gl-border-b-solid
'
,
'
gl-flex-wrap
'
,
'
gl-display-flex
'
,
'
gl-flex-direction-column-reverse
'
,
'
gl-lg-align-items-center
'
,
'
gl-lg-flex-direction-row
'
,
];
return
[
'
gl-display-flex
'
,
'
gl-flex-direction-column
'
];
},
canUserRotateToken
()
{
return
this
.
rotateInstanceIdPath
!==
''
;
...
...
@@ -171,29 +130,17 @@ export default {
shouldRenderErrorState
()
{
return
this
.
hasError
&&
!
this
.
isLoading
;
},
tabs
()
{
const
{
scopes
}
=
this
.
$options
;
return
[
{
name
:
__
(
'
Feature Flags
'
),
scope
:
scopes
[
FEATURE_FLAG_SCOPE
],
count
:
this
.
count
[
FEATURE_FLAG_SCOPE
],
isActive
:
this
.
scope
===
scopes
[
FEATURE_FLAG_SCOPE
],
},
{
name
:
__
(
'
Lists
'
),
scope
:
scopes
[
USER_LIST_SCOPE
],
count
:
this
.
count
[
USER_LIST_SCOPE
],
isActive
:
this
.
scope
===
scopes
[
USER_LIST_SCOPE
],
},
];
shouldRenderFeatureFlags
()
{
return
this
.
shouldRenderTable
(
SCOPES
.
FEATURE_FLAG_SCOPE
);
},
shouldRenderUserLists
()
{
return
this
.
shouldRenderTable
(
SCOPES
.
USER_LIST_SCOPE
);
},
hasNewPath
()
{
return
!
isEmpty
(
this
.
newFeatureFlagPath
);
},
emptyStateTitle
()
{
return
s__
(
`FeatureFlags|Get started with feature flags`
);
return
s__
(
'
FeatureFlags|Get started with feature flags
'
);
},
},
created
()
{
...
...
@@ -226,6 +173,12 @@ export default {
page
:
'
1
'
,
});
},
onFeatureFlagsTab
()
{
this
.
onChangeTab
(
SCOPES
.
FEATURE_FLAG_SCOPE
);
},
onUserListsTab
()
{
this
.
onChangeTab
(
SCOPES
.
USER_LIST_SCOPE
);
},
onChangePage
(
page
)
{
this
.
updateFeatureFlagOptions
({
scope
:
this
.
scope
,
...
...
@@ -243,7 +196,7 @@ export default {
historyPushState
(
buildUrlWithCurrentLocation
(
`?
${
queryString
}
`
));
this
.
setFeatureFlagsOptions
(
parameters
);
if
(
this
.
scope
===
this
.
$options
.
scopes
.
featureFlags
)
{
if
(
this
.
scope
===
SCOPES
.
FEATURE_FLAG_SCOPE
)
{
this
.
fetchFeatureFlags
();
}
else
{
this
.
fetchUserLists
();
...
...
@@ -267,7 +220,6 @@ export default {
<div>
<configure-feature-flags-modal
v-if=
"canUserConfigure"
:help-path=
"featureFlagsHelpPagePath"
:help-client-libraries-path=
"featureFlagsClientLibrariesHelpPagePath"
:help-client-example-path=
"featureFlagsClientExampleHelpPagePath"
:api-url=
"unleashApiUrl"
...
...
@@ -279,13 +231,7 @@ export default {
@
token=
"rotateInstanceId()"
/>
<div
:class=
"topAreaBaseClasses"
>
<navigation-tabs
:tabs=
"tabs"
scope=
"featureflags"
class=
"gl-border-none!"
@
onChangeTab=
"onChangeTab"
/>
<div
:class=
"navigationControlsClasses"
>
<div
class=
"gl-display-flex gl-flex-direction-column gl-display-md-none!"
>
<gl-button
v-if=
"canUserConfigure"
v-gl-modal=
"'configure-feature-flags'"
...
...
@@ -293,7 +239,7 @@ export default {
category=
"secondary"
data-qa-selector=
"configure_feature_flags_button"
data-testid=
"ff-configure-button"
:class=
"secondaryButtonClasses
"
class=
"gl-mb-3
"
>
{{
s__
(
'
FeatureFlags|Configure
'
)
}}
</gl-button>
...
...
@@ -303,10 +249,10 @@ export default {
:href=
"newUserListPath"
variant=
"success"
category=
"secondary"
:class=
"secondaryButtonClasses
"
class=
"gl-mb-3
"
data-testid=
"ff-new-list-button"
>
{{
s__
(
'
FeatureFlags|New list
'
)
}}
{{
s__
(
'
FeatureFlags|New
user
list
'
)
}}
</gl-button>
<gl-button
...
...
@@ -318,62 +264,87 @@ export default {
{{
s__
(
'
FeatureFlags|New feature flag
'
)
}}
</gl-button>
</div>
</div>
<gl-alert
v-for=
"(message, index) in alerts"
:key=
"index"
data-testid=
"serverErrors"
variant=
"danger"
@
dismiss=
"clearAlert(index)"
>
{{
message
}}
</gl-alert>
<gl-loading-icon
v-if=
"isLoading"
:label=
"s__('FeatureFlags|Loading feature flags')"
size=
"md"
class=
"js-loading-state prepend-top-20"
/>
<gl-empty-state
v-else-if=
"shouldRenderErrorState"
:title=
"s__(`FeatureFlags|There was an error fetching the feature flags.`)"
:description=
"s__(`FeatureFlags|Try again in a few moments or contact your support team.`)"
:svg-path=
"errorStateSvgPath"
/>
<gl-empty-state
v-else-if=
"shouldShowEmptyState"
class=
"js-feature-flags-empty-state"
:title=
"emptyStateTitle"
:svg-path=
"errorStateSvgPath"
>
<template
#description
>
{{
s__
(
'
FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.
'
,
)
}}
<gl-link
:href=
"featureFlagsHelpPagePath"
target=
"_blank"
rel=
"noopener noreferrer"
>
{{
s__
(
'
FeatureFlags|More information
'
)
}}
</gl-link>
</
template
>
</gl-empty-state>
<feature-flags-table
v-else-if=
"shouldRenderTable($options.scopes.featureFlags)"
:csrf-token=
"csrfToken"
:feature-flags=
"featureFlags"
@
toggle-flag=
"toggleFeatureFlag"
/>
<gl-tabs
v-model=
"selectedTab"
class=
"gl-align-items-center gl-w-full"
>
<feature-flags-tab
:title=
"s__('FeatureFlags|Feature Flags')"
:count=
"count.featureFlags"
:alerts=
"alerts"
:is-loading=
"isLoading"
:loading-label=
"s__('FeatureFlags|Loading feature flags')"
:error-state=
"shouldRenderErrorState"
:error-title=
"s__(`FeatureFlags|There was an error fetching the feature flags.`)"
:empty-state=
"shouldShowEmptyState"
:empty-title=
"emptyStateTitle"
data-testid=
"feature-flags-tab"
@
dismissAlert=
"clearAlert"
@
changeTab=
"onFeatureFlagsTab"
>
<feature-flags-table
v-if=
"shouldRenderFeatureFlags"
:csrf-token=
"csrfToken"
:feature-flags=
"featureFlags"
@
toggle-flag=
"toggleFeatureFlag"
/>
</feature-flags-tab>
<feature-flags-tab
:title=
"s__('FeatureFlags|User Lists')"
:count=
"count.userLists"
:alerts=
"alerts"
:is-loading=
"isLoading"
:loading-label=
"s__('FeatureFlags|Loading user lists')"
:error-state=
"shouldRenderErrorState"
:error-title=
"s__(`FeatureFlags|There was an error fetching the user lists.`)"
:empty-state=
"shouldShowEmptyState"
:empty-title=
"emptyStateTitle"
data-testid=
"user-lists-tab"
@
dismissAlert=
"clearAlert"
@
changeTab=
"onUserListsTab"
>
<user-lists-table
v-if=
"shouldRenderUserLists"
:user-lists=
"userLists"
@
delete=
"deleteUserList"
/>
</feature-flags-tab>
<template
#tabs-end
>
<div
class=
"gl-display-none gl-display-md-flex gl-align-items-center gl-flex-fill-1 gl-justify-content-end"
>
<gl-button
v-if=
"canUserConfigure"
v-gl-modal=
"'configure-feature-flags'"
variant=
"info"
category=
"secondary"
data-qa-selector=
"configure_feature_flags_button"
data-testid=
"ff-configure-button"
class=
"gl-mb-0 gl-mr-4"
>
{{
s__
(
'
FeatureFlags|Configure
'
)
}}
</gl-button>
<user-lists-table
v-else-if=
"shouldRenderTable($options.scopes.userLists)"
:user-lists=
"userLists"
@
delete=
"deleteUserList"
/>
<gl-button
v-if=
"newUserListPath"
:href=
"newUserListPath"
variant=
"success"
category=
"secondary"
class=
"gl-mb-0 gl-mr-4"
data-testid=
"ff-new-list-button"
>
{{
s__
(
'
FeatureFlags|New user list
'
)
}}
</gl-button>
<gl-button
v-if=
"hasNewPath"
:href=
"newFeatureFlagPath"
variant=
"success"
data-testid=
"ff-new-button"
>
{{
s__
(
'
FeatureFlags|New feature flag
'
)
}}
</gl-button>
</div>
</
template
>
</gl-tabs>
</div>
<table-pagination
v-if=
"shouldRenderPagination"
:change=
"onChangePage"
...
...
ee/app/assets/javascripts/feature_flags/components/feature_flags_tab.vue
0 → 100644
View file @
ca76d547
<
script
>
import
{
GlAlert
,
GlBadge
,
GlEmptyState
,
GlLink
,
GlLoadingIcon
,
GlTab
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlAlert
,
GlBadge
,
GlEmptyState
,
GlLink
,
GlLoadingIcon
,
GlTab
},
props
:
{
title
:
{
required
:
true
,
type
:
String
,
},
count
:
{
required
:
false
,
type
:
Number
,
default
:
null
,
},
alerts
:
{
required
:
true
,
type
:
Array
,
},
isLoading
:
{
required
:
true
,
type
:
Boolean
,
},
loadingLabel
:
{
required
:
true
,
type
:
String
,
},
errorState
:
{
required
:
true
,
type
:
Boolean
,
},
errorTitle
:
{
required
:
true
,
type
:
String
,
},
emptyState
:
{
required
:
true
,
type
:
Boolean
,
},
emptyTitle
:
{
required
:
true
,
type
:
String
,
},
},
inject
:
[
'
errorStateSvgPath
'
,
'
featureFlagsHelpPagePath
'
],
computed
:
{
itemCount
()
{
return
this
.
count
??
0
;
},
},
methods
:
{
clearAlert
(
index
)
{
this
.
$emit
(
'
dismissAlert
'
,
index
);
},
onClick
(
event
)
{
return
this
.
$emit
(
'
changeTab
'
,
event
);
},
},
};
</
script
>
<
template
>
<gl-tab
@
click=
"onClick"
>
<template
#title
>
<span
data-testid=
"feature-flags-tab-title"
>
{{
title
}}
</span>
<gl-badge
size=
"sm"
class=
"gl-tab-counter-badge"
>
{{
itemCount
}}
</gl-badge>
</
template
>
<
template
>
<gl-alert
v-for=
"(message, index) in alerts"
:key=
"index"
data-testid=
"serverErrors"
variant=
"danger"
@
dismiss=
"clearAlert(index)"
>
{{
message
}}
</gl-alert>
<gl-loading-icon
v-if=
"isLoading"
:label=
"loadingLabel"
size=
"md"
class=
"gl-mt-4"
/>
<gl-empty-state
v-else-if=
"errorState"
:title=
"errorTitle"
:description=
"s__(`FeatureFlags|Try again in a few moments or contact your support team.`)"
:svg-path=
"errorStateSvgPath"
data-testid=
"error-state"
/>
<gl-empty-state
v-else-if=
"emptyState"
:title=
"emptyTitle"
:svg-path=
"errorStateSvgPath"
data-testid=
"empty-state"
>
<template
#description
>
{{
s__
(
'
FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.
'
,
)
}}
<gl-link
:href=
"featureFlagsHelpPagePath"
target=
"_blank"
>
{{
s__
(
'
FeatureFlags|More information
'
)
}}
</gl-link>
</
template
>
</gl-empty-state>
<slot>
</slot>
</template>
</gl-tab>
</template>
ee/app/assets/javascripts/feature_flags/index.js
View file @
ca76d547
...
...
@@ -16,6 +16,8 @@ export default () =>
provide
()
{
return
{
projectName
:
this
.
dataset
.
projectName
,
featureFlagsHelpPagePath
:
this
.
dataset
.
featureFlagsHelpPagePath
,
errorStateSvgPath
:
this
.
dataset
.
errorStateSvgPath
,
};
},
render
(
createElement
)
{
...
...
@@ -23,8 +25,6 @@ export default () =>
props
:
{
endpoint
:
this
.
dataset
.
endpoint
,
projectId
:
this
.
dataset
.
projectId
,
errorStateSvgPath
:
this
.
dataset
.
errorStateSvgPath
,
featureFlagsHelpPagePath
:
this
.
dataset
.
featureFlagsHelpPagePath
,
featureFlagsClientLibrariesHelpPagePath
:
this
.
dataset
.
featureFlagsClientLibrariesHelpPagePath
,
featureFlagsClientExampleHelpPagePath
:
this
.
dataset
.
featureFlagsClientExampleHelpPagePath
,
...
...
ee/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
View file @
ca76d547
...
...
@@ -5,10 +5,12 @@ import Callout from '~/vue_shared/components/callout.vue';
describe
(
'
Configure Feature Flags Modal
'
,
()
=>
{
const
mockEvent
=
{
preventDefault
:
jest
.
fn
()
};
const
projectName
=
'
fakeProjectName
'
;
const
provide
=
{
projectName
:
'
fakeProjectName
'
,
featureFlagsHelpPagePath
:
'
/help/path
'
,
};
const
propsData
=
{
helpPath
:
'
/help/path
'
,
helpClientLibrariesPath
:
'
/help/path/#flags
'
,
helpClientExamplePath
:
'
/feature-flags#clientexample
'
,
apiUrl
:
'
/api/url
'
,
...
...
@@ -21,9 +23,7 @@ describe('Configure Feature Flags Modal', () => {
let
wrapper
;
const
factory
=
(
props
=
{},
{
mountFn
=
shallowMount
,
...
options
}
=
{})
=>
{
wrapper
=
mountFn
(
Component
,
{
provide
:
{
projectName
,
},
provide
,
stubs
:
{
GlSprintf
},
propsData
:
{
...
propsData
,
...
...
@@ -61,7 +61,7 @@ describe('Configure Feature Flags Modal', () => {
});
it
(
'
should clear the project name input after generating the token
'
,
async
()
=>
{
findProjectNameInput
().
vm
.
$emit
(
'
input
'
,
projectName
);
findProjectNameInput
().
vm
.
$emit
(
'
input
'
,
pro
vide
.
pro
jectName
);
findGlModal
().
vm
.
$emit
(
'
primary
'
,
mockEvent
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
findProjectNameInput
().
attributes
(
'
value
'
)).
toBe
(
''
);
...
...
@@ -78,7 +78,9 @@ describe('Configure Feature Flags Modal', () => {
});
it
(
'
should have links to the documentation
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
[data-testid="help-link"]
'
).
attributes
(
'
href
'
)).
toBe
(
propsData
.
helpPath
);
expect
(
wrapper
.
find
(
'
[data-testid="help-link"]
'
).
attributes
(
'
href
'
)).
toBe
(
provide
.
featureFlagsHelpPagePath
,
);
expect
(
wrapper
.
find
(
'
[data-testid="help-client-link"]
'
).
attributes
(
'
href
'
)).
toBe
(
propsData
.
helpClientLibrariesPath
,
);
...
...
@@ -91,7 +93,9 @@ describe('Configure Feature Flags Modal', () => {
});
it
(
'
should display a message asking to fill the project name
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
[data-testid="prevent-accident-text"]
'
).
text
()).
toMatch
(
projectName
);
expect
(
wrapper
.
find
(
'
[data-testid="prevent-accident-text"]
'
).
text
()).
toMatch
(
provide
.
projectName
,
);
});
it
(
'
should display the api URL in an input box
'
,
()
=>
{
...
...
@@ -110,7 +114,7 @@ describe('Configure Feature Flags Modal', () => {
beforeEach
(
factory
);
it
(
'
should enable the primary action
'
,
async
()
=>
{
findProjectNameInput
().
vm
.
$emit
(
'
input
'
,
projectName
);
findProjectNameInput
().
vm
.
$emit
(
'
input
'
,
pro
vide
.
pro
jectName
);
await
wrapper
.
vm
.
$nextTick
();
const
[{
disabled
}]
=
findPrimaryAction
().
attributes
;
expect
(
disabled
).
toBe
(
false
);
...
...
ee/spec/frontend/feature_flags/components/feature_flags_spec.js
View file @
ca76d547
...
...
@@ -2,14 +2,14 @@ import { shallowMount } from '@vue/test-utils';
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
GlEmptyState
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
Api
from
'
ee/api
'
;
import
store
from
'
ee/feature_flags/store
'
;
import
{
createStore
}
from
'
ee/feature_flags/store
'
;
import
FeatureFlagsTab
from
'
ee/feature_flags/components/feature_flags_tab.vue
'
;
import
FeatureFlagsComponent
from
'
ee/feature_flags/components/feature_flags.vue
'
;
import
FeatureFlagsTable
from
'
ee/feature_flags/components/feature_flags_table.vue
'
;
import
UserListsTable
from
'
ee/feature_flags/components/user_lists_table.vue
'
;
import
ConfigureFeatureFlagsModal
from
'
ee/feature_flags/components/configure_feature_flags_modal.vue
'
;
import
{
FEATURE_FLAG_SCOPE
,
USER_LIST_SCOPE
}
from
'
ee/feature_flags/constants
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
NavigationTabs
from
'
~/vue_shared/components/navigation_tabs.vue
'
;
import
TablePagination
from
'
~/vue_shared/components/pagination/table_pagination.vue
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
getRequestData
,
userList
}
from
'
../mock_data
'
;
...
...
@@ -18,8 +18,6 @@ describe('Feature flags', () => {
const
mockData
=
{
endpoint
:
`
${
TEST_HOST
}
/endpoint.json`
,
csrfToken
:
'
testToken
'
,
errorStateSvgPath
:
'
/assets/illustrations/feature_flag.svg
'
,
featureFlagsHelpPagePath
:
'
/help/feature-flags
'
,
featureFlagsClientLibrariesHelpPagePath
:
'
/help/feature-flags#unleash-clients
'
,
featureFlagsClientExampleHelpPagePath
:
'
/help/feature-flags#client-example
'
,
unleashApiUrl
:
`
${
TEST_HOST
}
/api/unleash`
,
...
...
@@ -33,12 +31,20 @@ describe('Feature flags', () => {
let
wrapper
;
let
mock
;
let
store
;
const
factory
=
(
propsData
=
mockData
,
fn
=
shallowMount
)
=>
{
store
=
createStore
();
wrapper
=
fn
(
FeatureFlagsComponent
,
{
store
,
propsData
,
provide
:
{
projectName
:
'
fakeProjectName
'
,
errorStateSvgPath
:
'
/assets/illustrations/feature_flag.svg
'
,
featureFlagsHelpPagePath
:
'
/help/feature-flags
'
,
},
stubs
:
{
FeatureFlagsTab
,
},
});
};
...
...
@@ -49,7 +55,6 @@ describe('Feature flags', () => {
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
jest
.
spyOn
(
store
,
'
dispatch
'
);
jest
.
spyOn
(
Api
,
'
fetchFeatureFlagUserLists
'
).
mockResolvedValue
({
data
:
[
userList
],
headers
:
{
...
...
@@ -66,6 +71,7 @@ describe('Feature flags', () => {
afterEach
(()
=>
{
mock
.
restore
();
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
without permissions
'
,
()
=>
{
...
...
@@ -127,32 +133,30 @@ describe('Feature flags', () => {
describe
(
'
without feature flags
'
,
()
=>
{
let
emptyState
;
beforeEach
(
done
=>
{
mock
.
onGet
(
mockData
.
endpoint
,
{
params
:
{
scope
:
FEATURE_FLAG_SCOPE
,
page
:
'
1
'
}
})
.
replyOnce
(
200
,
{
feature_flags
:
[],
count
:
{
all
:
0
,
enabled
:
0
,
disabled
:
0
,
},
beforeEach
(
async
()
=>
{
mock
.
onGet
(
mockData
.
endpoint
,
{
params
:
{
scope
:
FEATURE_FLAG_SCOPE
,
page
:
'
1
'
}
}).
reply
(
200
,
{
feature_flags
:
[],
count
:
{
all
:
0
,
enabled
:
0
,
disabled
:
0
,
},
{},
);
},
{},
);
factory
();
await
wrapper
.
vm
.
$nextTick
();
setImmediate
(()
=>
{
emptyState
=
wrapper
.
find
(
GlEmptyState
);
done
();
});
emptyState
=
wrapper
.
find
(
GlEmptyState
);
});
it
(
'
should render the empty state
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlEmptyState
).
exists
()).
toBe
(
true
);
it
(
'
should render the empty state
'
,
async
()
=>
{
await
axios
.
waitForAll
();
emptyState
=
wrapper
.
find
(
GlEmptyState
);
expect
(
emptyState
.
exists
()).
toBe
(
true
);
});
it
(
'
renders configure button
'
,
()
=>
{
...
...
@@ -189,6 +193,7 @@ describe('Feature flags', () => {
});
factory
();
jest
.
spyOn
(
store
,
'
dispatch
'
);
setImmediate
(()
=>
{
done
();
});
...
...
@@ -246,7 +251,7 @@ describe('Feature flags', () => {
it
(
'
should make an API request when using tabs
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
updateFeatureFlagOptions
'
);
wrapper
.
find
(
NavigationTabs
).
vm
.
$emit
(
'
onChangeTab
'
,
USER_LIST_SCOPE
);
wrapper
.
find
(
'
[data-testid="user-lists-tab"]
'
).
vm
.
$emit
(
'
changeTab
'
);
expect
(
wrapper
.
vm
.
updateFeatureFlagOptions
).
toHaveBeenCalledWith
({
scope
:
USER_LIST_SCOPE
,
...
...
@@ -265,7 +270,7 @@ describe('Feature flags', () => {
});
});
beforeEach
(()
=>
{
wrapper
.
find
(
NavigationTabs
).
vm
.
$emit
(
'
onChangeTab
'
,
USER_LIST_SCOPE
);
wrapper
.
find
(
'
[data-testid="user-lists-tab"]
'
).
vm
.
$emit
(
'
changeTab
'
);
return
wrapper
.
vm
.
$nextTick
();
});
...
...
ee/spec/frontend/feature_flags/components/feature_flags_tab_spec.js
0 → 100644
View file @
ca76d547
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
GlAlert
,
GlBadge
,
GlEmptyState
,
GlLink
,
GlLoadingIcon
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
FeatureFlagsTab
from
'
ee/feature_flags/components/feature_flags_tab.vue
'
;
const
DEFAULT_PROPS
=
{
title
:
'
test
'
,
count
:
5
,
alerts
:
[
'
an alert
'
,
'
another alert
'
],
isLoading
:
false
,
loadingLabel
:
'
test loading
'
,
errorState
:
false
,
errorTitle
:
'
test title
'
,
emptyState
:
true
,
emptyTitle
:
'
test empty
'
,
};
const
DEFAULT_PROVIDE
=
{
errorStateSvgPath
:
'
/error.svg
'
,
featureFlagsHelpPagePath
:
'
/help/page/path
'
,
};
describe
(
'
ee/feature_flags/components/feature_flags_tab.vue
'
,
()
=>
{
let
wrapper
;
const
factory
=
(
props
=
{})
=>
mount
(
{
components
:
{
GlTabs
,
FeatureFlagsTab
,
},
render
(
h
)
{
return
h
(
GlTabs
,
[
h
(
FeatureFlagsTab
,
{
props
:
this
.
$attrs
,
on
:
this
.
$listeners
},
this
.
$slots
.
default
),
]);
},
},
{
propsData
:
{
...
DEFAULT_PROPS
,
...
props
,
},
provide
:
DEFAULT_PROVIDE
,
slots
:
{
default
:
'
<p data-testid="test-slot">testing</p>
'
,
},
},
);
afterEach
(()
=>
{
if
(
wrapper
?.
destroy
)
{
wrapper
.
destroy
();
}
wrapper
=
null
;
});
describe
(
'
alerts
'
,
()
=>
{
let
alerts
;
beforeEach
(()
=>
{
wrapper
=
factory
();
alerts
=
wrapper
.
findAll
(
GlAlert
);
});
it
(
'
should show any alerts
'
,
()
=>
{
expect
(
alerts
).
toHaveLength
(
DEFAULT_PROPS
.
alerts
.
length
);
alerts
.
wrappers
.
forEach
((
alert
,
i
)
=>
expect
(
alert
.
text
()).
toBe
(
DEFAULT_PROPS
.
alerts
[
i
]));
});
it
(
'
should emit a dismiss event for a dismissed alert
'
,
()
=>
{
alerts
.
at
(
0
).
vm
.
$emit
(
'
dismiss
'
);
expect
(
wrapper
.
find
(
FeatureFlagsTab
).
emitted
(
'
dismissAlert
'
)).
toEqual
([[
0
]]);
});
});
describe
(
'
loading
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
factory
({
isLoading
:
true
});
});
it
(
'
should show a loading icon and nothing else
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
findAll
(
GlEmptyState
)).
toHaveLength
(
0
);
});
});
describe
(
'
error
'
,
()
=>
{
let
emptyState
;
beforeEach
(()
=>
{
wrapper
=
factory
({
errorState
:
true
});
emptyState
=
wrapper
.
find
(
GlEmptyState
);
});
it
(
'
should show an error state if there has been an error
'
,
()
=>
{
expect
(
emptyState
.
text
()).
toContain
(
DEFAULT_PROPS
.
errorTitle
);
expect
(
emptyState
.
text
()).
toContain
(
'
Try again in a few moments or contact your support team.
'
,
);
expect
(
emptyState
.
props
(
'
svgPath
'
)).
toBe
(
DEFAULT_PROVIDE
.
errorStateSvgPath
);
});
});
describe
(
'
empty
'
,
()
=>
{
let
emptyState
;
let
emptyStateLink
;
beforeEach
(()
=>
{
wrapper
=
factory
({
emptyState
:
true
});
emptyState
=
wrapper
.
find
(
GlEmptyState
);
emptyStateLink
=
emptyState
.
find
(
GlLink
);
});
it
(
'
should show an empty state if it is empty
'
,
()
=>
{
expect
(
emptyState
.
text
()).
toContain
(
DEFAULT_PROPS
.
emptyTitle
);
expect
(
emptyState
.
text
()).
toContain
(
'
Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.
'
,
);
expect
(
emptyState
.
props
(
'
svgPath
'
)).
toBe
(
DEFAULT_PROVIDE
.
errorStateSvgPath
);
expect
(
emptyStateLink
.
attributes
(
'
href
'
)).
toBe
(
DEFAULT_PROVIDE
.
featureFlagsHelpPagePath
);
expect
(
emptyStateLink
.
text
()).
toBe
(
'
More information
'
);
});
});
describe
(
'
slot
'
,
()
=>
{
let
slot
;
beforeEach
(
async
()
=>
{
wrapper
=
factory
();
await
wrapper
.
vm
.
$nextTick
();
slot
=
wrapper
.
find
(
'
[data-testid="test-slot"]
'
);
});
it
(
'
should display the passed slot
'
,
()
=>
{
expect
(
slot
.
exists
()).
toBe
(
true
);
expect
(
slot
.
text
()).
toBe
(
'
testing
'
);
});
});
describe
(
'
count
'
,
()
=>
{
it
(
'
should display a count if there is one
'
,
async
()
=>
{
wrapper
=
factory
();
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
GlBadge
).
text
()).
toBe
(
DEFAULT_PROPS
.
count
.
toString
());
});
it
(
'
should display 0 if there is no count
'
,
async
()
=>
{
wrapper
=
factory
({
count
:
undefined
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
GlBadge
).
text
()).
toBe
(
'
0
'
);
});
});
describe
(
'
title
'
,
()
=>
{
it
(
'
should show the title
'
,
async
()
=>
{
wrapper
=
factory
();
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
[data-testid="feature-flags-tab-title"]
'
).
text
()).
toBe
(
DEFAULT_PROPS
.
title
,
);
});
});
});
locale/gitlab.pot
View file @
ca76d547
...
...
@@ -10966,6 +10966,9 @@ msgstr ""
msgid "FeatureFlags|Loading feature flags"
msgstr ""
msgid "FeatureFlags|Loading user lists"
msgstr ""
msgid "FeatureFlags|More information"
msgstr ""
...
...
@@ -10984,7 +10987,7 @@ msgstr ""
msgid "FeatureFlags|New feature flag"
msgstr ""
msgid "FeatureFlags|New list"
msgid "FeatureFlags|New
user
list"
msgstr ""
msgid "FeatureFlags|Percent of users"
...
...
@@ -11023,6 +11026,9 @@ msgstr ""
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
msgid "FeatureFlags|There was an error fetching the user lists."
msgstr ""
msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
...
...
@@ -11038,6 +11044,9 @@ msgstr ""
msgid "FeatureFlags|User List"
msgstr ""
msgid "FeatureFlags|User Lists"
msgstr ""
msgid "FeatureFlag|List"
msgstr ""
...
...
@@ -15114,9 +15123,6 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
msgid "Lists"
msgstr ""
msgid "Live preview"
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