queryresult.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package v1
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "net/http"
  15. "sort"
  16. "strconv"
  17. "strings"
  18. "devt.de/krotik/common/errorutil"
  19. "devt.de/krotik/common/stringutil"
  20. "devt.de/krotik/eliasdb/api"
  21. "devt.de/krotik/eliasdb/eql"
  22. "devt.de/krotik/eliasdb/graph"
  23. "devt.de/krotik/eliasdb/graph/data"
  24. )
  25. /*
  26. EndpointQueryResult is the query result endpoint URL (rooted). Handles everything under queryresult/...
  27. */
  28. const EndpointQueryResult = api.APIRoot + APIv1 + "/queryresult/"
  29. /*
  30. QueryResultEndpointInst creates a new endpoint handler.
  31. */
  32. func QueryResultEndpointInst() api.RestEndpointHandler {
  33. return &queryResultEndpoint{}
  34. }
  35. /*
  36. Handler object for query result operations.
  37. */
  38. type queryResultEndpoint struct {
  39. *api.DefaultEndpointHandler
  40. }
  41. /*
  42. HandleGET handles info requests on query results.
  43. */
  44. func (qre *queryResultEndpoint) HandleGET(w http.ResponseWriter, r *http.Request, resources []string) {
  45. qre.handleRequest("get", w, r, resources)
  46. }
  47. /*
  48. HandlePUT handles state changing operations on query results.
  49. */
  50. func (qre *queryResultEndpoint) HandlePUT(w http.ResponseWriter, r *http.Request, resources []string) {
  51. qre.handleRequest("put", w, r, resources)
  52. }
  53. /*
  54. HandlePOST handles state changing operations on query results.
  55. */
  56. func (qre *queryResultEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
  57. qre.handleRequest("post", w, r, resources)
  58. }
  59. /*
  60. HandleDELETE handles state changing operations on query results.
  61. */
  62. func (qre *queryResultEndpoint) HandleDELETE(w http.ResponseWriter, r *http.Request, resources []string) {
  63. qre.handleRequest("delete", w, r, resources)
  64. }
  65. func (qre *queryResultEndpoint) handleRequest(requestType string, w http.ResponseWriter, r *http.Request, resources []string) {
  66. // Check parameters
  67. if !checkResources(w, resources, 2, 3, "Need a result ID and an operation") {
  68. return
  69. }
  70. // Limit is either not set (then -1) or a positive value
  71. limit, ok := queryParamPosNum(w, r, "limit")
  72. if !ok {
  73. return
  74. }
  75. resID := resources[0]
  76. op := resources[1]
  77. res, ok := ResultCache.Get(resID)
  78. if !ok {
  79. http.Error(w, "Unknown query result", http.StatusBadRequest)
  80. return
  81. }
  82. sres := res.(*APISearchResult)
  83. if op == "csv" {
  84. if requestType != "get" {
  85. http.Error(w, "Csv can only handle GET requests",
  86. http.StatusBadRequest)
  87. return
  88. }
  89. w.Header().Set("content-type", "text/plain; charset=utf-8")
  90. w.Write([]byte(sres.CSV()))
  91. return
  92. } else if op == "quickfilter" {
  93. qre.quickFilter(requestType, w, resources, sres, limit)
  94. return
  95. } else if op == "select" {
  96. qre.selectRows(requestType, w, resources, sres)
  97. return
  98. } else if op == "groupselected" {
  99. qre.groupSelected(requestType, w, r, resources, sres)
  100. return
  101. }
  102. http.Error(w, fmt.Sprintf("Unknown operation: %v", op), http.StatusBadRequest)
  103. }
  104. /*
  105. groupSelected implements the adding/removing of all selected nodes to a group functionality.
  106. */
  107. func (qre *queryResultEndpoint) groupSelected(requestType string, w http.ResponseWriter, r *http.Request,
  108. resources []string, sres *APISearchResult) {
  109. var col int
  110. var err error
  111. addNodeToGroup := func(trans graph.Trans, part, groupName, key, kind string) error {
  112. // Add to group
  113. edge := data.NewGraphEdge()
  114. edge.SetAttr("key", stringutil.MD5HexString(fmt.Sprintf("%s#%s#%s", key, kind, groupName)))
  115. edge.SetAttr("kind", "Containment")
  116. edge.SetAttr(data.EdgeEnd1Key, groupName)
  117. edge.SetAttr(data.EdgeEnd1Kind, eql.GroupNodeKind)
  118. edge.SetAttr(data.EdgeEnd1Role, "Container")
  119. edge.SetAttr(data.EdgeEnd1Cascading, false)
  120. edge.SetAttr(data.EdgeEnd2Key, key)
  121. edge.SetAttr(data.EdgeEnd2Kind, kind)
  122. edge.SetAttr(data.EdgeEnd2Role, "ContainedItem")
  123. edge.SetAttr(data.EdgeEnd2Cascading, false)
  124. return trans.StoreEdge(part, edge)
  125. }
  126. removeNodeFromGroup := func(trans graph.Trans, part, groupName, key, kind string) error {
  127. var nodes []data.Node
  128. var edges []data.Edge
  129. nodes, edges, err = api.GM.TraverseMulti(part, key, kind, ":::"+eql.GroupNodeKind, false)
  130. if err == nil {
  131. for i, n := range nodes {
  132. if n.Key() == groupName {
  133. errorutil.AssertOk(trans.RemoveEdge(part, edges[i].Key(), edges[i].Kind()))
  134. }
  135. }
  136. }
  137. return err
  138. }
  139. trans := graph.NewGraphTrans(api.GM)
  140. part := sres.Header().Partition()
  141. selections := sres.Selections()
  142. if col, err = sres.GetPrimaryNodeColumn(); err != nil {
  143. http.Error(w, err.Error(), http.StatusBadRequest)
  144. return
  145. }
  146. if len(resources) == 3 {
  147. if requestType != "put" && requestType != "delete" {
  148. http.Error(w, "Groupselected for a specific group can only handle PUT and DELETE requests",
  149. http.StatusBadRequest)
  150. return
  151. }
  152. groupName := resources[2]
  153. for i, srcs := range sres.RowSources() {
  154. src := strings.Split(srcs[col], ":")
  155. kind := src[1]
  156. key := src[2]
  157. if selections[i] {
  158. // Add or remove form group
  159. if requestType == "put" {
  160. errorutil.AssertOk(addNodeToGroup(trans, part, groupName, key, kind))
  161. } else if requestType == "delete" {
  162. errorutil.AssertOk(removeNodeFromGroup(trans, part, groupName, key, kind))
  163. }
  164. }
  165. }
  166. } else {
  167. if requestType != "get" && requestType != "post" {
  168. http.Error(w, "Groupselected can only handle GET and POST requests",
  169. http.StatusBadRequest)
  170. return
  171. }
  172. if requestType == "post" {
  173. var reqGroups []interface{}
  174. var reqKeys, reqKinds []interface{}
  175. // Apply the given state
  176. gdata := make(map[string]interface{})
  177. // Parse the data
  178. dec := json.NewDecoder(r.Body)
  179. if err := dec.Decode(&gdata); err != nil {
  180. http.Error(w, "Could not decode request body as object with lists of groups, keys and kinds: "+err.Error(),
  181. http.StatusBadRequest)
  182. return
  183. }
  184. reqGroupsVal, ok1 := gdata["groups"]
  185. reqKeysVal, ok2 := gdata["keys"]
  186. reqKindsVal, ok3 := gdata["kinds"]
  187. if ok1 && ok2 && ok3 {
  188. reqGroups, ok1 = reqGroupsVal.([]interface{})
  189. reqKeys, ok2 = reqKeysVal.([]interface{})
  190. reqKinds, ok3 = reqKindsVal.([]interface{})
  191. }
  192. if !ok1 || !ok2 || !ok3 {
  193. http.Error(w, "Wrong data structures in request body - expecting an object with lists of groups, keys and kinds.",
  194. http.StatusBadRequest)
  195. return
  196. }
  197. // Remove groups from all selected nodes
  198. trans2 := graph.NewGraphTrans(api.GM)
  199. for i, srcs := range sres.RowSources() {
  200. src := strings.Split(srcs[col], ":")
  201. kind := src[1]
  202. key := src[2]
  203. if selections[i] {
  204. var nodes []data.Node
  205. nodes, _, err = api.GM.TraverseMulti(part, key, kind, ":::"+eql.GroupNodeKind, false)
  206. if err == nil {
  207. for _, n := range nodes {
  208. errorutil.AssertOk(removeNodeFromGroup(trans2, part, n.Key(), key, kind)) // There should be no errors at this point
  209. }
  210. }
  211. }
  212. if err != nil {
  213. break
  214. }
  215. }
  216. if err == nil {
  217. err = trans2.Commit()
  218. if err == nil {
  219. for i, g := range reqGroups {
  220. reqKindsArr := reqKinds[i].([]interface{})
  221. for j, k := range reqKeys[i].([]interface{}) {
  222. errorutil.AssertOk(addNodeToGroup(trans, part,
  223. fmt.Sprint(g), fmt.Sprint(k), fmt.Sprint(reqKindsArr[j]))) // There should be no errors at this point
  224. }
  225. }
  226. }
  227. }
  228. }
  229. }
  230. if err == nil {
  231. if err = trans.Commit(); err == nil {
  232. var sstate map[string]interface{}
  233. if sstate, err = qre.groupSelectionState(sres, part, col, selections); err == nil {
  234. qre.dataWriter(w).Encode(sstate)
  235. }
  236. }
  237. }
  238. if err != nil {
  239. http.Error(w, err.Error(), http.StatusInternalServerError)
  240. return
  241. }
  242. }
  243. /*
  244. groupSelectionState returns the current group selection state of a given query result.
  245. */
  246. func (qre *queryResultEndpoint) groupSelectionState(sres *APISearchResult, part string, primaryNodeCol int, selections []bool) (map[string]interface{}, error) {
  247. var ret map[string]interface{}
  248. var err error
  249. // Get groups for all selected nodes
  250. retGroups := []string{}
  251. retKeys := [][]string{}
  252. retKinds := [][]string{}
  253. memberKeys := make(map[string][]string)
  254. memberKinds := make(map[string][]string)
  255. for i, srcs := range sres.RowSources() {
  256. src := strings.Split(srcs[primaryNodeCol], ":")
  257. kind := src[1]
  258. key := src[2]
  259. if selections[i] {
  260. var nodes []data.Node
  261. nodes, _, err = api.GM.TraverseMulti(part, key, kind, ":::"+eql.GroupNodeKind, false)
  262. if err == nil {
  263. for _, n := range nodes {
  264. nkeys, ok := memberKeys[n.Key()]
  265. nkinds, _ := memberKinds[n.Key()]
  266. if !ok {
  267. nkeys = make([]string, 0)
  268. nkinds = make([]string, 0)
  269. }
  270. memberKeys[n.Key()] = append(nkeys, key)
  271. memberKinds[n.Key()] = append(nkinds, kind)
  272. }
  273. }
  274. }
  275. if err != nil {
  276. break
  277. }
  278. }
  279. memberKeysList := make([]string, 0, len(memberKeys))
  280. for g := range memberKeys {
  281. memberKeysList = append(memberKeysList, g)
  282. }
  283. sort.Strings(memberKeysList)
  284. for _, g := range memberKeysList {
  285. retGroups = append(retGroups, g)
  286. retKeys = append(retKeys, memberKeys[g])
  287. retKinds = append(retKinds, memberKinds[g])
  288. }
  289. if err == nil {
  290. ret = map[string]interface{}{
  291. "groups": retGroups,
  292. "keys": retKeys,
  293. "kinds": retKinds,
  294. }
  295. }
  296. return ret, err
  297. }
  298. /*
  299. selectRows implements the row selection functionality.
  300. */
  301. func (qre *queryResultEndpoint) selectRows(requestType string, w http.ResponseWriter,
  302. resources []string, sres *APISearchResult) {
  303. if requestType != "put" && requestType != "get" {
  304. http.Error(w, "Select can only handle GET and PUT requests", http.StatusBadRequest)
  305. return
  306. }
  307. if requestType == "get" {
  308. var col int
  309. var err error
  310. var keys, kinds []string
  311. // Just return the current selections
  312. if col, err = sres.GetPrimaryNodeColumn(); err != nil {
  313. http.Error(w, err.Error(), http.StatusBadRequest)
  314. return
  315. }
  316. sels := sres.Selections()
  317. for i, srcs := range sres.RowSources() {
  318. if sels[i] {
  319. src := strings.Split(srcs[col], ":")
  320. keys = append(keys, src[2])
  321. kinds = append(kinds, src[1])
  322. }
  323. }
  324. qre.dataWriter(w).Encode(map[string][]string{
  325. "keys": keys,
  326. "kinds": kinds,
  327. })
  328. return
  329. } else if len(resources) < 3 {
  330. http.Error(w,
  331. "Need a selection ('all', 'none', 'invert' or row number)",
  332. http.StatusBadRequest)
  333. return
  334. }
  335. if resources[2] == "all" {
  336. sres.AllSelection()
  337. } else if resources[2] == "none" {
  338. sres.NoneSelection()
  339. } else if resources[2] == "invert" {
  340. sres.InvertSelection()
  341. } else {
  342. i, err := strconv.ParseInt(resources[2], 10, 64)
  343. row := int(i)
  344. selections := sres.Selections()
  345. if err != nil || row < 0 || row >= len(selections) {
  346. http.Error(w, "Invalid selection row number", http.StatusBadRequest)
  347. return
  348. }
  349. sres.SetSelection(row, !selections[row])
  350. }
  351. // Count total selections
  352. totalSels := 0
  353. for _, s := range sres.Selections() {
  354. if s {
  355. totalSels++
  356. }
  357. }
  358. qre.dataWriter(w).Encode(map[string]int{
  359. "total_selections": totalSels,
  360. })
  361. }
  362. /*
  363. quickfilter implements the quickfilter functionality.
  364. */
  365. func (qre *queryResultEndpoint) quickFilter(requestType string, w http.ResponseWriter,
  366. resources []string, sres *APISearchResult, limit int) {
  367. if requestType != "get" {
  368. http.Error(w, "Quickfilter can only handle GET requests", http.StatusBadRequest)
  369. return
  370. } else if len(resources) < 3 {
  371. http.Error(w, "Need a query result column to filter", http.StatusBadRequest)
  372. return
  373. }
  374. i, err := strconv.ParseInt(resources[2], 10, 64)
  375. index := int(i)
  376. if err != nil || index < 0 || index >= len(sres.Header().Labels()) {
  377. http.Error(w, "Invalid query result column", http.StatusBadRequest)
  378. return
  379. }
  380. // Go through the column in question and collect the data
  381. counts := make(map[string]uint64)
  382. for _, row := range sres.Rows() {
  383. val := fmt.Sprint(row[index])
  384. counts[val]++
  385. }
  386. values := make([]string, 0, len(counts))
  387. frequencies := make([]uint64, 0, len(counts))
  388. for val, freq := range counts {
  389. values = append(values, val)
  390. frequencies = append(frequencies, freq)
  391. }
  392. sort.Stable(&countComparator{values, frequencies})
  393. if limit != -1 && len(values) > limit {
  394. values = values[:limit]
  395. frequencies = frequencies[:limit]
  396. }
  397. qre.dataWriter(w).Encode(map[string]interface{}{
  398. "values": values,
  399. "frequencies": frequencies,
  400. })
  401. }
  402. /*
  403. dataWriter returns an object to write result data.
  404. */
  405. func (qre *queryResultEndpoint) dataWriter(w http.ResponseWriter) *json.Encoder {
  406. w.Header().Set("content-type", "application/json; charset=utf-8")
  407. return json.NewEncoder(w)
  408. }
  409. /*
  410. SwaggerDefs is used to describe the endpoint in swagger.
  411. */
  412. func (qre *queryResultEndpoint) SwaggerDefs(s map[string]interface{}) {
  413. required := []map[string]interface{}{
  414. map[string]interface{}{
  415. "name": "rid",
  416. "in": "path",
  417. "description": "Result ID of a query result.",
  418. "required": true,
  419. "type": "string",
  420. },
  421. }
  422. column := map[string]interface{}{
  423. "name": "column",
  424. "in": "path",
  425. "description": "Column of the query result.",
  426. "required": true,
  427. "type": "string",
  428. }
  429. row := map[string]interface{}{
  430. "name": "row",
  431. "in": "path",
  432. "description": "Row number of the query result or 'all', 'none' or 'invert'.",
  433. "required": true,
  434. "type": "string",
  435. }
  436. groupName := map[string]interface{}{
  437. "name": "group_name",
  438. "in": "path",
  439. "description": "Name of an existing group.",
  440. "required": true,
  441. "type": "string",
  442. }
  443. limit := map[string]interface{}{
  444. "name": "limit",
  445. "in": "query",
  446. "description": "Limit the maximum number of result items.",
  447. "required": false,
  448. "type": "string",
  449. }
  450. selectionStateParam := map[string]interface{}{
  451. "name": "selection_state",
  452. "in": "body",
  453. "description": "Group seletion state of a query result",
  454. "required": true,
  455. "schema": map[string]interface{}{
  456. "$ref": "#/definitions/GroupSelectionState",
  457. },
  458. }
  459. selectionStateGroups := map[string]interface{}{
  460. "type": "object",
  461. "properties": map[string]interface{}{
  462. "groups": map[string]interface{}{
  463. "description": "List of group names which include one or more selected nodes.",
  464. "type": "array",
  465. "items": map[string]interface{}{
  466. "description": "Group name.",
  467. "type": "string",
  468. },
  469. },
  470. "keys": map[string]interface{}{
  471. "description": "Lists of selected node keys which are part of the groups in the 'groups' list.",
  472. "type": "array",
  473. "items": map[string]interface{}{
  474. "description": "List of node keys.",
  475. "type": "array",
  476. "items": map[string]interface{}{
  477. "description": "Node key.",
  478. "type": "string",
  479. },
  480. },
  481. },
  482. "kinds": map[string]interface{}{
  483. "description": "Lists of selected node kinds which are part of the groups in the 'groups' list.",
  484. "type": "array",
  485. "items": map[string]interface{}{
  486. "description": "List of node kinds.",
  487. "type": "array",
  488. "items": map[string]interface{}{
  489. "description": "Node kind.",
  490. "type": "string",
  491. },
  492. },
  493. },
  494. },
  495. }
  496. selectionState := map[string]interface{}{
  497. "type": "object",
  498. "properties": map[string]interface{}{
  499. "keys": map[string]interface{}{
  500. "description": "Lists of selected node keys.",
  501. "type": "array",
  502. "items": map[string]interface{}{
  503. "description": "Node key.",
  504. "type": "string",
  505. },
  506. },
  507. "kinds": map[string]interface{}{
  508. "description": "Kinds of all selected nodes.",
  509. "type": "array",
  510. "items": map[string]interface{}{
  511. "description": "Node kind.",
  512. "type": "string",
  513. },
  514. },
  515. },
  516. }
  517. s["paths"].(map[string]interface{})["/v1/queryresult/{rid}/csv"] = map[string]interface{}{
  518. "get": map[string]interface{}{
  519. "summary": "Return the search result in CSV format.",
  520. "description": "The csv endpoint is used to generate a CSV string from the search result.",
  521. "produces": []string{
  522. "text/plain",
  523. },
  524. "parameters": append(required),
  525. "responses": map[string]interface{}{
  526. "200": map[string]interface{}{
  527. "description": "A CSV string.",
  528. },
  529. "default": map[string]interface{}{
  530. "description": "Error response",
  531. "schema": map[string]interface{}{
  532. "$ref": "#/definitions/Error",
  533. },
  534. },
  535. },
  536. },
  537. }
  538. s["paths"].(map[string]interface{})["/v1/queryresult/{rid}/quickfilter/{column}"] = map[string]interface{}{
  539. "get": map[string]interface{}{
  540. "summary": "Return quickfilter information on a given result column.",
  541. "description": "The quickfilter endpoint is used to determine the 10 most frequent used values in a particular result column.",
  542. "produces": []string{
  543. "text/plain",
  544. "application/json",
  545. },
  546. "parameters": append(required, column, limit),
  547. "responses": map[string]interface{}{
  548. "200": map[string]interface{}{
  549. "description": "An object containing values and frequencies.",
  550. },
  551. "default": map[string]interface{}{
  552. "description": "Error response",
  553. "schema": map[string]interface{}{
  554. "$ref": "#/definitions/Error",
  555. },
  556. },
  557. },
  558. },
  559. }
  560. s["paths"].(map[string]interface{})["/v1/queryresult/{rid}/select"] = map[string]interface{}{
  561. "get": map[string]interface{}{
  562. "summary": "Return the (primary) nodes which are currently selected.",
  563. "description": "The select endpoint is used to query all selected nodes of a given query result.",
  564. "produces": []string{
  565. "text/plain",
  566. "application/json",
  567. },
  568. "parameters": required,
  569. "responses": map[string]interface{}{
  570. "200": map[string]interface{}{
  571. "description": "Current total selections.",
  572. "schema": map[string]interface{}{
  573. "$ref": "#/definitions/SelectionState",
  574. },
  575. },
  576. "default": map[string]interface{}{
  577. "description": "Error response",
  578. "schema": map[string]interface{}{
  579. "$ref": "#/definitions/Error",
  580. },
  581. },
  582. },
  583. },
  584. }
  585. s["paths"].(map[string]interface{})["/v1/queryresult/{rid}/select/{row}"] = map[string]interface{}{
  586. "put": map[string]interface{}{
  587. "summary": "Selects one or more rows of a given query result.",
  588. "description": "The select endpoint is used to select one or more rows of a given query result.",
  589. "produces": []string{
  590. "text/plain",
  591. "application/json",
  592. },
  593. "parameters": append(required, row),
  594. "responses": map[string]interface{}{
  595. "200": map[string]interface{}{
  596. "description": "Current total selections.",
  597. },
  598. "default": map[string]interface{}{
  599. "description": "Error response",
  600. "schema": map[string]interface{}{
  601. "$ref": "#/definitions/Error",
  602. },
  603. },
  604. },
  605. },
  606. }
  607. s["paths"].(map[string]interface{})["/v1/queryresult/{rid}/groupselected/{group_name}"] = map[string]interface{}{
  608. "put": map[string]interface{}{
  609. "summary": "Add all selected nodes (primary node of each row) to the given group.",
  610. "description": "The groupselected endpoint is used to add all selected nodes (primary node of each row) to the given (existing) group.",
  611. "produces": []string{
  612. "text/plain",
  613. "application/json",
  614. },
  615. "parameters": append(required, groupName),
  616. "responses": map[string]interface{}{
  617. "200": map[string]interface{}{
  618. "description": "Current group selection state after the operation.",
  619. "schema": map[string]interface{}{
  620. "$ref": "#/definitions/GroupSelectionState",
  621. },
  622. },
  623. "default": map[string]interface{}{
  624. "description": "Error response",
  625. "schema": map[string]interface{}{
  626. "$ref": "#/definitions/Error",
  627. },
  628. },
  629. },
  630. },
  631. "delete": map[string]interface{}{
  632. "summary": "Remove all selected nodes (primary node of each row) from the given group.",
  633. "description": "The groupselected endpoint is used to remove all selected nodes (primary node of each row) from the given (existing) group.",
  634. "produces": []string{
  635. "text/plain",
  636. "application/json",
  637. },
  638. "parameters": append(required, groupName),
  639. "responses": map[string]interface{}{
  640. "200": map[string]interface{}{
  641. "description": "Current group selection state after the operation.",
  642. "schema": map[string]interface{}{
  643. "$ref": "#/definitions/GroupSelectionState",
  644. },
  645. },
  646. "default": map[string]interface{}{
  647. "description": "Error response",
  648. "schema": map[string]interface{}{
  649. "$ref": "#/definitions/Error",
  650. },
  651. },
  652. },
  653. },
  654. }
  655. s["paths"].(map[string]interface{})["/v1/queryresult/{rid}/groupselected"] = map[string]interface{}{
  656. "get": map[string]interface{}{
  657. "summary": "Get the current group selection state.",
  658. "description": "Returns the current selections state which contains all selected nodes which are in groups.",
  659. "produces": []string{
  660. "text/plain",
  661. "application/json",
  662. },
  663. "parameters": append(required),
  664. "responses": map[string]interface{}{
  665. "200": map[string]interface{}{
  666. "description": "Current group selection state.",
  667. "schema": map[string]interface{}{
  668. "$ref": "#/definitions/GroupSelectionState",
  669. },
  670. },
  671. "default": map[string]interface{}{
  672. "description": "Error response",
  673. "schema": map[string]interface{}{
  674. "$ref": "#/definitions/Error",
  675. },
  676. },
  677. },
  678. },
  679. "post": map[string]interface{}{
  680. "summary": "Set a new group selection state.",
  681. "description": "Sets the groups in the given selection state.",
  682. "produces": []string{
  683. "text/plain",
  684. "application/json",
  685. },
  686. "parameters": append(required, selectionStateParam),
  687. "responses": map[string]interface{}{
  688. "200": map[string]interface{}{
  689. "description": "Current group selection state after the operation.",
  690. "schema": map[string]interface{}{
  691. "$ref": "#/definitions/GroupSelectionState",
  692. },
  693. },
  694. "default": map[string]interface{}{
  695. "description": "Error response",
  696. "schema": map[string]interface{}{
  697. "$ref": "#/definitions/Error",
  698. },
  699. },
  700. },
  701. },
  702. }
  703. // Add generic error object to definition
  704. s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{
  705. "description": "A human readable error mesage.",
  706. "type": "string",
  707. }
  708. // Add selection states to definition
  709. s["definitions"].(map[string]interface{})["SelectionState"] = selectionState
  710. s["definitions"].(map[string]interface{})["GroupSelectionState"] = selectionStateGroups
  711. }
  712. /*
  713. countComparator is a comparator object used for sorting the counts
  714. */
  715. type countComparator struct {
  716. Values []string
  717. Frequencies []uint64
  718. }
  719. func (c countComparator) Len() int {
  720. return len(c.Values)
  721. }
  722. func (c countComparator) Less(i, j int) bool {
  723. if c.Frequencies[i] == c.Frequencies[j] {
  724. return c.Values[i] < c.Values[j]
  725. }
  726. return c.Frequencies[i] > c.Frequencies[j]
  727. }
  728. func (c countComparator) Swap(i, j int) {
  729. c.Values[i], c.Values[j] = c.Values[j], c.Values[i]
  730. c.Frequencies[i], c.Frequencies[j] = c.Frequencies[j], c.Frequencies[i]
  731. }