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
aa965b0c
Commit
aa965b0c
authored
Sep 03, 2020
by
David Pisek
Committed by
Rémy Coutable
Sep 03, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update profile settings
* Adds graphql queries / mutations * Adds helper to graphql-utils
parent
7240c379
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
331 additions
and
163 deletions
+331
-163
ee/app/assets/javascripts/dast_profiles/components/dast_profiles.vue
...ts/javascripts/dast_profiles/components/dast_profiles.vue
+94
-78
ee/app/assets/javascripts/dast_profiles/components/dast_profiles_list.vue
...vascripts/dast_profiles/components/dast_profiles_list.vue
+31
-24
ee/app/assets/javascripts/dast_profiles/graphql/cache_utils.js
...p/assets/javascripts/dast_profiles/graphql/cache_utils.js
+17
-15
ee/app/assets/javascripts/dast_profiles/graphql/dast_scanner_profiles.query.graphql
...dast_profiles/graphql/dast_scanner_profiles.query.graphql
+31
-0
ee/app/assets/javascripts/dast_profiles/graphql/dast_scanner_profiles_delete.mutation.graphql
...les/graphql/dast_scanner_profiles_delete.mutation.graphql
+7
-0
ee/app/assets/javascripts/dast_profiles/graphql/dast_site_profiles_delete.mutation.graphql
...ofiles/graphql/dast_site_profiles_delete.mutation.graphql
+1
-1
ee/app/assets/javascripts/dast_profiles/settings/profiles.js
ee/app/assets/javascripts/dast_profiles/settings/profiles.js
+51
-4
ee/spec/frontend/dast_profiles/components/dast_profiles_list_spec.js
...ntend/dast_profiles/components/dast_profiles_list_spec.js
+26
-8
ee/spec/frontend/dast_profiles/components/dast_profiles_spec.js
...c/frontend/dast_profiles/components/dast_profiles_spec.js
+35
-19
ee/spec/frontend/dast_profiles/graphql/cache_utils_spec.js
ee/spec/frontend/dast_profiles/graphql/cache_utils_spec.js
+23
-14
locale/gitlab.pot
locale/gitlab.pot
+15
-0
No files found.
ee/app/assets/javascripts/dast_profiles/components/dast_profiles.vue
View file @
aa965b0c
...
...
@@ -4,8 +4,6 @@ import { GlDropdown, GlDropdownItem, GlTab, GlTabs } from '@gitlab/ui';
import
{
s__
}
from
'
~/locale
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
ProfilesList
from
'
./dast_profiles_list.vue
'
;
import
dastSiteProfilesQuery
from
'
../graphql/dast_site_profiles.query.graphql
'
;
import
dastSiteProfilesDelete
from
'
../graphql/dast_site_profiles_delete.mutation.graphql
'
;
import
*
as
cacheUtils
from
'
../graphql/cache_utils
'
;
import
{
getProfileSettings
}
from
'
../settings/profiles
'
;
...
...
@@ -32,39 +30,11 @@ export default {
},
data
()
{
return
{
siteProfiles
:
[],
siteProfilesPageInfo
:
{},
profileTypes
:
{},
errorMessage
:
''
,
errorDetails
:
[],
};
},
apollo
:
{
siteProfiles
()
{
return
{
query
:
dastSiteProfilesQuery
,
variables
:
{
fullPath
:
this
.
projectFullPath
,
first
:
this
.
$options
.
profilesPerPage
,
},
result
({
data
,
error
})
{
if
(
!
error
)
{
this
.
siteProfilesPageInfo
=
data
.
project
.
siteProfiles
.
pageInfo
;
}
},
update
(
data
)
{
const
siteProfileEdges
=
data
?.
project
?.
siteProfiles
?.
edges
??
[];
return
siteProfileEdges
.
map
(({
node
})
=>
node
);
},
error
(
error
)
{
this
.
handleError
({
exception
:
error
,
message
:
this
.
$options
.
i18n
.
errorMessages
.
fetchNetworkError
,
});
},
};
},
},
computed
:
{
profileSettings
()
{
const
{
glFeatures
,
createNewProfilePaths
}
=
this
;
...
...
@@ -76,14 +46,66 @@ export default {
glFeatures
,
);
},
hasMoreSiteProfiles
()
{
return
this
.
siteProfilesPageInfo
.
hasNextPage
;
},
isLoadingSiteProfiles
()
{
return
this
.
$apollo
.
queries
.
siteProfiles
.
loading
;
},
},
created
()
{
this
.
addSmartQueriesForEnabledProfileTypes
();
},
methods
:
{
addSmartQueriesForEnabledProfileTypes
()
{
Object
.
values
(
this
.
profileSettings
).
forEach
(({
profileType
,
graphQL
:
{
query
}
})
=>
{
this
.
makeProfileTypeReactive
(
profileType
);
this
.
$apollo
.
addSmartQuery
(
profileType
,
this
.
createQuery
({
profileType
,
query
,
variables
:
{
fullPath
:
this
.
projectFullPath
,
first
:
this
.
$options
.
profilesPerPage
,
},
}),
);
});
},
makeProfileTypeReactive
(
profileType
)
{
this
.
$set
(
this
.
profileTypes
,
profileType
,
{
profiles
:
[],
pageInfo
:
{},
});
},
hasMoreProfiles
(
profileType
)
{
return
this
.
profileTypes
[
profileType
]?.
pageInfo
?.
hasNextPage
;
},
isLoadingProfiles
(
profileType
)
{
return
this
.
$apollo
.
queries
[
profileType
].
loading
;
},
createQuery
({
profileType
,
query
,
variables
})
{
return
{
query
,
variables
,
manual
:
true
,
result
({
data
,
error
})
{
if
(
!
error
)
{
const
{
project
}
=
data
;
const
profileEdges
=
project
?.[
profileType
]?.
edges
??
[];
const
profiles
=
profileEdges
.
map
(({
node
})
=>
node
);
const
pageInfo
=
project
?.[
profileType
].
pageInfo
;
this
.
profileTypes
[
profileType
]
=
{
profiles
,
pageInfo
,
};
}
},
error
(
error
)
{
this
.
handleError
({
exception
:
error
,
message
:
this
.
profileSettings
[
profileType
].
i18n
.
errorMessages
.
fetchNetworkError
,
});
},
};
},
handleError
({
exception
,
message
=
''
,
details
=
[]
})
{
Sentry
.
captureException
(
exception
);
this
.
errorMessage
=
message
;
...
...
@@ -93,32 +115,37 @@ export default {
this
.
errorMessage
=
''
;
this
.
errorDetails
=
[];
},
fetchMoreProfiles
()
{
fetchMoreProfiles
(
profileType
)
{
const
{
$apollo
,
siteProfilesPageInfo
,
$options
:
{
i18n
},
}
=
this
;
const
{
pageInfo
}
=
this
.
profileTypes
[
profileType
];
this
.
resetErrors
();
$apollo
.
queries
.
siteProfiles
$apollo
.
queries
[
profileType
]
.
fetchMore
({
variables
:
{
after
:
siteProfilesP
ageInfo
.
endCursor
},
updateQuery
:
cacheUtils
.
appendToPreviousResult
,
variables
:
{
after
:
p
ageInfo
.
endCursor
},
updateQuery
:
cacheUtils
.
appendToPreviousResult
(
profileType
)
,
})
.
catch
(
error
=>
{
this
.
handleError
({
exception
:
error
,
message
:
i18n
.
errorMessages
.
fetchNetworkError
});
});
},
delete
SiteProfile
(
profileToBeDeleted
Id
)
{
delete
Profile
(
profileType
,
profile
Id
)
{
const
{
projectFullPath
,
handleError
,
$options
:
{
i18n
},
profileSettings
:
{
[
profileType
]:
{
i18n
,
graphQL
:
{
deletion
},
},
},
$apollo
:
{
queries
:
{
siteProfiles
:
{
options
:
siteProfilesQ
ueryOptions
},
[
profileType
]:
{
options
:
q
ueryOptions
},
},
},
}
=
this
;
...
...
@@ -127,27 +154,23 @@ export default {
this
.
$apollo
.
mutate
({
mutation
:
d
astSiteProfilesDelete
,
mutation
:
d
eletion
.
mutation
,
variables
:
{
projectFullPath
,
profileId
:
profileToBeDeletedId
,
profileId
,
},
update
(
store
,
{
data
:
{
dastSiteProfileDelete
:
{
errors
=
[]
},
},
},
)
{
update
(
store
,
{
data
=
{}
})
{
const
errors
=
data
[
`
${
profileType
}
Delete`
]?.
errors
??
[];
if
(
errors
.
length
===
0
)
{
cacheUtils
.
removeProfile
({
profileId
,
profileType
,
store
,
queryBody
:
{
query
:
siteProfilesQ
ueryOptions
.
query
,
variables
:
siteProfilesQ
ueryOptions
.
variables
,
query
:
q
ueryOptions
.
query
,
variables
:
q
ueryOptions
.
variables
,
},
profileToBeDeletedId
,
});
}
else
{
handleError
({
...
...
@@ -156,7 +179,7 @@ export default {
});
}
},
optimisticResponse
:
cacheUtils
.
dastSiteProfilesDeleteResponse
()
,
optimisticResponse
:
deletion
.
optimisticResponse
,
})
.
catch
(
error
=>
{
this
.
handleError
({
...
...
@@ -173,15 +196,6 @@ export default {
subHeading
:
s__
(
'
DastProfiles|Save commonly used configurations for target sites and scan specifications as profiles. Use these with an on-demand scan.
'
,
),
errorMessages
:
{
fetchNetworkError
:
s__
(
'
DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later.
'
,
),
deletionNetworkError
:
s__
(
'
DastProfiles|Could not delete site profile. Please refresh the page, or try again later.
'
,
),
deletionBackendError
:
s__
(
'
DastProfiles|Could not delete site profiles:
'
),
},
},
};
</
script
>
...
...
@@ -200,11 +214,11 @@ export default {
class=
"gl-ml-auto"
>
<gl-dropdown-item
v-for=
"
{ i18n, createNewProfilePath,
key
} in profileSettings"
:key="
key
"
v-for=
"
{ i18n, createNewProfilePath,
profileType
} in profileSettings"
:key="
profileType
"
:href="createNewProfilePath"
>
{{
i18n
.
title
}}
{{
i18n
.
createNewLinkText
}}
</gl-dropdown-item>
</gl-dropdown>
</div>
...
...
@@ -214,20 +228,22 @@ export default {
</header>
<gl-tabs>
<gl-tab>
<gl-tab
v-for=
"(data, profileType) in profileSettings"
:key=
"profileType"
>
<template
#title
>
<span>
{{
s__
(
'
DastProfiles|Site Profiles
'
)
}}
</span>
<span>
{{
profileSettings
[
profileType
].
i18n
.
tabName
}}
</span>
</
template
>
<profiles-list
:data-testid=
"`${profileType}List`"
:error-message=
"errorMessage"
:error-details=
"errorDetails"
:has-more-profiles-to-load=
"hasMore
SiteProfiles
"
:is-loading=
"isLoading
SiteProfiles
"
:has-more-profiles-to-load=
"hasMore
Profiles(profileType)
"
:is-loading=
"isLoading
Profiles(profileType)
"
:profiles-per-page=
"$options.profilesPerPage"
:profiles=
"siteProfiles"
@
loadMoreProfiles=
"fetchMoreProfiles"
@
deleteProfile=
"deleteSiteProfile"
:profiles=
"profileTypes[profileType].profiles"
:fields=
"profileSettings[profileType].tableFields"
@
load-more-profiles=
"fetchMoreProfiles(profileType)"
@
delete-profile=
"deleteProfile(profileType, $event)"
/>
</gl-tab>
</gl-tabs>
...
...
ee/app/assets/javascripts/dast_profiles/components/dast_profiles_list.vue
View file @
aa965b0c
...
...
@@ -27,6 +27,10 @@ export default {
type
:
Array
,
required
:
true
,
},
fields
:
{
type
:
Array
,
required
:
true
,
},
errorMessage
:
{
type
:
String
,
required
:
false
,
...
...
@@ -76,10 +80,17 @@ export default {
modalId
()
{
return
`dast-profiles-list-
${
uniqueId
()}
`
;
},
tableFields
()
{
const
defaultClasses
=
[
'
gl-word-break-all
'
];
const
dataFields
=
this
.
fields
.
map
(
key
=>
({
key
,
class
:
defaultClasses
}));
const
staticFields
=
[{
key
:
'
actions
'
}];
return
[...
dataFields
,
...
staticFields
];
},
},
methods
:
{
handleDelete
()
{
this
.
$emit
(
'
delete
P
rofile
'
,
this
.
toBeDeletedProfileId
);
this
.
$emit
(
'
delete
-p
rofile
'
,
this
.
toBeDeletedProfileId
);
},
prepareProfileDeletion
(
profileId
)
{
this
.
toBeDeletedProfileId
=
profileId
;
...
...
@@ -89,25 +100,6 @@ export default {
this
.
toBeDeletedProfileId
=
null
;
},
},
tableFields
:
[
{
key
:
'
profileName
'
,
class
:
'
gl-word-break-all
'
,
},
{
key
:
'
targetUrl
'
,
class
:
'
gl-word-break-all
'
,
},
{
key
:
'
validationStatus
'
,
// NOTE: hidden for now, since the site validation is still WIP and will be finished in an upcoming iteration
// roadmap: https://gitlab.com/groups/gitlab-org/-/epics/2912#ui-configuration
class
:
'
gl-display-none!
'
,
},
{
key
:
'
actions
'
,
},
],
};
</
script
>
<
template
>
...
...
@@ -116,13 +108,13 @@ export default {
<gl-table
:aria-label=
"s__('DastProfiles|Site Profiles')"
:busy=
"isLoadingInitialProfiles"
:fields=
"
$options.
tableFields"
:fields=
"tableFields"
:items=
"profiles"
stacked=
"md"
thead-class=
"gl-display-none"
>
<template
v-if=
"hasError"
#top-row
>
<td
:colspan=
"
$options.
tableFields.length"
>
<td
:colspan=
"tableFields.length"
>
<gl-alert
class=
"gl-my-4"
variant=
"danger"
:dismissible=
"false"
>
{{
errorMessage
}}
<ul
...
...
@@ -161,7 +153,22 @@ export default {
:aria-label=
"__('Delete')"
@
click=
"prepareProfileDeletion(item.id)"
/>
<gl-button
:href=
"item.editPath"
>
{{
__
(
'
Edit
'
)
}}
</gl-button>
<gl-button
v-if=
"item.editPath"
:href=
"item.editPath"
>
{{
__
(
'
Edit
'
)
}}
</gl-button>
<!--
NOTE: The tooltip and `disable` on the button is temporary until the edit feature has been implemented
further details: https://gitlab.com/groups/gitlab-org/-/epics/3786 (iteration outline)
-->
<span
v-else
v-gl-tooltip
.
hover
:title=
"
s__(
'DastProfiles|Edit feature will come soon. Please create a new profile if changes needed',
)
"
>
<gl-button
disabled
>
{{
__
(
'
Edit
'
)
}}
</gl-button>
</span>
</div>
</
template
>
...
...
@@ -181,7 +188,7 @@ export default {
<gl-button
data-testid=
"loadMore"
:loading=
"isLoading && !hasError"
@
click=
"$emit('load
MoreP
rofiles')"
@
click=
"$emit('load
-more-p
rofiles')"
>
{{ __('Load more') }}
</gl-button>
...
...
ee/app/assets/javascripts/dast_profiles/graphql/cache_utils.js
View file @
aa965b0c
...
...
@@ -2,16 +2,15 @@
* Appends paginated results to existing ones
* - to be used with $apollo.queries.x.fetchMore
*
* @param previousResult
* @param fetchMoreResult
* @returns {*}
* @param {*} profileType
* @returns {function(*, {fetchMoreResult: *}): *}
*/
export
const
appendToPreviousResult
=
(
previousResult
,
{
fetchMoreResult
})
=>
{
export
const
appendToPreviousResult
=
profileType
=>
(
previousResult
,
{
fetchMoreResult
})
=>
{
const
newResult
=
{
...
fetchMoreResult
};
const
previousEdges
=
previousResult
.
project
.
siteProfiles
.
edges
;
const
newEdges
=
newResult
.
project
.
siteProfiles
.
edges
;
const
previousEdges
=
previousResult
.
project
[
profileType
]
.
edges
;
const
newEdges
=
newResult
.
project
[
profileType
]
.
edges
;
newResult
.
project
.
siteProfiles
.
edges
=
[...
previousEdges
,
...
newEdges
];
newResult
.
project
[
profileType
]
.
edges
=
[...
previousEdges
,
...
newEdges
];
return
newResult
;
};
...
...
@@ -19,15 +18,16 @@ export const appendToPreviousResult = (previousResult, { fetchMoreResult }) => {
/**
* Removes profile with given id from the cache and writes the result to it
*
* @param profileId
* @param profileType
* @param store
* @param queryBody
* @param profileToBeDeletedId
*/
export
const
removeProfile
=
({
store
,
queryBody
,
profileToBeDeletedId
})
=>
{
export
const
removeProfile
=
({
profileId
,
profileType
,
store
,
queryBody
})
=>
{
const
data
=
store
.
readQuery
(
queryBody
);
data
.
project
.
siteProfiles
.
edges
=
data
.
project
.
siteProfiles
.
edges
.
filter
(({
node
})
=>
{
return
node
.
id
!==
profile
ToBeDeleted
Id
;
data
.
project
[
profileType
].
edges
=
data
.
project
[
profileType
]
.
edges
.
filter
(({
node
})
=>
{
return
node
.
id
!==
profileId
;
});
store
.
writeQuery
({
...
queryBody
,
data
});
...
...
@@ -36,13 +36,15 @@ export const removeProfile = ({ store, queryBody, profileToBeDeletedId }) => {
/**
* Returns an object representing a optimistic response for site-profile deletion
*
* @returns {{__typename: string, dastSiteProfileDelete: {__typename: string, errors: []}}}
* @param mutationName
* @param payloadTypeName
* @returns {{[p: string]: string, __typename: string}}
*/
export
const
dast
SiteProfilesDeleteResponse
=
(
)
=>
({
export
const
dast
ProfilesDeleteResponse
=
({
mutationName
,
payloadTypeName
}
)
=>
({
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename
:
'
Mutation
'
,
dastSiteProfileDelete
:
{
__typename
:
'
DastSiteProfileDeletePayload
'
,
[
mutationName
]
:
{
__typename
:
payloadTypeName
,
errors
:
[],
},
});
ee/app/assets/javascripts/dast_profiles/graphql/dast_scanner_profiles.query.graphql
0 → 100644
View file @
aa965b0c
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query
DastScannerProfiles
(
$fullPath
:
ID
!
$after
:
String
$before
:
String
$first
:
Int
$last
:
Int
)
{
project
(
fullPath
:
$fullPath
)
{
scannerProfiles
:
dastScannerProfiles
(
after
:
$after
before
:
$before
first
:
$first
last
:
$last
)
{
pageInfo
{
...
PageInfo
}
edges
{
cursor
node
{
id
:
globalId
profileName
spiderTimeout
targetTimeout
}
}
}
}
}
ee/app/assets/javascripts/dast_profiles/graphql/dast_scanner_profiles_delete.mutation.graphql
0 → 100644
View file @
aa965b0c
mutation
dastScannerProfileDelete
(
$projectFullPath
:
ID
!,
$profileId
:
DastScannerProfileID
!)
{
scannerProfilesDelete
:
dastScannerProfileDelete
(
input
:
{
fullPath
:
$projectFullPath
,
id
:
$profileId
}
)
{
errors
}
}
ee/app/assets/javascripts/dast_profiles/graphql/dast_site_profiles_delete.mutation.graphql
View file @
aa965b0c
mutation
dastSiteProfileDelete
(
$projectFullPath
:
ID
!,
$profileId
:
DastSiteProfileID
!)
{
dastSiteProfileDelete
(
input
:
{
fullPath
:
$projectFullPath
,
id
:
$profileId
})
{
siteProfilesDelete
:
dastSiteProfileDelete
(
input
:
{
fullPath
:
$projectFullPath
,
id
:
$profileId
})
{
errors
}
}
ee/app/assets/javascripts/dast_profiles/settings/profiles.js
View file @
aa965b0c
import
dastSiteProfilesQuery
from
'
ee/dast_profiles/graphql/dast_site_profiles.query.graphql
'
;
import
dastSiteProfilesDelete
from
'
ee/dast_profiles/graphql/dast_site_profiles_delete.mutation.graphql
'
;
import
dastScannerProfilesQuery
from
'
ee/dast_profiles/graphql/dast_scanner_profiles.query.graphql
'
;
import
dastScannerProfilesDelete
from
'
ee/dast_profiles/graphql/dast_scanner_profiles_delete.mutation.graphql
'
;
import
{
dastProfilesDeleteResponse
}
from
'
ee/dast_profiles/graphql/cache_utils
'
;
import
{
s__
}
from
'
~/locale
'
;
const
hasNoFeatureFlagOrIsEnabled
=
glFeatures
=>
([,
{
featureFlag
}])
=>
{
...
...
@@ -11,18 +16,60 @@ const hasNoFeatureFlagOrIsEnabled = glFeatures => ([, { featureFlag }]) => {
export
const
getProfileSettings
=
({
createNewProfilePaths
},
glFeatures
)
=>
{
const
settings
=
{
siteProfiles
:
{
key
:
'
siteProfiles
'
,
profileType
:
'
siteProfiles
'
,
createNewProfilePath
:
createNewProfilePaths
.
siteProfile
,
graphQL
:
{
query
:
dastSiteProfilesQuery
,
deletion
:
{
mutation
:
dastSiteProfilesDelete
,
optimisticResponse
:
dastProfilesDeleteResponse
({
mutationName
:
'
siteProfilesDelete
'
,
payloadTypeName
:
'
DastSiteProfileDeletePayload
'
,
}),
},
},
tableFields
:
[
'
profileName
'
,
'
targetUrl
'
],
i18n
:
{
title
:
s__
(
'
DastProfiles|Site Profile
'
),
createNewLinkText
:
s__
(
'
DastProfiles|Site Profile
'
),
tabName
:
s__
(
'
DastProfiles|Site Profiles
'
),
errorMessages
:
{
fetchNetworkError
:
s__
(
'
DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later.
'
,
),
deletionNetworkError
:
s__
(
'
DastProfiles|Could not delete site profile. Please refresh the page, or try again later.
'
,
),
deletionBackendError
:
s__
(
'
DastProfiles|Could not delete site profiles:
'
),
},
},
},
scannerProfiles
:
{
key
:
'
scannerProfiles
'
,
profileType
:
'
scannerProfiles
'
,
createNewProfilePath
:
createNewProfilePaths
.
scannerProfile
,
graphQL
:
{
query
:
dastScannerProfilesQuery
,
deletion
:
{
mutation
:
dastScannerProfilesDelete
,
optimisticResponse
:
dastProfilesDeleteResponse
({
mutationName
:
'
scannerProfilesDelete
'
,
payloadTypeName
:
'
DastScannerProfileDeletePayload
'
,
}),
},
},
featureFlag
:
'
securityOnDemandScansScannerProfiles
'
,
tableFields
:
[
'
profileName
'
],
i18n
:
{
title
:
s__
(
'
DastProfiles|Scanner Profile
'
),
createNewLinkText
:
s__
(
'
DastProfiles|Scanner Profile
'
),
tabName
:
s__
(
'
DastProfiles|Scanner Profiles
'
),
errorMessages
:
{
fetchNetworkError
:
s__
(
'
DastProfiles|Could not fetch scanner profiles. Please refresh the page, or try again later.
'
,
),
deletionNetworkError
:
s__
(
'
DastProfiles|Could not delete scanner profile. Please refresh the page, or try again later.
'
,
),
deletionBackendError
:
s__
(
'
DastProfiles|Could not delete scanner profiles:
'
),
},
},
},
};
...
...
ee/spec/frontend/dast_profiles/components/dast_profiles_list_spec.js
View file @
aa965b0c
...
...
@@ -12,6 +12,7 @@ describe('EE - DastProfilesList', () => {
const
createComponentFactory
=
(
mountFn
=
shallowMount
)
=>
(
options
=
{})
=>
{
const
defaultProps
=
{
profiles
:
[],
fields
:
[
'
profileName
'
,
'
targetUrl
'
,
'
validationStatus
'
],
hasMorePages
:
false
,
profilesPerPage
:
10
,
errorMessage
:
''
,
...
...
@@ -116,6 +117,13 @@ describe('EE - DastProfilesList', () => {
editPath
:
'
/2/edit
'
,
validationStatus
:
'
Pending
'
,
},
{
id
:
3
,
profileName
:
'
Profile 2
'
,
targetUrl
:
'
http://example-2.com
'
,
editPath
:
''
,
validationStatus
:
'
Pending
'
,
},
];
const
getTableRowForProfile
=
profile
=>
getAllTableRows
()[
profiles
.
indexOf
(
profile
)];
...
...
@@ -147,9 +155,19 @@ describe('EE - DastProfilesList', () => {
expect
(
validationStatusCell
.
innerText
).
toContain
(
profile
.
validationStatus
);
expect
(
within
(
actionsCell
).
getByRole
(
'
button
'
,
{
name
:
/delete/i
})).
not
.
toBe
(
null
);
const
editLink
=
within
(
actionsCell
).
getByRole
(
'
link
'
,
{
name
:
/edit/i
});
expect
(
editLink
).
not
.
toBe
(
null
);
expect
(
editLink
.
getAttribute
(
'
href
'
)).
toBe
(
profile
.
editPath
);
if
(
profile
.
editPath
)
{
const
editLink
=
within
(
actionsCell
).
getByRole
(
'
link
'
,
{
name
:
/edit/i
});
expect
(
editLink
).
not
.
toBe
(
null
);
expect
(
editLink
.
getAttribute
(
'
href
'
)).
toBe
(
profile
.
editPath
);
}
else
{
const
editButton
=
within
(
actionsCell
).
getByRole
(
'
button
'
,
{
name
:
/edit/i
});
const
helpText
=
within
(
actionsCell
).
getByTitle
(
/edit feature will come soon. please create a new profile if changes needed/i
,
);
expect
(
helpText
).
not
.
toBe
(
null
);
expect
(
editButton
).
not
.
toBe
(
null
);
expect
(
editButton
.
getAttribute
(
'
disabled
'
)).
not
.
toBe
(
null
);
}
});
});
...
...
@@ -169,12 +187,12 @@ describe('EE - DastProfilesList', () => {
expect
(
getLoadMoreButton
().
exists
()).
toBe
(
true
);
});
it
(
'
emits "load
MoreP
rofiles" when the load-more button is clicked
'
,
async
()
=>
{
expect
(
wrapper
.
emitted
(
'
load
MoreP
rofiles
'
)).
toBe
(
undefined
);
it
(
'
emits "load
-more-p
rofiles" when the load-more button is clicked
'
,
async
()
=>
{
expect
(
wrapper
.
emitted
(
'
load
-more-p
rofiles
'
)).
toBe
(
undefined
);
await
getLoadMoreButton
().
trigger
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
load
MoreP
rofiles
'
)).
toEqual
(
expect
.
any
(
Array
));
expect
(
wrapper
.
emitted
(
'
load
-more-p
rofiles
'
)).
toEqual
(
expect
.
any
(
Array
));
});
});
});
...
...
@@ -200,7 +218,7 @@ describe('EE - DastProfilesList', () => {
});
it
(
`emits "@deleteProfile" with the right payload when the modal's primary action is triggered`
,
async
()
=>
{
expect
(
wrapper
.
emitted
(
'
delete
P
rofile
'
)).
toBe
(
undefined
);
expect
(
wrapper
.
emitted
(
'
delete
-p
rofile
'
)).
toBe
(
undefined
);
getCurrentProfileDeleteButton
().
trigger
(
'
click
'
);
...
...
@@ -208,7 +226,7 @@ describe('EE - DastProfilesList', () => {
getModal
().
vm
.
$emit
(
'
ok
'
);
expect
(
wrapper
.
emitted
(
'
delete
P
rofile
'
)[
0
]).
toEqual
([
profile
.
id
]);
expect
(
wrapper
.
emitted
(
'
delete
-p
rofile
'
)[
0
]).
toEqual
([
profile
.
id
]);
});
});
});
...
...
ee/spec/frontend/dast_profiles/components/dast_profiles_spec.js
View file @
aa965b0c
...
...
@@ -3,7 +3,6 @@ import { within } from '@testing-library/dom';
import
{
merge
}
from
'
lodash
'
;
import
{
GlDropdown
}
from
'
@gitlab/ui
'
;
import
DastProfiles
from
'
ee/dast_profiles/components/dast_profiles.vue
'
;
import
DastProfilesList
from
'
ee/dast_profiles/components/dast_profiles_list.vue
'
;
const
TEST_NEW_DAST_SCANNER_PROFILE_PATH
=
'
/-/on_demand_scans/scanner_profiles/new
'
;
const
TEST_NEW_DAST_SITE_PROFILE_PATH
=
'
/-/on_demand_scans/site_profiles/new
'
;
...
...
@@ -27,8 +26,18 @@ describe('EE - DastProfiles', () => {
siteProfiles
:
{
fetchMore
:
jest
.
fn
().
mockResolvedValue
(),
},
scannerProfiles
:
{
fetchMore
:
jest
.
fn
().
mockResolvedValue
(),
},
},
mutate
:
jest
.
fn
().
mockResolvedValue
(),
addSmartQuery
:
jest
.
fn
(),
},
};
const
defaultProvide
=
{
glFeatures
:
{
securityOnDemandScansScannerProfiles
:
true
,
},
};
...
...
@@ -39,6 +48,7 @@ describe('EE - DastProfiles', () => {
{
propsData
:
defaultProps
,
mocks
:
defaultMocks
,
provide
:
defaultProvide
,
},
options
,
),
...
...
@@ -67,7 +77,7 @@ describe('EE - DastProfiles', () => {
};
const
withinComponent
=
()
=>
within
(
wrapper
.
element
);
const
get
SiteProfilesComponent
=
()
=>
wrapper
.
find
(
DastProfilesList
);
const
get
ProfilesComponent
=
profileType
=>
wrapper
.
find
(
`[data-testid="
${
profileType
}
List"]`
);
const
getDropdownComponent
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
getSiteProfilesDropdownItem
=
text
=>
within
(
getDropdownComponent
().
element
).
queryByText
(
text
);
...
...
@@ -125,8 +135,9 @@ describe('EE - DastProfiles', () => {
});
it
.
each
`
tabName | shouldBeSelectedByDefault
${
'
Site Profiles
'
}
|
${
true
}
tabName | shouldBeSelectedByDefault
${
'
Site Profiles
'
}
|
${
true
}
${
'
Scanner Profiles
'
}
|
${
false
}
`
(
'
shows a "$tabName" tab which has "selected" set to "$shouldBeSelectedByDefault"
'
,
({
tabName
,
shouldBeSelectedByDefault
})
=>
{
...
...
@@ -140,65 +151,70 @@ describe('EE - DastProfiles', () => {
);
});
describe
(
'
site profiles
'
,
()
=>
{
describe
.
each
`
description | profileType
${
'
Site Profiles List
'
}
|
${
'
siteProfiles
'
}
${
'
Scanner Profiles List
'
}
|
${
'
scannerProfiles
'
}
`
(
'
$description
'
,
({
profileType
})
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
passes down the correct default props
'
,
()
=>
{
expect
(
get
SiteProfilesComponent
(
).
props
()).
toEqual
({
expect
(
get
ProfilesComponent
(
profileType
).
props
()).
toEqual
({
errorMessage
:
''
,
errorDetails
:
[],
hasMoreProfilesToLoad
:
false
,
isLoading
:
false
,
profilesPerPage
:
expect
.
any
(
Number
),
profiles
:
[],
fields
:
expect
.
any
(
Array
),
});
});
it
.
each
([
true
,
false
])(
'
passes down the loading state
'
,
loading
=>
{
createComponent
({
mocks
:
{
$apollo
:
{
queries
:
{
siteProfiles
:
{
loading
}
}
}
}
});
it
.
each
([
true
,
false
])(
'
passes down the loading state
when loading is "%s"
'
,
loading
=>
{
createComponent
({
mocks
:
{
$apollo
:
{
queries
:
{
[
profileType
]
:
{
loading
}
}
}
}
});
expect
(
get
SiteProfilesComponent
(
).
props
(
'
isLoading
'
)).
toBe
(
loading
);
expect
(
get
ProfilesComponent
(
profileType
).
props
(
'
isLoading
'
)).
toBe
(
loading
);
});
it
.
each
`
givenData | propName | expectedPropValue
${{
errorMessage
:
'
foo
'
}
} |
${
'
errorMessage
'
}
|
${
'
foo
'
}
${{
siteProfilesPageInfo
:
{
hasNextPage
:
true
}
}} |
${
'
hasMoreProfilesToLoad
'
}
|
${
true
}
${{
siteProfiles
:
[{
foo
:
'
bar
'
}]
}
}
|
${
'
profiles
'
}
|
${[{
foo
:
'
bar
'
}]}
givenData
| propName | expectedPropValue
${{
errorMessage
:
'
foo
'
}
}
|
${
'
errorMessage
'
}
|
${
'
foo
'
}
${{
profileTypes
:
{
[
profileType
]:
{
pageInfo
:
{
hasNextPage
:
true
}
}
} }} |
${
'
hasMoreProfilesToLoad
'
}
|
${
true
}
${{
profileTypes
:
{
[
profileType
]:
{
profiles
:
[{
foo
:
'
bar
'
}]
}
} }}
|
${
'
profiles
'
}
|
${[{
foo
:
'
bar
'
}]}
`
(
'
passes down $propName correctly
'
,
async
({
givenData
,
propName
,
expectedPropValue
})
=>
{
wrapper
.
setData
(
givenData
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
get
SiteProfilesComponent
(
).
props
(
propName
)).
toEqual
(
expectedPropValue
);
expect
(
get
ProfilesComponent
(
profileType
).
props
(
propName
)).
toEqual
(
expectedPropValue
);
});
it
(
'
fetches more results when "@load
MoreP
rofiles" is emitted
'
,
()
=>
{
it
(
'
fetches more results when "@load
-more-p
rofiles" is emitted
'
,
()
=>
{
const
{
$apollo
:
{
queries
:
{
siteProfiles
:
{
fetchMore
},
[
profileType
]
:
{
fetchMore
},
},
},
}
=
wrapper
.
vm
;
expect
(
fetchMore
).
not
.
toHaveBeenCalled
();
get
SiteProfilesComponent
().
vm
.
$emit
(
'
loadMoreP
rofiles
'
);
get
ProfilesComponent
(
profileType
).
vm
.
$emit
(
'
load-more-p
rofiles
'
);
expect
(
fetchMore
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
deletes profile when "@delete
P
rofile" is emitted
'
,
()
=>
{
it
(
'
deletes profile when "@delete
-p
rofile" is emitted
'
,
()
=>
{
const
{
$apollo
:
{
mutate
},
}
=
wrapper
.
vm
;
expect
(
mutate
).
not
.
toHaveBeenCalled
();
get
SiteProfilesComponent
().
vm
.
$emit
(
'
deleteP
rofile
'
);
get
ProfilesComponent
(
profileType
).
vm
.
$emit
(
'
delete-p
rofile
'
);
expect
(
mutate
).
toHaveBeenCalledTimes
(
1
);
});
...
...
ee/spec/frontend/dast_profiles/graphql/cache_utils_spec.js
View file @
aa965b0c
import
{
appendToPreviousResult
,
removeProfile
,
dast
Site
ProfilesDeleteResponse
,
dastProfilesDeleteResponse
,
}
from
'
ee/dast_profiles/graphql/cache_utils
'
;
describe
(
'
EE - DastProfiles GraphQL CacheUtils
'
,
()
=>
{
describe
(
'
appendToPreviousResult
'
,
()
=>
{
it
(
'
appends new results to previous
'
,
()
=>
{
const
previousResult
=
{
project
:
{
siteProfiles
:
{
edges
:
[
'
foo
'
]
}
}
};
const
fetchMoreResult
=
{
project
:
{
siteProfiles
:
{
edges
:
[
'
bar
'
]
}
}
};
it
.
each
([
'
siteProfiles
'
,
'
scannerProfiles
'
])(
'
appends new results to previous
'
,
profileType
=>
{
const
previousResult
=
{
project
:
{
[
profileType
]
:
{
edges
:
[
'
foo
'
]
}
}
};
const
fetchMoreResult
=
{
project
:
{
[
profileType
]
:
{
edges
:
[
'
bar
'
]
}
}
};
const
expected
=
{
project
:
{
siteProfiles
:
{
edges
:
[
'
foo
'
,
'
bar
'
]
}
}
};
const
result
=
appendToPreviousResult
(
previousResult
,
{
fetchMoreResult
});
const
expected
=
{
project
:
{
[
profileType
]
:
{
edges
:
[
'
foo
'
,
'
bar
'
]
}
}
};
const
result
=
appendToPreviousResult
(
pr
ofileType
)(
pr
eviousResult
,
{
fetchMoreResult
});
expect
(
result
).
toEqual
(
expected
);
});
});
describe
(
'
removeProfile
'
,
()
=>
{
it
(
'
removes the profile with the given id from the cache
'
,
()
=>
{
it
.
each
([
'
foo
'
,
'
bar
'
])(
'
removes the profile with the given id from the cache
'
,
profileType
=>
{
const
mockQueryBody
=
{
query
:
'
foo
'
,
variables
:
{
foo
:
'
bar
'
}
};
const
mockProfiles
=
[{
id
:
0
},
{
id
:
1
}];
const
mockData
=
{
project
:
{
siteProfiles
:
{
[
profileType
]
:
{
edges
:
[{
node
:
mockProfiles
[
0
]
},
{
node
:
mockProfiles
[
1
]
}],
},
},
...
...
@@ -36,14 +36,15 @@ describe('EE - DastProfiles GraphQL CacheUtils', () => {
removeProfile
({
store
:
mockStore
,
queryBody
:
mockQueryBody
,
profileToBeDeletedId
:
mockProfiles
[
0
].
id
,
profileId
:
mockProfiles
[
0
].
id
,
profileType
,
});
expect
(
mockStore
.
writeQuery
).
toHaveBeenCalledWith
({
...
mockQueryBody
,
data
:
{
project
:
{
siteProfiles
:
{
[
profileType
]
:
{
edges
:
[{
node
:
mockProfiles
[
1
]
}],
},
},
...
...
@@ -52,12 +53,20 @@ describe('EE - DastProfiles GraphQL CacheUtils', () => {
});
});
describe
(
'
dast
Site
ProfilesDeleteResponse
'
,
()
=>
{
describe
(
'
dastProfilesDeleteResponse
'
,
()
=>
{
it
(
'
returns a mutation response with the correct shape
'
,
()
=>
{
expect
(
dastSiteProfilesDeleteResponse
()).
toEqual
({
const
mockMutationName
=
'
mutationName
'
;
const
mockPayloadTypeName
=
'
payloadTypeName
'
;
expect
(
dastProfilesDeleteResponse
({
mutationName
:
mockMutationName
,
payloadTypeName
:
mockPayloadTypeName
,
}),
).
toEqual
({
__typename
:
'
Mutation
'
,
dastSiteProfileDelete
:
{
__typename
:
'
DastSiteProfileDeletePayload
'
,
[
mockMutationName
]
:
{
__typename
:
mockPayloadTypeName
,
errors
:
[],
},
});
...
...
locale/gitlab.pot
View file @
aa965b0c
...
...
@@ -7728,12 +7728,21 @@ msgstr ""
msgid "DastProfiles|Could not create the site profile. Please try again."
msgstr ""
msgid "DastProfiles|Could not delete scanner profile. Please refresh the page, or try again later."
msgstr ""
msgid "DastProfiles|Could not delete scanner profiles:"
msgstr ""
msgid "DastProfiles|Could not delete site profile. Please refresh the page, or try again later."
msgstr ""
msgid "DastProfiles|Could not delete site profiles:"
msgstr ""
msgid "DastProfiles|Could not fetch scanner profiles. Please refresh the page, or try again later."
msgstr ""
msgid "DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later."
msgstr ""
...
...
@@ -7746,6 +7755,9 @@ msgstr ""
msgid "DastProfiles|Do you want to discard your changes?"
msgstr ""
msgid "DastProfiles|Edit feature will come soon. Please create a new profile if changes needed"
msgstr ""
msgid "DastProfiles|Edit site profile"
msgstr ""
...
...
@@ -7788,6 +7800,9 @@ msgstr ""
msgid "DastProfiles|Scanner Profile"
msgstr ""
msgid "DastProfiles|Scanner Profiles"
msgstr ""
msgid "DastProfiles|Site Profile"
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