How to use MergeMaps method of common Package

Best Testkube code snippet using common.MergeMaps

qwmsg.go

Source:qwmsg.go Github

copy

Full Screen

1package qwmsg2import (3 "errors"4 "fmt"5 "os"6 "path"7 "runtime"8 "time"9 "github.com/holimon/requests"10 "github.com/patrickmn/go-cache"11)12type Config struct {13 Corpid string14 Corpsecret string15 Agentid uint16 Expiresin uint17 Retry int18}19type actkCache struct {20 stop chan bool21 interval time.Duration22 cache *cache.Cache23 cacheLocal string24}25const tkcacheKey = "TOKEN"26const maxExpiresin = 700027type Qwmsg struct {28 tokenCache *actkCache29 Configs Config30 CommonField map[string]interface{}31}32type CommonField struct {33 ToUser string34 ToParty string35 ToTag string36 AgentId uint37 Enidtrans bool38 Endupcheck bool39 Dupinterval int40}41type MediaType string42const (43 MediaImage MediaType = "image"44 MediaVideo MediaType = "video"45 MediaVoice MediaType = "voice"46 MediaFile MediaType = "file"47)48type errmsg error49var (50 ErrorJsonUnmarshal errmsg = errors.New("exception occurs when the response message body is decoded as json")51 ErrorDefault errmsg = errors.New("exception has occurred")52 ErrorStill errmsg = errors.New("exception still occurs after the request is retried")53)54func IF(condition bool, trueval, falseval interface{}) interface{} {55 if condition {56 return trueval57 } else {58 return falseval59 }60}61func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {62 merged := make(map[string]interface{})63 for _, m := range maps {64 for k, v := range m {65 merged[k] = v66 }67 }68 return merged69}70func newtkCache(qwmsg *Qwmsg) *actkCache {71 duration := time.Duration(qwmsg.Configs.Expiresin) * time.Second72 tokenCache := &actkCache{stop: make(chan bool), interval: duration, cache: cache.New(duration, -1), cacheLocal: path.Join(os.TempDir(), "qwmsg")}73 if _, err := os.Stat(tokenCache.cacheLocal); err == nil {74 tokenCache.cache.LoadFile(tokenCache.cacheLocal)75 }76 if _, expiresin, geted := tokenCache.cache.GetWithExpiration(tkcacheKey); (!geted) || time.Now().After(expiresin) {77 if tk, err := getToken(qwmsg); err == nil {78 tokenCache.cache.Set(tkcacheKey, tk, tokenCache.interval)79 tokenCache.cache.SaveFile(tokenCache.cacheLocal)80 }81 }82 return tokenCache83}84func (tokenCache *actkCache) tkcacheRun(qwmsg *Qwmsg) {85 ticker := time.NewTicker(tokenCache.interval)86 defer ticker.Stop()87 for {88 select {89 case <-ticker.C:90 if token, err := getToken(qwmsg); err == nil {91 tokenCache.cache.Set(tkcacheKey, token, tokenCache.interval)92 tokenCache.cache.SaveFile(tokenCache.cacheLocal)93 }94 case <-tokenCache.stop:95 return96 }97 }98}99func New(configs Config) *Qwmsg {100 if configs.Expiresin > maxExpiresin {101 configs.Expiresin = maxExpiresin102 }103 qwmsg := &Qwmsg{Configs: configs}104 qwmsg.CommonField = make(map[string]interface{})105 qwmsg.SetCommonField(CommonField{ToUser: "@all", AgentId: configs.Agentid})106 qwmsg.tokenCache = newtkCache(qwmsg)107 go qwmsg.tokenCache.tkcacheRun(qwmsg)108 runtime.SetFinalizer(qwmsg, (*Qwmsg).tkcacheStop)109 return qwmsg110}111func (qwmsg *Qwmsg) tkcacheStop() {112 qwmsg.tokenCache.stop <- true113}114func getToken(qwmsg *Qwmsg) (string, error) {115 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", qwmsg.Configs.Corpid, qwmsg.Configs.Corpsecret)116 for try := qwmsg.Configs.Retry; try >= 0; try-- {117 if response, err := requests.Get(requrl); err == nil {118 v := make(map[string]interface{})119 if response.Json(&v) == nil {120 if v["errcode"].(float64) == 0 {121 return v["access_token"].(string), nil122 } else {123 return "", errors.New(v["errmsg"].(string))124 }125 }126 }127 }128 return "", ErrorStill129}130func (qwmsg *Qwmsg) token() string {131 if tk, geted := qwmsg.tokenCache.cache.Get(tkcacheKey); geted {132 return tk.(string)133 }134 return ""135}136func (qwmsg *Qwmsg) Test() {137 token, _, _ := qwmsg.tokenCache.cache.GetWithExpiration(tkcacheKey)138 fmt.Println(token)139}140func (qwmsg *Qwmsg) SetCommonField(common CommonField) {141 qwmsg.CommonField["touser"] = common.ToUser142 qwmsg.CommonField["toparty"] = common.ToParty143 qwmsg.CommonField["totag"] = common.ToTag144 qwmsg.CommonField["agentid"] = common.AgentId145 // qwmsg.CommonField["enable_id_trans"] = IF(common.Enidtrans, 1, 0)146 qwmsg.CommonField["enable_duplicate_check"] = IF(common.Endupcheck, 1, 0)147 qwmsg.CommonField["duplicate_check_interval"] = common.Dupinterval148}149func (qwmsg *Qwmsg) SendTextMsg(content string, safe bool) error {150 reqdata := mergeMaps(qwmsg.CommonField, map[string]interface{}{151 "msgtype": "text",152 "text": map[string]string{"content": content},153 })154 if safe {155 reqdata = mergeMaps(reqdata, map[string]interface{}{156 "safe": 1,157 })158 }159 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", qwmsg.token())160 for try := qwmsg.Configs.Retry; try >= 0; try-- {161 if response, err := requests.PostJson(requrl, reqdata); err == nil {162 v := make(map[string]interface{})163 if e := response.Json(&v); e == nil {164 if v["errcode"].(float64) == 0 {165 return nil166 } else {167 return errors.New(v["errmsg"].(string))168 }169 } else {170 return e171 }172 }173 }174 return ErrorStill175}176func (qwmsg *Qwmsg) PostMedia(filename string, filetype MediaType) (media_id string, ierr error) {177 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", qwmsg.token(), filetype)178 for try := qwmsg.Configs.Retry; try >= 0; try-- {179 if response, err := requests.Post(requrl, requests.Files{"media": filename}); err == nil {180 v := make(map[string]interface{})181 if e := response.Json(&v); e == nil {182 if v["errcode"].(float64) == 0 {183 return v["media_id"].(string), nil184 } else {185 return "", errors.New(v["errmsg"].(string))186 }187 } else {188 return "", e189 }190 }191 }192 return "", ErrorStill193}194func (qwmsg *Qwmsg) SendImageMsg(media_id string, safe bool) error {195 reqdata := mergeMaps(qwmsg.CommonField, map[string]interface{}{196 "msgtype": "image",197 "image": map[string]string{"media_id": media_id},198 })199 if safe {200 reqdata = mergeMaps(reqdata, map[string]interface{}{201 "safe": 1,202 })203 }204 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", qwmsg.token())205 for try := qwmsg.Configs.Retry; try >= 0; try-- {206 if response, err := requests.PostJson(requrl, reqdata); err == nil {207 v := make(map[string]interface{})208 if e := response.Json(&v); e == nil {209 if v["errcode"].(float64) == 0 {210 return nil211 } else {212 return errors.New(v["errmsg"].(string))213 }214 } else {215 return e216 }217 }218 }219 return ErrorStill220}221func (qwmsg *Qwmsg) SendFileMsg(media_id string, safe bool) error {222 reqdata := mergeMaps(qwmsg.CommonField, map[string]interface{}{223 "msgtype": "file",224 "file": map[string]string{"media_id": media_id},225 })226 if safe {227 reqdata = mergeMaps(reqdata, map[string]interface{}{228 "safe": 1,229 })230 }231 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", qwmsg.token())232 for try := qwmsg.Configs.Retry; try >= 0; try-- {233 if response, err := requests.PostJson(requrl, reqdata); err == nil {234 v := make(map[string]interface{})235 if e := response.Json(&v); e == nil {236 if v["errcode"].(float64) == 0 {237 return nil238 } else {239 return errors.New(v["errmsg"].(string))240 }241 } else {242 return e243 }244 }245 }246 return ErrorStill247}248func (qwmsg *Qwmsg) SendTextCardMsg(title, description, url string) error {249 reqdata := mergeMaps(qwmsg.CommonField, map[string]interface{}{250 "msgtype": "textcard",251 "textcard": map[string]string{252 "title": title,253 "description": description,254 "url": url,255 },256 })257 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", qwmsg.token())258 for try := qwmsg.Configs.Retry; try >= 0; try-- {259 if response, err := requests.PostJson(requrl, reqdata); err == nil {260 v := make(map[string]interface{})261 if e := response.Json(&v); e == nil {262 if v["errcode"].(float64) == 0 {263 return nil264 } else {265 return errors.New(v["errmsg"].(string))266 }267 } else {268 return e269 }270 }271 }272 return ErrorStill273}274type NewsMsg struct {275 Title string276 Description string277 Url string278 Picurl string279}280func (qwmsg *Qwmsg) SendNewsMsg(news []NewsMsg, safe bool) error {281 articles := make([]map[string]string, 0)282 for _, art := range news {283 temp := make(map[string]string)284 temp["title"] = art.Title285 temp["description"] = art.Description286 temp["url"] = art.Url287 temp["picurl"] = art.Picurl288 articles = append(articles, temp)289 }290 reqdata := mergeMaps(qwmsg.CommonField, map[string]interface{}{291 "msgtype": "news",292 "news": map[string]interface{}{293 "articles": articles,294 },295 })296 if safe {297 reqdata = mergeMaps(reqdata, map[string]interface{}{298 "safe": 1,299 })300 }301 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", qwmsg.token())302 for try := qwmsg.Configs.Retry; try >= 0; try-- {303 if response, err := requests.PostJson(requrl, reqdata); err == nil {304 v := make(map[string]interface{})305 if e := response.Json(&v); e == nil {306 if v["errcode"].(float64) == 0 {307 return nil308 } else {309 return errors.New(v["errmsg"].(string))310 }311 } else {312 return e313 }314 }315 }316 return ErrorStill317}318func (qwmsg *Qwmsg) SendMarkdownMsg(content string) error {319 reqdata := mergeMaps(qwmsg.CommonField, map[string]interface{}{320 "msgtype": "markdown",321 "markdown": map[string]interface{}{322 "content": content,323 },324 })325 requrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", qwmsg.token())326 for try := qwmsg.Configs.Retry; try >= 0; try-- {327 if response, err := requests.PostJson(requrl, reqdata); err == nil {328 v := make(map[string]interface{})329 if e := response.Json(&v); e == nil {330 if v["errcode"].(float64) == 0 {331 return nil332 } else {333 return errors.New(v["errmsg"].(string))334 }335 } else {336 return e337 }338 }339 }340 return ErrorStill341}...

Full Screen

Full Screen

kubernetes_object.go

Source:kubernetes_object.go Github

copy

Full Screen

...36 },37 Ports: ports,38 ClusterIP: clusterIP,39 }40 labels := util.MergeMaps(job.Labels, map[string]string{41 domain.JobId: pod.Labels[domain.JobId],42 domain.Queue: pod.Labels[domain.Queue],43 domain.PodNumber: pod.Labels[domain.PodNumber],44 })45 annotation := util.MergeMaps(job.Annotations, map[string]string{46 domain.JobSetId: job.JobSetId,47 domain.Owner: job.Owner,48 })49 service := &v1.Service{50 ObjectMeta: metav1.ObjectMeta{51 Name: fmt.Sprintf("%s-%s", pod.Name, strings.ToLower(ingSvcType.String())),52 Labels: labels,53 Annotations: annotation,54 Namespace: job.Namespace,55 },56 Spec: serviceSpec,57 }58 return service59}60func CreateIngress(61 name string,62 job *api.Job,63 pod *v1.Pod,64 service *v1.Service,65 executorIngressConfig *configuration.IngressConfiguration,66 jobConfig *IngressServiceConfig,67) *networking.Ingress {68 labels := util.MergeMaps(job.Labels, map[string]string{69 domain.JobId: pod.Labels[domain.JobId],70 domain.Queue: pod.Labels[domain.Queue],71 domain.PodNumber: pod.Labels[domain.PodNumber],72 })73 annotations := util.MergeMaps(job.Annotations, executorIngressConfig.Annotations)74 annotations = util.MergeMaps(annotations, jobConfig.Annotations)75 annotations = util.MergeMaps(annotations, map[string]string{76 domain.JobSetId: job.JobSetId,77 domain.Owner: job.Owner,78 })79 rules := make([]networking.IngressRule, 0, len(service.Spec.Ports))80 tlsHosts := make([]string, 0, len(service.Spec.Ports))81 // Rest of the hosts are generated off port information82 for _, servicePort := range service.Spec.Ports {83 if !contains(jobConfig, uint32(servicePort.Port)) {84 continue85 }86 host := fmt.Sprintf("%s-%s.%s.%s", servicePort.Name, pod.Name, pod.Namespace, executorIngressConfig.HostnameSuffix)87 tlsHosts = append(tlsHosts, host)88 // Workaround to get constant's address89 pathType := networking.PathTypeImplementationSpecific90 path := networking.IngressRule{91 Host: host,92 IngressRuleValue: networking.IngressRuleValue{93 HTTP: &networking.HTTPIngressRuleValue{94 Paths: []networking.HTTPIngressPath{95 {96 Path: "/",97 PathType: &pathType,98 Backend: networking.IngressBackend{99 Service: &networking.IngressServiceBackend{100 Name: service.Name,101 Port: networking.ServiceBackendPort{102 Number: servicePort.Port,103 },104 },105 },106 },107 },108 },109 },110 }111 rules = append(rules, path)112 }113 tls := make([]networking.IngressTLS, 0, 1)114 if jobConfig.TlsEnabled {115 certName := jobConfig.CertName116 if certName == "" {117 certName = fmt.Sprintf("%s-%s", job.Namespace, executorIngressConfig.CertNameSuffix)118 }119 tls = append(tls, networking.IngressTLS{120 Hosts: tlsHosts,121 SecretName: certName,122 })123 }124 ingress := &networking.Ingress{125 ObjectMeta: metav1.ObjectMeta{126 Name: name,127 Labels: labels,128 Annotations: annotations,129 Namespace: job.Namespace,130 },131 Spec: networking.IngressSpec{132 Rules: rules,133 TLS: tls,134 },135 }136 return ingress137}138func CreateOwnerReference(pod *v1.Pod) metav1.OwnerReference {139 return metav1.OwnerReference{140 APIVersion: "v1",141 Kind: "Pod",142 Name: pod.Name,143 UID: pod.UID,144 }145}146func CreatePod(job *api.Job, defaults *configuration.PodDefaults, i int) *v1.Pod {147 allPodSpecs := job.GetAllPodSpecs()148 podSpec := allPodSpecs[i]149 applyDefaults(podSpec, defaults)150 labels := util.MergeMaps(job.Labels, map[string]string{151 domain.JobId: job.Id,152 domain.Queue: job.Queue,153 domain.PodNumber: strconv.Itoa(i),154 domain.PodCount: strconv.Itoa(len(allPodSpecs)),155 })156 annotation := util.MergeMaps(job.Annotations, map[string]string{157 domain.JobSetId: job.JobSetId,158 domain.Owner: job.Owner,159 })160 setRestartPolicyNever(podSpec)161 pod := &v1.Pod{162 ObjectMeta: metav1.ObjectMeta{163 Name: common.PodNamePrefix + job.Id + "-" + strconv.Itoa(i),164 Labels: labels,165 Annotations: annotation,166 Namespace: job.Namespace,167 },168 Spec: *podSpec,169 }170 return pod...

Full Screen

Full Screen

overrides.go

Source:overrides.go Github

copy

Full Screen

...15//ForRelease returns overrides for release16func (o *Provider) ForRelease(releaseName string) (string, error) {17 o.refreshStore()18 allOverrides := Map{}19 MergeMaps(allOverrides, o.common)20 MergeMaps(allOverrides, o.components[releaseName])21 return ToYaml(allOverrides)22}23//New returns new Data instance.24func New(client kubernetes.Interface) OverrideData {25 configReader := &reader{26 client: client,27 }28 res := Provider{29 configReader: *configReader,30 }31 return &res32}33func (o *Provider) refreshStore() error {34 versionsMap, err := versionOverrides()35 if err != nil {36 return err37 }38 commonOverridesData, err := o.configReader.readCommonOverrides()39 if err != nil {40 return err41 }42 commonOverrides := UnflattenToMap(joinOverridesMap(commonOverridesData...))43 commonOverridesMap := Map{}44 MergeMaps(commonOverridesMap, versionsMap)45 MergeMaps(commonOverridesMap, commonOverrides)46 componentsOverridesData, err := o.configReader.readComponentOverrides()47 if err != nil {48 return err49 }50 componentsMap := unflattenComponentOverrides(joinComponentOverrides(componentsOverridesData...))51 o.common = commonOverridesMap52 o.components = componentsMap53 return nil54}55//versionOverrides reads overrides for component versions (versions.yaml)56func versionOverrides() (Map, error) {57 versionsFileData, err := loadComponentsVersions()58 if err != nil {59 return nil, err...

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 map1 := map[string]string{"a": "apple", "b": "ball", "c": "cat"}4 map2 := map[string]string{"d": "dog", "e": "elephant", "f": "fish"}5 map3 := map[string]string{"g": "goat", "h": "horse", "i": "iguana"}6 mergedMap := common.MergeMaps(map1, map2, map3)7 fmt.Println(mergedMap)8}9func MergeMaps(maps ...map[string]string) map[string]string {10 mergedMap := make(map[string]string)11 for _, m := range maps {12 for k, v := range m {13 }14 }15}

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 map1 := make(map[string]string)4 map2 := make(map[string]string)5 map3 := make(map[string]string)6 map4 := make(map[string]string)7 map5 := make(map[string]string)8 map6 := make(map[string]string)9 map7 := make(map[string]string)10 map8 := make(map[string]string)11 map9 := make(map[string]string)12 map10 := make(map[string]string)

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 fmt.Println("Hello, playground")4 m1 := map[string]string{"a": "apple", "b": "ball"}5 m2 := map[string]string{"c": "cat", "d": "dog"}6 m3 := common.MergeMaps(m1, m2)7 fmt.Println(m3)8}9func MergeMaps(m1, m2 map[string]string) map[string]string {10 m3 := make(map[string]string)11 for k, v := range m1 {12 }13 for k, v := range m2 {14 }15}16import (17func main() {18 fmt.Println("Hello, playground")19 m1 := map[string]string{"a": "apple", "b": "ball"}20 m2 := map[string]string{"c": "cat", "d": "dog"}21 m3 := common.MergeMaps(m1, m2)22 fmt.Println(m3)23}24func MergeMaps(m1, m2 map[string]string) map[string]string {25 m3 := make(map[string]string)26 for k, v := range m1 {27 }28 for k, v := range m2 {29 }30}

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 m := map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}4 n := map[string]string{"key4": "value4", "key5": "value5", "key6": "value6"}5 result := common.MergeMaps(m, n)6 fmt.Println(result)7}8Example 2: Merge two maps of type map[string]interface{}9import (10func main() {11 m := map[string]interface{}{"key1": "value1", "key2": "value2", "key3": "value3"}12 n := map[string]interface{}{"key4": "value4", "key5": "value5", "key6": "value6"}13 result := common.MergeMaps(m, n)14 fmt.Println(result)15}

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 fmt.Println("Merge maps")4 map1 := map[string]string{"a": "1", "b": "2"}5 map2 := map[string]string{"c": "3", "d": "4"}6 fmt.Println(common.MergeMaps(map1, map2))7}8continue for import return var

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 m1 := map[string]string{"a": "A", "b": "B"}4 m2 := map[string]string{"c": "C", "d": "D"}5 m3 := common.MergeMaps(m1, m2)6 fmt.Println(m3)7}8func MergeMaps(m1, m2 map[string]string) map[string]string {9 m3 := make(map[string]string)10 for k, v := range m1 {11 }12 for k, v := range m2 {13 }14}15import (16func main() {17 m1 := map[string]string{"a": "A", "b": "B"}18 m2 := map[string]string{"c": "C", "d": "D"}19 m3 := map[string]string{"e": "E", "f": "F"}20 m4 := common.MergeMaps(m1, m2, m3)21 fmt.Println(m4)22}23func MergeMaps(maps ...map[string]string) map[string]string {24 result := make(map[string]string)25 for _, m := range maps {26 for k, v := range m {27 }28 }29}

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 m1 := map[string]string{"name": "Ashish", "age": "26"}4 m2 := map[string]string{"name": "Gupta", "city": "Delhi"}5 fmt.Println(common.MergeMaps(m1, m2))6}7func MergeMaps(m1, m2 map[string]string) map[string]string {8 for key, value := range m2 {9 }10}11import (12func main() {13 s1 := []int{1, 2, 3}14 s2 := []int{4, 5, 6}15 fmt.Println(append(s1, s2...))16}17import (18func main() {19 s1 := []int{1, 2, 3}20 s2 := []int{4, 5, 6}21 s3 := append(s1, s2...)22 fmt.Println(removeDuplicates(s3))23}24func removeDuplicates(s []int) []int {25 keys := make(map[int]bool)26 list := []int{}27 for _, entry := range s {28 if _, value := keys[entry]; !value {29 list = append(list, entry)30 }31 }32}33import (34func main() {35 s1 := []int{1, 2, 3}36 s2 := []int{4, 5, 6}37 s3 := append(s1, s2...)38 fmt.Println(removeDuplicates(s3))39}40func removeDuplicates(s []int) []int {

Full Screen

Full Screen

MergeMaps

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 fmt.Println("Hello World")4 map1 := make(map[string]int)5 map2 := make(map[string]int)6 map3 := make(map[string]int)7 map4 := make(map[string]int)8 map5 := make(map[string]int)9 mapSlice := []map[string]int{map1, map2, map3, map4, map5}10 mergedMap := common.MergeMaps(mapSlice...)11 fmt.Println(mergedMap)12}

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Testkube automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful