пятница, 3 апреля 2009 г.

ClearQuest и .NET Часть 3

Ну что же продолжаем знакомиться с API ClearQuest.

Сегодня будем добавлять дефекты в его БД. Получилось два метода, хотя можно было уложить и в один. Собственно вот:


Public Sub AddDefect(ByVal defect As DefectObject)
Dim sessionObj = GetSession()

Dim entityObj = sessionObj.BuildEntity("defect")

'Обязательные поля
entityObj.SetFieldValue("SubProject", defect.SubProject)
AddSectionsInDefect(entityObj, defect.Section)
entityObj.SetFieldValue("Headline", defect.Headline)
entityObj.SetFieldValue("Owner", defect.Owner)
entityObj.SetFieldValue("Type", defect.Type)
entityObj.SetFieldValue("Severity", defect.Severity)
'================================================
entityObj.SetFieldValue("Description", defect.Description)
entityObj.SetFieldValue("FoundInBuild", defect.FoundInBuild)
entityObj.SetFieldValue("OriginalSource", defect.OriginalSource)

Dim status = entityObj.Validate()

If (status <> "") Then Throw New ArgumentException(status, "defect")

entityObj.Commit()
End Sub

Private Sub AddSectionsInDefect(ByVal entityObj As Object, ByVal secrions As IList(Of String))
Dim tempStr As String

For Each tempStr In secrions

entityObj.AddFieldValue("Section", tempStr)

Next tempStr
End Sub

Так... начнём, как обычно, с теории.
API предоставляет достаточно удобный интерфейс для создания и заведения дефектов.
В общем-то процесс добавления разбит на несколько стадий: Получение сессии, создание сущности, заполнение полей, валидация и сохранение в БД.


Получить сессию - думаю понятно, далее создаём сущность, в данном случае - это сущность дефекта, вот так Dim entityObj = sessionObj.BuildEntity("defect") . Заполняем поля - для этого существует специальный метод: SetFieldValue("SubProject", defect.SubProject) , на вход которому необходимо подать, соответственно, название поля и значение, которое необходимо присвоить. Так же существует способ заполнения списочных (коллекций) полей AddFieldValue("Section", tempStr) . Сигнатура метода такая же, но работает он как метод Add(объект) у коллекций во многих языках.

Далее самое интересное - валидация. Она нужна для того, чтобы проверить наш заполненный объект сущности на соответствие схеме базы данных. Так как обычно при развороте схемы появляются поля, которые должны быть заполнены в обязательном порядке, да и константы не кто не отменял. Так вот для валидации служит специальный метод у объекта сущности под названием Validate() . Он возвращает значение строкового типа, а так же меняет статус объект на "валидированный" и фишка в том, что только из этого статуса объект можно сохранить командой Commit() .

Так вот, метод Validate() возвращает строку. Если возвращенная строка пустая, то значит всё ОК, если нет значит нам возвращается описание проблемы, к тому же достаточно подробное. Т.е. это возвращаемое значение нужно как-то обрабатывать.

Всё... теперь, если валидация успешна, можно сохранять данные :) PS. метод
Commit() .

В следующий раз расскажу как аттачить файлы.

среда, 1 апреля 2009 г.

ClearQuest и .NET Часть 2

Продолжаем разговор об API ClearQuest

Как обещал пишем запрос к базе данных CQ.
Как я говорил - любые действия с api начинаются с получения сессии.
В API предусмотрен специальный генератор запросов к базе дефектов. Сейчас я приведу несколько методов, которые я использовал для запроса:
Public Function GetDefectById(ByVal id As String) As DefectObject
Dim cqSession = GetSession()
Dim queryDef = PrepareQueryDef(cqSession)

Dim filterNode = queryDef.BuildFilterOperator(EBoolOp.BOOL_OP_OR)
filterNode.BuildFilter("id", ECompOp.COMP_OP_EQ, id)

Dim resultSet = cqSession.BuildResultSet(queryDef)

Return ExecuteAndParseResultSet(resultSet)(0)
End Function

Private Function PrepareQueryDef(ByVal session As SessionClass) As Object
Dim querydef = session.BuildQuery("defect")
querydef.BuildField("id")
querydef.BuildField("State")
querydef.BuildField("SubProject")
querydef.BuildField("Section")
querydef.BuildField("OriginalSource")
querydef.BuildField("Headline")
querydef.BuildField("Description")
querydef.BuildField("Severity")
querydef.BuildField("Owner")
querydef.BuildField("FoundInBuild")
querydef.BuildField("Type")
querydef.BuildField("Submit_Date")
Return querydef
End Function

Private Function ExecuteAndParseResultSet(ByVal resultSet As Object) As IList(Of
DefectObject)

resultSet.EnableRecordCount()
resultSet.Execute()
Dim count = resultSet.RecordCount

If (count = 0) Then Return Nothing

Dim ListOfDefects As New List(Of DefectObject)

Dim i As Integer

For i = 1 To count

resultSet.MoveNext()

Dim defectObject As New DefectObject()

defectObject.Id = resultSet.GetColumnValue(1)
defectObject.State = resultSet.GetColumnValue(2)
defectObject.SubProject = resultSet.GetColumnValue(3)
defectObject.Section = resultSet.GetColumnValue(4)
defectObject.OriginalSource = resultSet.GetColumnValue(5)
defectObject.Headline = resultSet.GetColumnValue(6)
defectObject.Description = resultSet.GetColumnValue(7)
defectObject.Severity = resultSet.GetColumnValue(8)
defectObject.Owner = resultSet.GetColumnValue(9)
defectObject.FoundInBuild = resultSet.GetColumnValue(10)
defectObject.Type = resultSet.GetColumnValue(11)
defectObject.SubmitDate = resultSet.GetColumnValue(12)

ListOfDefects.Add(defectObject)

Next i

Return ListOfDefects
End Function


Теперь разберём, что же мы делаем в данных функциях.
Функция GetDefectById(ByVal id As String) входная функция, которая видна при использовании класса.
Две другие функции являются вспомогальными и служат просто для логического разделения кода. Функция PrepareQueryDef(ByVal session As SessionClass) As Object настраивает запрос, т.е. в ней указывается какие поля необходимо получить, а функция ExecuteAndParseResultSet(ByVal resultSet As Object) As IList(Of DefectObject) выполняет запрос и разбирает то что нам вернуло API.
Теперь по порядку... что же делается в функциях.

В API для создания запроса существует специальный объект BuildQuery("defect"), который мы получаем из сессии. При его создании мы указываем для какой сущности будем генерировать запрос. Это может быть defect, Section или что-то ещё... в общем зависит от схемы, используемой в вашей базе.

Далее созданный объект запроса необходимо настроить. Настройка заключается как и в SQL в указании того, какие поля необходимы (то что пишется в SQL после ключевого слова select) и какие фильтры мы накладываем на запрос (выражение where).

Указание необходимых полей происходит через метод BuildField("id") , как видно ему передаётся строковый параметр с названием поля.

Для формирования фильтра необходимо получить специальный объект Dim filterNode = queryDef.BuildFilterOperator(EBoolOp.BOOL_OP_OR), у объекта запроса, что я сделал здесь. Передаваемое значение конструктору является указанием на то, что будет за оператор между выражениями. Фактически может быть два значения 1 или 2, соотвественно OR или AND. Далее нужно создать собтвенно выжение указывающее на то по какому полю ведётся фильтрация, делается это в этом выражении filterNode.BuildFilter("id", ECompOp.COMP_OP_EQ, id) , первый параметр указывает по какому полю фильтруем, второй указывает по какому провилу (в данном случаем это правило равенства (=)... и в последнем параметре указывается значение для фильтра. Всем правилам фильрации соответствует большая группа целочисленных констант. В моём примере это COMP_OP_EQ которая в реале равна 1.
На сайте IBM есть более-менее пригодная документации по API,вот тут указано как использовать фильтр. А вот тут рассказано, какие константы правил понимает API.

Так... продолжаем.

Вас наверное уже интересует, а как же получить то, для чего мы формировали запрос. Тут всё классически. В этом смысле API CQ очень похожа на DataReader из стандартной библиотеки .NET. да-да.. всё как обычно экзекьют и получаем выборку :) Правда есть одна загвоздка: по умолчанию отключен каунтер записей, его надо включить :)
Но давайте обо всём по порядку.

Мы подготовили объект запроса. Теперь этот запрос нужно выполнить. Для этого в API существует специальный объект под названием ResultSet, его необходимо получить из объекта сессии вот так Dim resultSet = cqSession.BuildResultSet(queryDef) . Объект получили... теперь включаем подсчёт записей и выполняем запрос: esultSet.EnableRecordCount(), resultSet.Execute() . Всё, теперь результат запроса находится в нашем объекте.

Далее всё аналогично DataReader. Получаем каунт Dim count = resultSet.RecordCount . Так же как и в DataReader есть специальный метод для последовательного перебора записей полученной выборки. Для того чтобы начать читать необходимо сместиться к первой записи resultSet.MoveNext() . Для перевода "корретки" к следующей записи используем этот же метод. Для того чтобы получить доступ к значению поля в записи используем метод GetColumnValue(1) . Параметр передаваемый в данный метод целочисленного типа и определяет номер столбца. Столбцы нумеруются в том же порядке, в котором мы их создавали при создании объекта запроса. Т.е. полная аналогия с DataReader.
Ещё хотелось бы сделать замечание. Если мы просим вернуть дефект со сложным полем (например, Section - т.е. дефект может быть сразу из нескольких областей), то мы получим несколько записей с одним и тем же дефектом, но с разным значеним сложного поля... Поэтому при перегоне выборки в более человеческий вид нужно это учитывать и соответственно обработать соответсвенным образом.

На этом пока всё. :) Потом расскажу как добавлять записи в CQ через API.

ClearQuest и .NET

Встала задача написать библиотеку которая сможет нормально использовать API Rational ClearQuest и при этом методы будут доступны на платформе .NET.

Попытки осуществить эту затею на C# с грохотом провалились, так как рефлексия COM объекта ClearQuestOleServer не даёт полной информации о его членах. А C#, как известно, не поддерживает позднего связывания.

Немного поковырявшись в носу, пришлось ковырять на Visual Basic, так как он поддерживает позднее связывание, да и примеры в документации приведены на VBScript.

Ну что же приступим.

Почитав документацию, начинаешь понимать что чёрт не так страшен как кажется. Сначала появилось желание использовать чисто способ запросов как к базе данных, возможность которых есть в API, но после нескольких экспериментов выяснилось, что
API просто передаёт запрос в базу (следовательно пользователь с ограниченными провами получал в ответ сообщение о том, что ему не доступна данная операция), а так как расчёт ставится не только на администратора БД, но и на обычного пользователя, то пришлось разбираться с возможностями конструктора запросов)

Ну так вот....

Любые действия с API CQ выполняются через получение сессии к базе... на бейсике это делается приблизительно вот так:

Private Function GetSession() As SessionClass

If (_userName = "" Or _userName = Nothing) Then
Throw New NullReferenceException("Имя пользователя не установлено. Воспользуйтесь сначала методом SetConnectionSetting.")
End If

If (_nameDataBaseProject = "" Or _nameDataBaseProject = Nothing) Then
Throw New NullReferenceException("Название базы CQ не установлено. Воспользуйтесь сначала методом SetConnectionSetting.")
End If

Dim session As New Session()
session.UserLogon(_userName, _password, _nameDataBaseProject, 0, "")

Return session

End Function


Главное тут это session.UserLogon(_userName, _password, _nameDataBaseProject, 0, "") . Здесь мы регистрируем свою пользовательскую сессию к CQ. Соотвественно, передаём методу ИмяПользователя, Пароль, НазваниеБазыДанных, далее идёт целочисленный параметр. Смысл его в том, что он указывает тип сессии. Вообще в документации говорится что нужно передавать значения от 1 до 4, но я передаю 0 и при этом всё корректно и приятно работает. Последний же строковый параметр так же не понятно для чего нужен, я использовал пустую строку, в документации говориться что-то про инстансы баз данных, если честно не понял.

На этом пока всё... в следующем сообщении рассмотрю как создавать простые запросы к CQ для поиска дефектов.