RallyRestAPIForClojure is a Clojure library to access your Rally data. It currently supports querying, creates, reads, updates and deletes. If you would like more information on the Rally Rest API please see Rally's Web Services API documentation.
The rest API is the central object of the Clojure API for Rally. An API can be created with a username/password or with an API key.
(require '[rally.api :as api])
(require '[rally.api.request :as request])
(def rest-api (api/create-rest-api {:username "me@mycompany.com" :password "supersecret"}))
; => #'user/rest-api
(def rest-api (api/create-rest-api {:api-key "mysecret key"}))
; => #'user/rest-api
;; Using an older webservice version
(def rest-api (-> {:username "me@mycompany.com" :password "supersecret"}
(api/create-rest-api)
(request/set-version :1.43))
; => #'user/rest-api
The query API is written in a way that should be comfortable to most Clojure developers. There are three major parts to the query API.
Before we get into the details, let's take a look at a simple example.
(first (api/query rest-api :defect)
; => {:metadata/rally-api-major 2,
; :metadata/rally-api-minor 0,
; :metadata/ref #<URI http://testing.rallydev.com/slm/webservice/v2.0/defect/12345,
; :metadata/ref-object-uuid #uuid "f5f770f5-d1e9-4dc7-847a-fd9164d93127",
; :metadata/ref-object-name "stuff is broken",
; :metadata/type :defect}
This simple query gets translated into the URL http://rally1.rallydev.com/slm/webservice/v2.0/Defect
.
A couple of things to notice about this first example:
- The keyword
:defect
is translated into"Defect"
- The results of the query are returned as a sequence of maps.
- The keys of each of the maps have been translated into clojure idiomatic keywords.
- api/query returns a lazy seq of all the paged results.
The rest API tries to make working with Rally in Clojure seem natural. Most of the translations Rally->Clojure and Clojure->Rally are done with a library called camel-snake-kebab. When going from Rally -> Clojure we use the ->kebab-case-keyword translation.
(data/->clojure-case "Defect")
; => :defect
(data/->clojure-case "CurrentProjectName")
; => :current-project-name
The Rally->Clojure translation does a little more than just change case. There are three types of data that are returned in a Rally object: built in, custom and metadata fields. The translation code handles each of these types of data a little differently.
In Rally, metadata fields start with an _
(underscore). Custom fields start with a c_
. The API translates each of these two data types into keywords with namespace that
represent their meaning.
(data/->clojure-case "_ref")
; => :metadata/ref
(data/->clojure-case "c_MyCustomField")
; => :custom/my-custom-field
As you might guess, the API has the reverse function.
(data/->rally-case :defect)
; => "Defect"
(data/->rally-case :current-project-name)
; => "CurrentProjectName"
(data/->rally-case :metadata/ref)
; => "_ref"
(data/->rally-case :custom/my-custom-field)
; => "c_MyCustomField"
These translations are used when writing queries and returning results. All object fields are translated using the data/->clojure-case
function before return the results to the caller. The data/->rally-case
function is used when translating queries to their proper Rally format.
Query specs are modeled after honey-sql. Let's look at a couple of examples to get the hang of it.
(api/query rest-api :defect [:contains :name "Foo"])
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(Name contains "Foo")
(api/query rest-api :defect [:or [:= :name "Foo"] [:= :name "Junk"]])
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect?query=((Name = "Foo") OR (Name = "Junk"))
(api/query rest-api :defect [:and [:contains :name "Foo"]
[:or [:= :state "Open"]
[:= :state "In-Progress"]]])
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect?query=((Name contains "Foo") AND ((State = "Open") OR (State = "In-Progress")))
(api/query rest-api :userstory [:= :parent.name "Foo"])
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/HierarchicalRequirement?query=(Parent.Name = "Foo")
;; ORDER
(api/query rest-api :userstory {:query [:= :name "Foo"] :order :state})
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/HierarchicalRequirement?query=(Name = "Foo")&order="State"
(api/query rest-api :defect {:query [:contains :name "Foo"] :order [:state :name]})
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(Name contains "Foo")&order="State,Name"
(api/query rest-api :defect {:query [:contains :name "Foo"] :order [:state [:name :desc]]})
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(Name contains "Foo")&order="State,Name desc"
Query specs can also contain information like pagesize
or start
.
(api/query rest-api :defect {:query [:= :name "Foo"] :pagesize 10})
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(Name = "Foo")&pagesize=10
Almost anything reasonable can be used as an URI in the API.
(api/query rest-api :defect)
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect
(api/query rest-api :userstory)
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/HierarchicalRequirement
(api/query rest-api "http://rally1.rallydev.com/slm/webservice/v2.0/defect")
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect
(def my-defect (api/create! rest-api :defect {:name "foo"}))
(api/query rest-api (:tasks my-defect))
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect/123/tasks
(def my-defect (api/create! rest-api :defect {:name "foo"}))
(api/query rest-api [my-defect :tasks])
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect/123/tasks
(def my-defect (api/create! rest-api :defect {:name "foo"}))
(api/find rest-api my-defect)
;; Translates to http://rally1.rallydev.com/slm/webservice/v2.0/defect/123
;; Create a user story
(api/create! rest-api :user-story {:name "This feature is really cool"})
; => {:description "",
; :formatted-id "US1",
; :tags {:metadata/rally-api-major 2,
; :metadata/rally-api-minor 0,
; :metadata/ref #<URI http://testing.rallydev.com/HierarchicalRequirement/1234/Tags,
; :metadata/type :tag,
; :metadata/tags-name-array [],
; :count 0},
; .... }
The API allows "defaulting" of data during api/create!
. If you want to default data, then you will need to provide default-data-fn
. The default-data-fn
is a function
that takes 2 parameters. The parameters are a type and a data map. The type is the data type in which the user is trying to create (:defect
, :task
, ...) The data map is the map of data that will be used to create the object.
(let [default-name (fn [type data] (merge {:name "Foo"} data))]
(-> rest-api
(request/set-default-data-fn default-name)
(api/create! rest-api :userstory)))
If you want to pass request parameters such as fetch, pass them as a map:
(api/create! rest-api :user-story {:name "This feature is really cool"} {:fetch :name})
; => {:name "This feature is really cool",
; ...metadata... }
(def my-user-story (api/create! rest-api :user-story {:name "This feature is really cool"}))
(api/update! rest-api my-user-story {:description "This is my description"})
(api/update! rest-api my-user-story {:description "Only return name for the response"} {:fetch :name})
;; Add a defect to a user story
(def my-user-story (api/create! rest-api :user-story {:name "This feature is really cool"}))
(def my-defect (api/create! rest-api :defect {:name "This doesn't work correctly"}))
(api/update-collection! rest-api (:defects my-user-story) :add [my-defect])
;; Add a parent to a userstory
(def my-user-story (api/create! rest-api :user-story {:name "This feature is really cool"}))
(def my-parent (api/create! rest-api :user-story {:name "Really cool parent"}))
(api/update! rest-api my-user-story {:parent my-parent})
;; Copy an artifact
(def my-defect (api/create! rest-api :defect {:name "This defect can be copied"}))
(api/copy! rest-api my-defect)
; => {:description "",
; :formatted-id "D342",
; :tags {:metadata/rally-api,
; ...
; :metadata/ref-object-name "(Copy of) This defect can be copied",
; ...}
Copyright (c) Rally Software Development Corp. 2013-2015 Distributed under the MIT License.
The Rally REST API for Clojure is available on an as-is basis.
Rally Software does not actively maintain or support this toolkit. If you have a question or problem, we recommend posting it to Stack Overflow: http://stackoverflow.com/questions/ask?tags=rally
- Start up alm
- Create test data
- Back in the RallyRestAPIForClojure directory, add a
.lein-env
file containing{:username "ue@test.com" :password "Password" :rally-host "http://localhost:7001"}
- Run
lein test
or, for a smaller subset of the tests,lein test :only rally.api-test
orlein test :only rally.api-test/objects-can-be-copied
- Run
lein test :integration
for deftests with^:integration
metadata.