feature(notify): Add query and return value for contact UPDATE interface

1. Add same return value with contact GET interface when updating
2. Add pull params for query to pull subcontact by mobile number.
   For example, pull user_id from dingtalk throught user's mobile
   number.
3. Add corresponding feature based on above for climc.
4. UserCache will try FetchByName if there is no id matched user when
   fetching users from KeyStone.
This commit is contained in:
Rain
2020-03-05 17:10:04 +08:00
parent 850544ee4d
commit 5f36cbb31c
9 changed files with 88 additions and 55 deletions
+23 -12
View File
@@ -29,11 +29,11 @@ func init() {
* 操作用户的通信地址(如果用户的通信地址不存在则进行添加;如果已存在则进行修改;如果设置空则进行删除。)
*/
type ContactsUpdateOptions struct {
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
CONTACTTYPE string `help:"The contact type email|mobile" choices:"email|mobile|dingtalk"`
CONTACT string `help:"The contacts details mobile number or email address or dingtalk's userid, if set it the empty str means delete"`
Status string `help:"Enabled or disabled contact status" choices:"enable|disable"`
UpdateDingtalk bool `help:"if update dingtalk"`
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
CONTACTTYPE string `help:"The contact type email|mobile" choices:"email|mobile|dingtalk"`
CONTACT string `help:"The contacts details mobile number or email address or dingtalk's userid, if set it the empty str means delete"`
Status string `help:"Enabled or disabled contact status" choices:"enable|disable"`
Pull string `help:"pull some subcontacts(e.g., dingtalk, feishu, etc) related to mobile"`
}
R(&ContactsUpdateOptions{}, "contact-update", "Create, delete or update contact for user", func(s *mcclient.ClientSession, args *ContactsUpdateOptions) error {
arr := jsonutils.NewArray()
@@ -52,12 +52,8 @@ func init() {
params := jsonutils.NewDict()
params.Add(arr, "contacts")
if args.UpdateDingtalk {
params.Add(jsonutils.JSONTrue, "update_dingtalk")
}
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params)
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", args.Pull, params)
if err != nil {
return err
}
@@ -66,6 +62,21 @@ func init() {
return nil
})
type ContactsPullOptions struct {
UID string `help:"The user you wanna pull contact"`
CONTACTTYPE string `help:"The contact type"`
}
R(&ContactsPullOptions{}, "contact-pull", "Pull contact", func(s *mcclient.ClientSession, args *ContactsPullOptions) error {
params := jsonutils.NewDict()
params.Set("contacts", jsonutils.NewArray())
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", args.CONTACTTYPE, params)
if err != nil {
return err
}
printObject(contact)
return nil
})
type ContactsDeleteOptions struct {
UID string `help:"The user you wanna add contact to (Keystone User ID)"`
CONTACTTYPE string `help:"The contact type email|mobile|dingtalk" choices:"email|mobile|dingtalk"`
@@ -78,7 +89,7 @@ func init() {
arr.Add(tmpObj)
params := jsonutils.NewDict()
params.Add(arr, "contacts")
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", params)
contact, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "update-contact", "", params)
if err != nil {
return err
}
@@ -169,7 +180,7 @@ func init() {
tmpDict := jsonutils.NewDict()
tmpDict.Add(jsonutils.NewString(args.CONTACT_TYPE), "contact_type")
tmpDict.Add(jsonutils.NewString(args.CONTACT), "contact")
_, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "verify", tmpDict)
_, err := modules.Contacts.CustomizedPerformAction(s, args.UID, "verify", "", tmpDict)
if err != nil {
return err
}
+1 -1
View File
@@ -29,7 +29,7 @@ func init() {
*/
type NotificationCreateOptions struct {
CONTACTTYPE string `help:"User's contacts type" choices:"email|mobile|dingtalk|webconsole"`
CONTACTTYPE string `help:"User's contacts type"`
TOPIC string `help:"Title or topic of the notification"`
PRIORITY string `help:"Priority of the notification" choices:"normal|important|fatal"`
MSG string `help:"The content of the notification"`
+9 -4
View File
@@ -115,10 +115,15 @@ func (manager *SUserCacheManager) FetchUserFromKeystone(ctx context.Context, idS
user, err := modules.UsersV3.GetById(s, idStr, nil)
if err != nil {
if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
return nil, sql.ErrNoRows
user, err = modules.UsersV3.GetByName(s, idStr, nil)
if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
return nil, sql.ErrNoRows
}
}
if err != nil {
log.Errorf("fetch user %s fail %s", idStr, err)
return nil, errors.Wrap(err, "modules.UsersV3.Get")
}
log.Errorf("fetch user %s fail %s", idStr, err)
return nil, errors.Wrap(err, "modules.UsersV3.Get")
}
id, _ := user.GetString("id")
name, _ := user.GetString("name")
@@ -133,7 +138,7 @@ func (manager *SUserCacheManager) Save(ctx context.Context, idStr string, name s
objo, err := manager.FetchById(idStr)
if err != nil && err != sql.ErrNoRows {
log.Errorf("FetchTenantbyId fail %s", err)
log.Errorf("FetchUserbyId fail %s", err)
return nil, err
}
if err == nil {
+5 -2
View File
@@ -45,7 +45,7 @@ func (this *ContactsManager) DoBatchDeleteContacts(s *mcclient.ClientSession, pa
return modulebase.Post(this.ResourceManager, s, path, params, this.Keyword)
}
func (this *ContactsManager) CustomizedPerformAction(session *mcclient.ClientSession, id string, action string,
func (this *ContactsManager) CustomizedPerformAction(session *mcclient.ClientSession, id, action, pull string,
params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
body := jsonutils.NewDict()
@@ -53,7 +53,10 @@ func (this *ContactsManager) CustomizedPerformAction(session *mcclient.ClientSes
body.Add(params, this.Keyword)
}
path := fmt.Sprintf("/%s/%s/%s?uname=true", this.ContextPath(nil), url.PathEscape(id), url.PathEscape(action))
return modulebase.Post(this.ResourceManager, session, path, params, this.KeywordPlural)
if len(pull) > 0 {
path += fmt.Sprintf("&pull=%s", pull)
}
return modulebase.Post(this.ResourceManager, session, path, body, this.KeywordPlural)
}
func (this *ContactsManager) CustomizedGet(session *mcclient.ClientSession, id string,
+22 -6
View File
@@ -379,7 +379,7 @@ func (self *NotifyModelDispatcher) DeleteContacts(ctx context.Context, uids2 []j
// UpdateContacts analysis the data and update corresponding contacts if they exist in the database create new ones.
func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr string, query jsonutils.JSONObject,
datas []jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) error {
datas []jsonutils.JSONObject, ctxIds []dispatcher.SResourceContext) (jsonutils.JSONObject, error) {
type pair struct {
contact string
@@ -406,7 +406,7 @@ func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr str
records, err := models.ContactManager.FetchByUIDAndCType(idstr, contactTypes)
if err != nil {
return httperrors.NewGeneralError(err)
return nil, httperrors.NewGeneralError(err)
}
// updateFailed record the information of failed update
@@ -482,13 +482,29 @@ func (self *NotifyModelDispatcher) UpdateContacts(ctx context.Context, idstr str
errInfoBuffer.WriteString(" create failed. ")
}
errInfo := errInfoBuffer.String()
return httperrors.NewGeneralError(errors.Error(errInfo))
return nil, httperrors.NewGeneralError(errors.Error(errInfo))
}
if query.Contains("update_dingtalk") {
models.UpdateDingtalk(idstr)
if query.Contains("pull") {
cType, _ := query.GetString("pull")
models.PullContact(idstr, cType)
}
return nil
// keep the return value same as this of the GET interface
ret := jsonutils.NewDict()
contact, err := models.ContactManager.FetchByUIDs(ctx, []string{idstr}, false)
if err != nil {
log.Errorf(err.Error())
return ret, nil
}
outDetails, err := contact[0].GetExtraDetails(ctx, userCred, ret, false)
if err != nil {
log.Errorf(err.Error())
return ret, nil
}
out := jsonutils.Marshal(outDetails)
out.(*jsonutils.JSONDict).Set("created_at", jsonutils.NewString(contact[0].CreatedAt.String()))
return out, nil
}
func (self *NotifyModelDispatcher) UpdateTemplate(ctx context.Context, ctype string, query jsonutils.JSONObject,
+2 -8
View File
@@ -306,19 +306,13 @@ func contactUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Re
}
uid := params["<uid>"]
queryDict := mergeQueryParams(params, query)
update, _ := body.Bool(manager.Keyword(), "update_dingtalk")
if update {
dict := queryDict.(*jsonutils.JSONDict)
dict.Add(jsonutils.JSONTrue, "update_dingtalk")
}
err = manager.UpdateContacts(ctx, uid, queryDict, data, nil)
out, err := manager.UpdateContacts(ctx, uid, mergeQueryParams(params, query), data, nil)
if err != nil {
log.Errorf(err.Error())
httperrors.BadRequestError(w, "")
return
}
return
appsrv.SendJSON(w, wrap(out, manager.Keyword()))
}
// delete contact handler
+2
View File
@@ -115,6 +115,8 @@ func (self *SContactManager) InitializeData() error {
return nil
}
// FetchByUIDs fetch all SContancts whose uid included in uids.
// If some elements of uids are uname of users, setting param 'uname' as true will fetch correct results.
func (self *SContactManager) FetchByUIDs(ctx context.Context, uids []string, uname bool) ([]SContact, error) {
var err error
if uname {
+17 -17
View File
@@ -108,60 +108,60 @@ func SendVerifyMessage(ctx context.Context, userCred mcclient.TokenCredential, v
return nil
}
func UpdateDingtalk(uid string) {
func PullContact(uid string, contactType string) {
workMan.Run(func() {
updateDingtalk(context.Background(), uid)
pullContact(context.Background(), uid, contactType)
}, nil, nil)
}
func updateDingtalk(ctx context.Context, uid string) {
contacts, err := ContactManager.FetchByUIDAndCType(uid, []string{MOBILE, DINGTALK})
func pullContact(ctx context.Context, uid string, contactType string) {
contacts, err := ContactManager.FetchByUIDAndCType(uid, []string{MOBILE, contactType})
if err != nil {
log.Errorf("fetch contacts error")
}
if len(contacts) == 0 {
return
}
var mobileContact, dingtalkContact *SContact
var mobileContact, subContact *SContact
for i := range contacts {
if contacts[i].ContactType == MOBILE {
mobileContact = &contacts[i]
} else {
dingtalkContact = &contacts[i]
subContact = &contacts[i]
}
}
if mobileContact == nil {
return
}
userid, err := NotifyService.ContactByMobile(ctx, mobileContact.Contact, DINGTALK)
userid, err := NotifyService.ContactByMobile(ctx, mobileContact.Contact, contactType)
if err != nil {
log.Errorf("fetch dingtalk userid by mobile failed: %s", err.Error())
log.Errorf("fetch %s contact by mobile failed: %s", contactType, err.Error())
}
if dingtalkContact != nil {
dingtalkContact.SetModelManager(ContactManager, dingtalkContact)
origin := dingtalkContact.Contact
_, err := db.Update(dingtalkContact, func() error {
dingtalkContact.Contact = userid
if subContact != nil {
subContact.SetModelManager(ContactManager, subContact)
origin := subContact.Contact
_, err := db.Update(subContact, func() error {
subContact.Contact = userid
return nil
})
if err != nil {
log.Errorf("update dingtalk userid %s => %s failed", origin, userid)
log.Errorf("update %s contact userid %s => %s failed", contactType, origin, userid)
}
return
}
contact := SContact{
UID: uid,
ContactType: DINGTALK,
ContactType: contactType,
Contact: userid,
Enabled: "1",
VerifiedAt: time.Now(),
}
contact.Status = CONTACT_VERIFIED
err = ContactManager.TableSpec().InsertOrUpdate(&contact)
err = ContactManager.TableSpec().Insert(&contact)
if err != nil {
log.Errorf("create new dingtalk contact failed")
log.Errorf("create new %s contact failed", contactType)
}
}
+7 -5
View File
@@ -82,7 +82,7 @@ func (self *SRpcService) InitAll() error {
}
}
if self.SendServices.Len() == 0 {
log.Errorf("No available send service.")
log.Infof("No available send service.")
} else {
log.Infof("Total %d send service init successful", self.SendServices.Len())
}
@@ -285,12 +285,14 @@ func (self *SRpcService) startNewService(ctx context.Context, serviceName string
_, err = sendService.UpdateConfig(ctx, &args)
if err != nil {
st := status.Convert(err)
if st.Code() == codes.Unavailable {
if st.Code() == codes.FailedPrecondition {
// no such rpc serve
os.Remove(filename)
return nil, fmt.Errorf("no such rpc serve")
err = fmt.Errorf(st.Message())
}
return nil, fmt.Errorf(st.Message())
if st.Code() == codes.Unavailable {
err = fmt.Errorf("service is unavailable for now: %s", st.Message())
}
return nil, errors.Wrap(err, "UpdateConfig")
}
return sendService, nil