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
3b4adf96
Commit
3b4adf96
authored
Dec 15, 2021
by
Lee Tickett
Committed by
James Fargher
Dec 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add edit CRM contact UI
parent
15b3414b
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
425 additions
and
90 deletions
+425
-90
app/assets/javascripts/crm/components/contact_form.vue
app/assets/javascripts/crm/components/contact_form.vue
+92
-38
app/assets/javascripts/crm/components/contacts_root.vue
app/assets/javascripts/crm/components/contacts_root.vue
+48
-10
app/assets/javascripts/crm/components/queries/update_contact.mutation.graphql
...ts/crm/components/queries/update_contact.mutation.graphql
+10
-0
app/assets/javascripts/crm/constants.js
app/assets/javascripts/crm/constants.js
+3
-0
app/assets/javascripts/crm/contacts_bundle.js
app/assets/javascripts/crm/contacts_bundle.js
+2
-8
app/assets/javascripts/crm/routes.js
app/assets/javascripts/crm/routes.js
+20
-0
app/assets/javascripts/graphql_shared/constants.js
app/assets/javascripts/graphql_shared/constants.js
+4
-3
app/controllers/groups/crm/contacts_controller.rb
app/controllers/groups/crm/contacts_controller.rb
+4
-0
config/routes/group.rb
config/routes/group.rb
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+6
-3
spec/frontend/crm/contact_form_spec.js
spec/frontend/crm/contact_form_spec.js
+61
-14
spec/frontend/crm/contacts_root_spec.js
spec/frontend/crm/contacts_root_spec.js
+45
-13
spec/frontend/crm/mock_data.js
spec/frontend/crm/mock_data.js
+28
-0
spec/requests/groups/crm/contacts_controller_spec.rb
spec/requests/groups/crm/contacts_controller_spec.rb
+101
-0
No files found.
app/assets/javascripts/crm/components/
new_
contact_form.vue
→
app/assets/javascripts/crm/components/contact_form.vue
View file @
3b4adf96
...
...
@@ -4,7 +4,8 @@ import { produce } from 'immer';
import
{
__
,
s__
}
from
'
~/locale
'
;
import
{
convertToGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
TYPE_GROUP
}
from
'
~/graphql_shared/constants
'
;
import
createContact
from
'
./queries/create_contact.mutation.graphql
'
;
import
createContactMutation
from
'
./queries/create_contact.mutation.graphql
'
;
import
updateContactMutation
from
'
./queries/update_contact.mutation.graphql
'
;
import
getGroupContactsQuery
from
'
./queries/get_group_contacts.query.graphql
'
;
export
default
{
...
...
@@ -21,6 +22,11 @@ export default {
type
:
Boolean
,
required
:
true
,
},
contact
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
{},
},
},
data
()
{
return
{
...
...
@@ -35,66 +41,111 @@ export default {
},
computed
:
{
invalid
()
{
return
this
.
firstName
===
''
||
this
.
lastName
===
''
||
this
.
email
===
''
;
const
{
firstName
,
lastName
,
email
}
=
this
;
return
firstName
.
trim
()
===
''
||
lastName
.
trim
()
===
''
||
email
.
trim
()
===
''
;
},
editMode
()
{
return
Boolean
(
this
.
contact
);
},
title
()
{
return
this
.
editMode
?
this
.
$options
.
i18n
.
editTitle
:
this
.
$options
.
i18n
.
newTitle
;
},
buttonLabel
()
{
return
this
.
editMode
?
this
.
$options
.
i18n
.
editButtonLabel
:
this
.
$options
.
i18n
.
createButtonLabel
;
},
mutation
()
{
return
this
.
editMode
?
updateContactMutation
:
createContactMutation
;
},
variables
()
{
const
{
contact
,
firstName
,
lastName
,
phone
,
email
,
description
,
editMode
,
groupId
}
=
this
;
const
variables
=
{
input
:
{
firstName
,
lastName
,
phone
,
email
,
description
,
},
};
if
(
editMode
)
{
variables
.
input
.
id
=
contact
.
id
;
}
else
{
variables
.
input
.
groupId
=
convertToGraphQLId
(
TYPE_GROUP
,
groupId
);
}
return
variables
;
},
},
mounted
()
{
if
(
this
.
editMode
)
{
const
{
contact
}
=
this
;
this
.
firstName
=
contact
.
firstName
||
''
;
this
.
lastName
=
contact
.
lastName
||
''
;
this
.
phone
=
contact
.
phone
||
''
;
this
.
email
=
contact
.
email
||
''
;
this
.
description
=
contact
.
description
||
''
;
}
},
methods
:
{
save
()
{
const
{
mutation
,
variables
,
updateCache
,
close
}
=
this
;
this
.
submitting
=
true
;
return
this
.
$apollo
.
mutate
({
mutation
:
createContact
,
variables
:
{
input
:
{
groupId
:
convertToGraphQLId
(
TYPE_GROUP
,
this
.
groupId
),
firstName
:
this
.
firstName
,
lastName
:
this
.
lastName
,
phone
:
this
.
phone
,
email
:
this
.
email
,
description
:
this
.
description
,
},
},
update
:
this
.
updateCache
,
mutation
,
variables
,
update
:
updateCache
,
})
.
then
(({
data
})
=>
{
if
(
data
.
customerRelationsContactCreate
.
errors
.
length
===
0
)
this
.
close
(
true
);
if
(
data
.
customerRelationsContactCreate
?.
errors
.
length
===
0
||
data
.
customerRelationsContactUpdate
?.
errors
.
length
===
0
)
{
close
(
true
);
}
this
.
submitting
=
false
;
})
.
catch
(()
=>
{
this
.
errorMessages
=
[
__
(
'
Something went wrong. Please try again.
'
)
];
this
.
errorMessages
=
[
this
.
$options
.
i18n
.
somethingWentWrong
];
this
.
submitting
=
false
;
});
},
close
(
success
)
{
this
.
$emit
(
'
close
'
,
success
);
},
updateCache
(
store
,
{
data
:
{
customerRelationsContactCreate
}
})
{
if
(
customerRelationsContactCreate
.
errors
.
length
>
0
)
{
this
.
errorMessages
=
customerRelationsContactCreate
.
errors
;
updateCache
(
store
,
{
data
})
{
const
mutationData
=
data
.
customerRelationsContactCreate
||
data
.
customerRelationsContactUpdate
;
if
(
mutationData
?.
errors
.
length
>
0
)
{
this
.
errorMessages
=
mutationData
.
errors
;
return
;
}
const
variables
=
{
groupFullPath
:
this
.
groupFullPath
,
};
const
sourceData
=
store
.
readQuery
({
const
queryArgs
=
{
query
:
getGroupContactsQuery
,
variables
,
}
)
;
variables
:
{
groupFullPath
:
this
.
groupFullPath
}
,
};
const
data
=
produce
(
sourceData
,
(
draftState
)
=>
{
const
sourceData
=
store
.
readQuery
(
queryArgs
);
queryArgs
.
data
=
produce
(
sourceData
,
(
draftState
)
=>
{
draftState
.
group
.
contacts
.
nodes
=
[
...
sourceData
.
group
.
contacts
.
nodes
,
customerRelationsContactCreate
.
contact
,
...
sourceData
.
group
.
contacts
.
nodes
.
filter
(({
id
})
=>
id
!==
this
.
contact
?.
id
)
,
mutationData
.
contact
,
];
});
store
.
writeQuery
({
query
:
getGroupContactsQuery
,
variables
,
data
,
});
store
.
writeQuery
(
queryArgs
);
},
getDrawerHeaderHeight
()
{
const
wrapperEl
=
document
.
querySelector
(
'
.content-wrapper
'
);
...
...
@@ -107,14 +158,17 @@ export default {
},
},
i18n
:
{
buttonLabel
:
s__
(
'
Crm|Create new contact
'
),
createButtonLabel
:
s__
(
'
Crm|Create new contact
'
),
editButtonLabel
:
__
(
'
Save changes
'
),
cancel
:
__
(
'
Cancel
'
),
firstName
:
s__
(
'
Crm|First name
'
),
lastName
:
s__
(
'
Crm|Last name
'
),
email
:
s__
(
'
Crm|Email
'
),
phone
:
s__
(
'
Crm|Phone number (optional)
'
),
description
:
s__
(
'
Crm|Description (optional)
'
),
title
:
s__
(
'
Crm|New Contact
'
),
newTitle
:
s__
(
'
Crm|New contact
'
),
editTitle
:
s__
(
'
Crm|Edit contact
'
),
somethingWentWrong
:
__
(
'
Something went wrong. Please try again.
'
),
},
};
</
script
>
...
...
@@ -127,7 +181,7 @@ export default {
@
close=
"close(false)"
>
<template
#title
>
<h
4>
{{
$options
.
i18n
.
title
}}
</h4
>
<h
3>
{{
title
}}
</h3
>
</
template
>
<gl-alert
v-if=
"errorMessages.length"
variant=
"danger"
@
dismiss=
"errorMessages = []"
>
<ul
class=
"gl-mb-0! gl-ml-5"
>
...
...
@@ -160,9 +214,9 @@ export default {
variant=
"confirm"
:disabled=
"invalid"
:loading=
"submitting"
data-testid=
"
create-new
-contact-button"
data-testid=
"
save
-contact-button"
type=
"submit"
>
{{
$options.i18n.
buttonLabel }}
</gl-button
>
{{ buttonLabel }}
</gl-button
>
</span>
</form>
...
...
app/assets/javascripts/crm/components/contacts_root.vue
View file @
3b4adf96
...
...
@@ -2,9 +2,11 @@
import
{
GlAlert
,
GlButton
,
GlLoadingIcon
,
GlTable
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
convertToGraphQLId
,
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
TYPE_CRM_CONTACT
}
from
'
~/graphql_shared/constants
'
;
import
{
INDEX_ROUTE_NAME
,
NEW_ROUTE_NAME
,
EDIT_ROUTE_NAME
}
from
'
../constants
'
;
import
getGroupContactsQuery
from
'
./queries/get_group_contacts.query.graphql
'
;
import
NewContactForm
from
'
./new_
contact_form.vue
'
;
import
ContactForm
from
'
./
contact_form.vue
'
;
export
default
{
components
:
{
...
...
@@ -12,7 +14,7 @@ export default {
GlButton
,
GlLoadingIcon
,
GlTable
,
New
ContactForm
,
ContactForm
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
...
...
@@ -47,11 +49,19 @@ export default {
return
this
.
$apollo
.
queries
.
contacts
.
loading
;
},
showNewForm
()
{
return
this
.
$route
.
path
.
startsWith
(
'
/new
'
)
;
return
this
.
$route
.
name
===
NEW_ROUTE_NAME
;
},
canCreateNew
()
{
showEditForm
()
{
return
!
this
.
isLoading
&&
this
.
$route
.
name
===
EDIT_ROUTE_NAME
;
},
canAdmin
()
{
return
parseBoolean
(
this
.
canAdminCrmContact
);
},
editingContact
()
{
return
this
.
contacts
.
find
(
(
contact
)
=>
contact
.
id
===
convertToGraphQLId
(
TYPE_CRM_CONTACT
,
this
.
$route
.
params
.
id
),
);
},
},
methods
:
{
extractContacts
(
data
)
{
...
...
@@ -61,16 +71,28 @@ export default {
displayNewForm
()
{
if
(
this
.
showNewForm
)
return
;
this
.
$router
.
push
({
path
:
'
/new
'
});
this
.
$router
.
push
({
name
:
NEW_ROUTE_NAME
});
},
hideNewForm
(
success
)
{
if
(
success
)
this
.
$toast
.
show
(
s__
(
'
Crm|Contact has been added
'
));
this
.
$router
.
replace
({
path
:
'
/
'
});
this
.
$router
.
replace
({
name
:
INDEX_ROUTE_NAME
});
},
hideEditForm
(
success
)
{
if
(
success
)
this
.
$toast
.
show
(
s__
(
'
Crm|Contact has been updated
'
));
this
.
editingContactId
=
0
;
this
.
$router
.
replace
({
name
:
INDEX_ROUTE_NAME
});
},
getIssuesPath
(
path
,
value
)
{
return
`
${
path
}
?scope=all&state=opened&crm_contact_id=
${
value
}
`
;
},
edit
(
value
)
{
if
(
this
.
showEditForm
)
return
;
this
.
editingContactId
=
value
;
this
.
$router
.
push
({
name
:
EDIT_ROUTE_NAME
,
params
:
{
id
:
value
}
});
},
},
fields
:
[
{
key
:
'
firstName
'
,
sortable
:
true
},
...
...
@@ -87,7 +109,7 @@ export default {
},
{
key
:
'
id
'
,
label
:
__
(
'
Issues
'
)
,
label
:
''
,
formatter
:
(
id
)
=>
{
return
getIdFromGraphQLId
(
id
);
},
...
...
@@ -96,6 +118,7 @@ export default {
i18n
:
{
emptyText
:
s__
(
'
Crm|No contacts found
'
),
issuesButtonLabel
:
__
(
'
View issues
'
),
editButtonLabel
:
__
(
'
Edit
'
),
title
:
s__
(
'
Crm|Customer Relations Contacts
'
),
newContact
:
s__
(
'
Crm|New contact
'
),
errorText
:
__
(
'
Something went wrong. Please try again.
'
),
...
...
@@ -116,7 +139,7 @@ export default {
</h2>
<div
class=
"gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end"
>
<gl-button
v-if=
"can
CreateNew
"
v-if=
"can
Admin
"
variant=
"confirm"
data-testid=
"new-contact-button"
@
click=
"displayNewForm"
...
...
@@ -125,7 +148,13 @@ export default {
</gl-button>
</div>
</div>
<new-contact-form
v-if=
"showNewForm"
:drawer-open=
"showNewForm"
@
close=
"hideNewForm"
/>
<contact-form
v-if=
"showNewForm"
:drawer-open=
"showNewForm"
@
close=
"hideNewForm"
/>
<contact-form
v-if=
"showEditForm"
:contact=
"editingContact"
:drawer-open=
"showEditForm"
@
close=
"hideEditForm"
/>
<gl-loading-icon
v-if=
"isLoading"
class=
"gl-mt-5"
size=
"lg"
/>
<gl-table
v-else
...
...
@@ -138,11 +167,20 @@ export default {
<template
#cell(id)=
"data"
>
<gl-button
v-gl-tooltip.hover.bottom=
"$options.i18n.issuesButtonLabel"
class=
"gl-mr-3"
data-testid=
"issues-link"
icon=
"issues"
:aria-label=
"$options.i18n.issuesButtonLabel"
:href=
"getIssuesPath(groupIssuesPath, data.value)"
/>
<gl-button
v-if=
"canAdmin"
v-gl-tooltip.hover.bottom=
"$options.i18n.editButtonLabel"
data-testid=
"edit-contact-button"
icon=
"pencil"
:aria-label=
"$options.i18n.editButtonLabel"
@
click=
"edit(data.value)"
/>
</
template
>
</gl-table>
</div>
...
...
app/assets/javascripts/crm/components/queries/update_contact.mutation.graphql
0 → 100644
View file @
3b4adf96
#import "./crm_contact_fields.fragment.graphql"
mutation
updateContact
(
$input
:
CustomerRelationsContactUpdateInput
!)
{
customerRelationsContactUpdate
(
input
:
$input
)
{
contact
{
...
ContactFragment
}
errors
}
}
app/assets/javascripts/crm/constants.js
0 → 100644
View file @
3b4adf96
export
const
INDEX_ROUTE_NAME
=
'
index
'
;
export
const
NEW_ROUTE_NAME
=
'
new
'
;
export
const
EDIT_ROUTE_NAME
=
'
edit
'
;
app/assets/javascripts/crm/contacts_bundle.js
View file @
3b4adf96
...
...
@@ -4,6 +4,7 @@ import VueApollo from 'vue-apollo';
import
VueRouter
from
'
vue-router
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
CrmContactsRoot
from
'
./components/contacts_root.vue
'
;
import
routes
from
'
./routes
'
;
Vue
.
use
(
VueApollo
);
Vue
.
use
(
VueRouter
);
...
...
@@ -25,14 +26,7 @@ export default () => {
const
router
=
new
VueRouter
({
base
:
basePath
,
mode
:
'
history
'
,
routes
:
[
{
// eslint-disable-next-line @gitlab/require-i18n-strings
name
:
'
Contacts List
'
,
path
:
'
/
'
,
component
:
CrmContactsRoot
,
},
],
routes
,
});
return
new
Vue
({
...
...
app/assets/javascripts/crm/routes.js
0 → 100644
View file @
3b4adf96
import
{
INDEX_ROUTE_NAME
,
NEW_ROUTE_NAME
,
EDIT_ROUTE_NAME
}
from
'
./constants
'
;
import
CrmContactsRoot
from
'
./components/contacts_root.vue
'
;
export
default
[
{
name
:
INDEX_ROUTE_NAME
,
path
:
'
/
'
,
component
:
CrmContactsRoot
,
},
{
name
:
NEW_ROUTE_NAME
,
path
:
'
/new
'
,
component
:
CrmContactsRoot
,
},
{
name
:
EDIT_ROUTE_NAME
,
path
:
'
/:id/edit
'
,
component
:
CrmContactsRoot
,
},
];
app/assets/javascripts/graphql_shared/constants.js
View file @
3b4adf96
export
const
MINIMUM_SEARCH_LENGTH
=
3
;
export
const
TYPE_CI_RUNNER
=
'
Ci::Runner
'
;
export
const
TYPE_CRM_CONTACT
=
'
CustomerRelations::Contact
'
;
export
const
TYPE_DISCUSSION
=
'
Discussion
'
;
export
const
TYPE_EPIC
=
'
Epic
'
;
export
const
TYPE_GROUP
=
'
Group
'
;
export
const
TYPE_ISSUE
=
'
Issue
'
;
...
...
@@ -8,11 +10,10 @@ export const TYPE_ITERATION = 'Iteration';
export
const
TYPE_ITERATIONS_CADENCE
=
'
Iterations::Cadence
'
;
export
const
TYPE_MERGE_REQUEST
=
'
MergeRequest
'
;
export
const
TYPE_MILESTONE
=
'
Milestone
'
;
export
const
TYPE_NOTE
=
'
Note
'
;
export
const
TYPE_PACKAGES_PACKAGE
=
'
Packages::Package
'
;
export
const
TYPE_PROJECT
=
'
Project
'
;
export
const
TYPE_SCANNER_PROFILE
=
'
DastScannerProfile
'
;
export
const
TYPE_SITE_PROFILE
=
'
DastSiteProfile
'
;
export
const
TYPE_USER
=
'
User
'
;
export
const
TYPE_VULNERABILITY
=
'
Vulnerability
'
;
export
const
TYPE_NOTE
=
'
Note
'
;
export
const
TYPE_DISCUSSION
=
'
Discussion
'
;
export
const
TYPE_PACKAGES_PACKAGE
=
'
Packages::Package
'
;
app/controllers/groups/crm/contacts_controller.rb
View file @
3b4adf96
...
...
@@ -9,6 +9,10 @@ class Groups::Crm::ContactsController < Groups::ApplicationController
render
action:
"index"
end
def
edit
render
action:
"index"
end
private
def
authorize_read_crm_contact!
...
...
config/routes/group.rb
View file @
3b4adf96
...
...
@@ -127,7 +127,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end
namespace
:crm
do
resources
:contacts
,
only:
[
:index
,
:new
]
resources
:contacts
,
only:
[
:index
,
:new
,
:edit
]
resources
:organizations
,
only:
[
:index
]
end
end
...
...
locale/gitlab.pot
View file @
3b4adf96
...
...
@@ -10264,6 +10264,9 @@ msgstr ""
msgid "Crm|Contact has been added"
msgstr ""
msgid "Crm|Contact has been updated"
msgstr ""
msgid "Crm|Create new contact"
msgstr ""
...
...
@@ -10273,6 +10276,9 @@ msgstr ""
msgid "Crm|Description (optional)"
msgstr ""
msgid "Crm|Edit contact"
msgstr ""
msgid "Crm|Email"
msgstr ""
...
...
@@ -10282,9 +10288,6 @@ msgstr ""
msgid "Crm|Last name"
msgstr ""
msgid "Crm|New Contact"
msgstr ""
msgid "Crm|New contact"
msgstr ""
...
...
spec/frontend/crm/
new_
contact_form_spec.js
→
spec/frontend/crm/contact_form_spec.js
View file @
3b4adf96
...
...
@@ -4,41 +4,49 @@ import VueApollo from 'vue-apollo';
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
NewContactForm
from
'
~/crm/components/new_
contact_form.vue
'
;
import
ContactForm
from
'
~/crm/components/
contact_form.vue
'
;
import
createContactMutation
from
'
~/crm/components/queries/create_contact.mutation.graphql
'
;
import
updateContactMutation
from
'
~/crm/components/queries/update_contact.mutation.graphql
'
;
import
getGroupContactsQuery
from
'
~/crm/components/queries/get_group_contacts.query.graphql
'
;
import
{
createContactMutationErrorResponse
,
createContactMutationResponse
,
getGroupContactsQueryResponse
,
updateContactMutationErrorResponse
,
updateContactMutationResponse
,
}
from
'
./mock_data
'
;
describe
(
'
Customer relations contact
s root app
'
,
()
=>
{
describe
(
'
Customer relations contact
form component
'
,
()
=>
{
Vue
.
use
(
VueApollo
);
let
wrapper
;
let
fakeApollo
;
let
mutation
;
let
queryHandler
;
const
find
CreateNewContactButton
=
()
=>
wrapper
.
findByTestId
(
'
create-new
-contact-button
'
);
const
find
SaveContactButton
=
()
=>
wrapper
.
findByTestId
(
'
save
-contact-button
'
);
const
findCancelButton
=
()
=>
wrapper
.
findByTestId
(
'
cancel-button
'
);
const
findForm
=
()
=>
wrapper
.
find
(
'
form
'
);
const
findError
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
mountComponent
=
({
mountFunction
=
shallowMountExtended
}
=
{})
=>
{
fakeApollo
=
createMockApollo
([[
createContactM
utation
,
queryHandler
]]);
const
mountComponent
=
({
mountFunction
=
shallowMountExtended
,
editForm
=
false
}
=
{})
=>
{
fakeApollo
=
createMockApollo
([[
m
utation
,
queryHandler
]]);
fakeApollo
.
clients
.
defaultClient
.
cache
.
writeQuery
({
query
:
getGroupContactsQuery
,
variables
:
{
groupFullPath
:
'
flightjs
'
},
data
:
getGroupContactsQueryResponse
.
data
,
});
wrapper
=
mountFunction
(
NewContactForm
,
{
const
propsData
=
{
drawerOpen
:
true
};
if
(
editForm
)
propsData
.
contact
=
{
firstName
:
'
First
'
,
lastName
:
'
Last
'
,
email
:
'
email@example.com
'
};
wrapper
=
mountFunction
(
ContactForm
,
{
provide
:
{
groupId
:
26
,
groupFullPath
:
'
flightjs
'
},
apolloProvider
:
fakeApollo
,
propsData
:
{
drawerOpen
:
true
}
,
propsData
,
});
};
beforeEach
(()
=>
{
mutation
=
createContactMutation
;
queryHandler
=
jest
.
fn
().
mockResolvedValue
(
createContactMutationResponse
);
});
...
...
@@ -47,14 +55,14 @@ describe('Customer relations contacts root app', () => {
fakeApollo
=
null
;
});
describe
(
'
Create new
contact button
'
,
()
=>
{
it
(
'
should be disabled
by default
'
,
()
=>
{
describe
(
'
Save
contact button
'
,
()
=>
{
it
(
'
should be disabled
when required fields are empty
'
,
()
=>
{
mountComponent
();
expect
(
find
CreateNewContactButton
().
attributes
(
'
disabled
'
)).
toBeTruthy
(
);
expect
(
find
SaveContactButton
().
props
(
'
disabled
'
)).
toBe
(
true
);
});
it
(
'
should not be disabled when
first, last and email
have values
'
,
async
()
=>
{
it
(
'
should not be disabled when
required fields
have values
'
,
async
()
=>
{
mountComponent
();
wrapper
.
find
(
'
#contact-first-name
'
).
vm
.
$emit
(
'
input
'
,
'
A
'
);
...
...
@@ -62,7 +70,7 @@ describe('Customer relations contacts root app', () => {
wrapper
.
find
(
'
#contact-email
'
).
vm
.
$emit
(
'
input
'
,
'
C
'
);
await
waitForPromises
();
expect
(
find
CreateNewContactButton
().
attributes
(
'
disabled
'
)).
toBeFalsy
(
);
expect
(
find
SaveContactButton
().
props
(
'
disabled
'
)).
toBe
(
false
);
});
});
...
...
@@ -74,7 +82,7 @@ describe('Customer relations contacts root app', () => {
expect
(
wrapper
.
emitted
().
close
).
toBeTruthy
();
});
describe
(
'
when
query
is successful
'
,
()
=>
{
describe
(
'
when
create mutation
is successful
'
,
()
=>
{
it
(
"
should emit 'close'
"
,
async
()
=>
{
mountComponent
();
...
...
@@ -85,7 +93,7 @@ describe('Customer relations contacts root app', () => {
});
});
describe
(
'
when
query
fails
'
,
()
=>
{
describe
(
'
when
create mutation
fails
'
,
()
=>
{
it
(
'
should show error on reject
'
,
async
()
=>
{
queryHandler
=
jest
.
fn
().
mockRejectedValue
(
'
ERROR
'
);
mountComponent
();
...
...
@@ -107,4 +115,43 @@ describe('Customer relations contacts root app', () => {
expect
(
findError
().
text
()).
toBe
(
'
Phone is invalid.
'
);
});
});
describe
(
'
when update mutation is successful
'
,
()
=>
{
it
(
"
should emit 'close'
"
,
async
()
=>
{
mutation
=
updateContactMutation
;
queryHandler
=
jest
.
fn
().
mockResolvedValue
(
updateContactMutationResponse
);
mountComponent
({
editForm
:
true
});
findForm
().
trigger
(
'
submit
'
);
await
waitForPromises
();
expect
(
wrapper
.
emitted
().
close
).
toBeTruthy
();
});
});
describe
(
'
when update mutation fails
'
,
()
=>
{
beforeEach
(()
=>
{
mutation
=
updateContactMutation
;
});
it
(
'
should show error on reject
'
,
async
()
=>
{
queryHandler
=
jest
.
fn
().
mockRejectedValue
(
'
ERROR
'
);
mountComponent
({
editForm
:
true
});
findForm
().
trigger
(
'
submit
'
);
await
waitForPromises
();
expect
(
findError
().
exists
()).
toBe
(
true
);
});
it
(
'
should show error on error response
'
,
async
()
=>
{
queryHandler
=
jest
.
fn
().
mockResolvedValue
(
updateContactMutationErrorResponse
);
mountComponent
({
editForm
:
true
});
findForm
().
trigger
(
'
submit
'
);
await
waitForPromises
();
expect
(
findError
().
exists
()).
toBe
(
true
);
expect
(
findError
().
text
()).
toBe
(
'
Email is invalid.
'
);
});
});
});
spec/frontend/crm/contacts_root_spec.js
View file @
3b4adf96
...
...
@@ -6,8 +6,10 @@ import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_help
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
ContactsRoot
from
'
~/crm/components/contacts_root.vue
'
;
import
NewContactForm
from
'
~/crm/components/new_
contact_form.vue
'
;
import
ContactForm
from
'
~/crm/components/
contact_form.vue
'
;
import
getGroupContactsQuery
from
'
~/crm/components/queries/get_group_contacts.query.graphql
'
;
import
{
NEW_ROUTE_NAME
,
EDIT_ROUTE_NAME
}
from
'
~/crm/constants
'
;
import
routes
from
'
~/crm/routes
'
;
import
{
getGroupContactsQueryResponse
}
from
'
./mock_data
'
;
describe
(
'
Customer relations contacts root app
'
,
()
=>
{
...
...
@@ -21,7 +23,8 @@ describe('Customer relations contacts root app', () => {
const
findRowByName
=
(
rowName
)
=>
wrapper
.
findAllByRole
(
'
row
'
,
{
name
:
rowName
});
const
findIssuesLinks
=
()
=>
wrapper
.
findAllByTestId
(
'
issues-link
'
);
const
findNewContactButton
=
()
=>
wrapper
.
findByTestId
(
'
new-contact-button
'
);
const
findNewContactForm
=
()
=>
wrapper
.
findComponent
(
NewContactForm
);
const
findEditContactButton
=
()
=>
wrapper
.
findByTestId
(
'
edit-contact-button
'
);
const
findContactForm
=
()
=>
wrapper
.
findComponent
(
ContactForm
);
const
findError
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
successQueryHandler
=
jest
.
fn
().
mockResolvedValue
(
getGroupContactsQueryResponse
);
...
...
@@ -49,7 +52,7 @@ describe('Customer relations contacts root app', () => {
router
=
new
VueRouter
({
base
:
basePath
,
mode
:
'
history
'
,
routes
:
[]
,
routes
,
});
});
...
...
@@ -79,12 +82,12 @@ describe('Customer relations contacts root app', () => {
});
});
describe
(
'
new
contact form
'
,
()
=>
{
describe
(
'
contact form
'
,
()
=>
{
it
(
'
should not exist by default
'
,
async
()
=>
{
mountComponent
();
await
waitForPromises
();
expect
(
find
New
ContactForm
().
exists
()).
toBe
(
false
);
expect
(
findContactForm
().
exists
()).
toBe
(
false
);
});
it
(
'
should exist when user clicks new contact button
'
,
async
()
=>
{
...
...
@@ -93,25 +96,54 @@ describe('Customer relations contacts root app', () => {
findNewContactButton
().
vm
.
$emit
(
'
click
'
);
await
waitForPromises
();
expect
(
find
New
ContactForm
().
exists
()).
toBe
(
true
);
expect
(
findContactForm
().
exists
()).
toBe
(
true
);
});
it
(
'
should exist when user navigates directly to
/new
'
,
async
()
=>
{
router
.
replace
({
path
:
'
/new
'
});
it
(
'
should exist when user navigates directly to
`new` route
'
,
async
()
=>
{
router
.
replace
({
name
:
NEW_ROUTE_NAME
});
mountComponent
();
await
waitForPromises
();
expect
(
find
New
ContactForm
().
exists
()).
toBe
(
true
);
expect
(
findContactForm
().
exists
()).
toBe
(
true
);
});
it
(
'
should not exist when form emits close
'
,
async
()
=>
{
router
.
replace
({
path
:
'
/new
'
});
it
(
'
should exist when user clicks edit contact button
'
,
async
()
=>
{
mountComponent
({
mountFunction
:
mountExtended
});
await
waitForPromises
();
findEditContactButton
().
vm
.
$emit
(
'
click
'
);
await
waitForPromises
();
expect
(
findContactForm
().
exists
()).
toBe
(
true
);
});
it
(
'
should exist when user navigates directly to `edit` route
'
,
async
()
=>
{
router
.
replace
({
name
:
EDIT_ROUTE_NAME
,
params
:
{
id
:
16
}
});
mountComponent
();
await
waitForPromises
();
expect
(
findContactForm
().
exists
()).
toBe
(
true
);
});
it
(
'
should not exist when new form emits close
'
,
async
()
=>
{
router
.
replace
({
name
:
NEW_ROUTE_NAME
});
mountComponent
();
findContactForm
().
vm
.
$emit
(
'
close
'
);
await
waitForPromises
();
expect
(
findContactForm
().
exists
()).
toBe
(
false
);
});
it
(
'
should not exist when edit form emits close
'
,
async
()
=>
{
router
.
replace
({
name
:
EDIT_ROUTE_NAME
,
params
:
{
id
:
16
}
});
mountComponent
();
await
waitForPromises
();
find
New
ContactForm
().
vm
.
$emit
(
'
close
'
);
findContactForm
().
vm
.
$emit
(
'
close
'
);
await
waitForPromises
();
expect
(
find
New
ContactForm
().
exists
()).
toBe
(
false
);
expect
(
findContactForm
().
exists
()).
toBe
(
false
);
});
});
...
...
spec/frontend/crm/mock_data.js
View file @
3b4adf96
...
...
@@ -106,3 +106,31 @@ export const createContactMutationErrorResponse = {
},
},
};
export
const
updateContactMutationResponse
=
{
data
:
{
customerRelationsContactUpdate
:
{
__typeName
:
'
CustomerRelationsContactCreatePayload
'
,
contact
:
{
__typename
:
'
CustomerRelationsContact
'
,
id
:
'
gid://gitlab/CustomerRelations::Contact/1
'
,
firstName
:
'
First
'
,
lastName
:
'
Last
'
,
email
:
'
email@example.com
'
,
phone
:
null
,
description
:
null
,
organization
:
null
,
},
errors
:
[],
},
},
};
export
const
updateContactMutationErrorResponse
=
{
data
:
{
customerRelationsContactUpdate
:
{
contact
:
null
,
errors
:
[
'
Email is invalid.
'
],
},
},
};
spec/requests/groups/crm/contacts_controller_spec.rb
0 → 100644
View file @
3b4adf96
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Groups
::
Crm
::
ContactsController
do
let_it_be
(
:user
)
{
create
(
:user
)
}
shared_examples
'response with 404 status'
do
it
'returns 404'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
end
shared_examples
'ok response with index template'
do
it
'renders the index template'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
render_template
(
:index
)
end
end
shared_examples
'ok response with index template if authorized'
do
context
'private group'
do
let
(
:group
)
{
create
(
:group
,
:private
)
}
context
'with authorized user'
do
before
do
group
.
add_reporter
(
user
)
sign_in
(
user
)
end
context
'when feature flag is enabled'
do
it_behaves_like
'ok response with index template'
end
context
'when feature flag is not enabled'
do
before
do
stub_feature_flags
(
customer_relations:
false
)
end
it_behaves_like
'response with 404 status'
end
end
context
'with unauthorized user'
do
before
do
sign_in
(
user
)
end
it_behaves_like
'response with 404 status'
end
context
'with anonymous user'
do
it
'blah'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
:found
)
expect
(
response
).
to
redirect_to
(
new_user_session_path
)
end
end
end
context
'public group'
do
let
(
:group
)
{
create
(
:group
,
:public
)
}
context
'with anonymous user'
do
it_behaves_like
'ok response with index template'
end
end
end
describe
'GET #index'
do
subject
do
get
group_crm_contacts_path
(
group
)
response
end
it_behaves_like
'ok response with index template if authorized'
end
describe
'GET #new'
do
subject
do
get
new_group_crm_contact_path
(
group
)
response
end
it_behaves_like
'ok response with index template if authorized'
end
describe
'GET #edit'
do
subject
do
get
edit_group_crm_contact_path
(
group
,
id:
1
)
response
end
it_behaves_like
'ok response with index template if authorized'
end
end
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