10 апреля 2019 г.

MS TFS REST API через боль и слёзы

Первый подход к снаряду. Выглядит так себе
    Раскуриваю REST API у TFS и мне хочется выть. Нет, возможно это нормально для больших и ветвистых проектов, а мои знания в прошлые разы были слишком простыми, т.к. строились на несложных опен сорсных проектах типа STF https://github.com/openstf/stf и подобных. И из-за этого у меня сложилось ложное впечатлиение, что REST API, это когда просто потянул за верёвочку и всё тебе пришло или толкнул коробку с данными, которые быстро набросал туда и всё снова работает.

    Но Microsoft мне показал, насколько я неправ.


    Первое, что я решил сделать — получить список всех не закрытых багов на моём проекте. Но такого метода нет. ШТОШ. Нашёл, что всё такое делается через запросы (Wiql): https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/wiql/query%20by%20wiql?view=vsts-rest-tfs-4.1

    Так, понятно. У меня есть два пути:
  1. Заранее создать и сохранить нужные мне запросы и дёргать их напрямую. Создать можно, разумеется, любым удобным способом: Студия, консольная программа, веб-морда, тот же REST API
  2. Составлять запросы вручную каждый раз
    Для универсальности я пока выбираю второй путь (позже сделаю и дёрганья готовых запросов, а также их сохранение) — у меня будет несколько методов ("получить все баги", "получить все задачи") с параметрами (приоритет такой-то и выше, состояние такое-то или не такое-то). Это, конечно, имитация просто кучи заранее сформированных запросов из п. 1, но я планирую этим модулем делиться внутри команд и им точно не нужны будут запросы, завязанные на меня самого. А когда от них будет отклик, допилю так, как удобно не только мне.

    Смотрим в примеры. Среди них есть очень полезный пример, выбирающий не закрытые и не удалённые ошибки:
POST https://{instance}/{collection}/_apis/wit/wiql?api-version=4.1
    Отлично, то что надо! Смущает только, что есть коллекция (DefaultCollection обычно), но нет проекта. Ну пока ладно.

    Пихаем в боди запрос из примера (ВАЖНО! Все POST запросы должны иметь в хидерах Content-type: application/json!):
{
 "query": "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] = 'Task' AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc"
}
    Только заменяем Task на Bug. Ну и да, получили все баги на всех проектах, куда есть права. Это вовсе не то, что мне нужно.

    Но позвольте, в самом начале адрес запроса был другим и там были упомянуты и проект и даже команда (тим)!
POST https://{instance}/{collection}/{project}/{team}/_apis/wit/wiql?api-version=4.1
Просто эти куски адреса не обязательны. Ну так добавим! Вот типа так:
POST https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/wiql?api-version=4.1&$top=10
$top=10 — это чтобы нам вернуло первые 10 результатов, а не всё, что есть.
Проверяем результат и первый же из них...
{
    "id": 93277,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/93277"
  }
...ведёт на какой-то совершенно левый проект. Ну то есть при таком запросе и проект, команда (я проверил это вторым запросом) просто игнорируются.

    Ну, понятно. Нужно добавлять проект в само тело запроса. Но посмотрите на название полей в селекте. Там не те названия, к которым привыкли при использовании Студии или веб-интерфейса, там внутренние названия этой системы. А это означает, что нам нужно получить сначала эти названия. Давайте сделаем это!

    Где их вообще спросить? Очевидно (мне), что в WorkItem Types: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/work%20item%20types/list?view=vsts-rest-tfs-4.1 Во-первых потому что в примере селект делался из ВоркАйтемов и он делался по типам, а во-вторых потому что я уже несколько часов изучаю документацию и даже начал её понимать. До того начал, что уже знаю, почему выше не задействован проект и как его всё же использовать :)
    Итак, спросим, какие вообще типы ворк айтемов бывают для нашего проекта:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/workitemtypes?api-version=4.1
    Мне отдало 16 типов. Классно! Среди них Bug, Task, вот это всё. Но у укаждого их них ещё есть ещё куча полей. Потому что у баги есть поле с датой создания, с имненем автора, с прилинованными к нему багами, с приоритетом, с путём к нему внутри проекта и множество других полей. Так сколько их всего, этих полей, у каждого из 16 (на моём проекте) типов?
    У каждого из этих полей есть параметр referenceName с внутренним названием и name с публичным названием. К примеру для публичного имени «State Change Date», относящегося к Шаред Степам, соотвествующее внутреннее имя «Microsoft.VSTS.Common.StateChangeDate». На самом деле это имя точно такое же и для Багов, и для Тасков. Потому обращаться можно хоть по внутреннему имени, хоть по «человеческому» То есть в оригинальном Select из примера выше можно заменить System.Id на «человеческое» имя «ID» и всё будет работать. Жить стало проще! Ведь теперь не
[Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc
а просто
[Priority] asc, [Created Date] desc
    Для [System.WorkItemType] нужным мне именем будет «Work Item Type», т.е. все три слова отдельно. Это видно всё из той же выдачи:
{
        "defaultValue": null,
        "alwaysRequired": false,
        "referenceName": "System.WorkItemType",
        "name": "Work Item Type",
        "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/fields/System.WorkItemType"
}
    Сколь же всего таких полей? Поиск по referenceName выдаёт 1924 вхождения, то есть примерно по 120 полей на каждый тип. При этом вовсе не все из них — это от Microsoft. В выдаче есть поля с названием «KL.RND.LocalizationLanguage», «KL.RND.OSVersion» и множество других подобных. Эти поля заведены администраторами TFS.

    Что это означает? Это означает, что для составления запроса, вам нужно сначала получить все типы ворк айтемов, с которыми можно работать в данном проекте (я работаю в Cons-SafeMoney-Mob в данном случае), а затем для каждого из этих айтемов получить список возможных полей и уже эти знания использовать в запросах. Это если делать так, как прям правильно.
    Или же вы просто прикопаете у себя в коде наиболее нужные названия и будете работать только с ними. И если вдруг понадобится что-то ещё, вы расширите список названий.

    Ну и просто для информации. Получить поля только нужных типы ворк айтемов можно без запроса всех типов и вычленения только для нужного типа (как в примере выше). Для этого нужно сделать запрос по интересующему типу:
GET https://{instance}/{collection}/{project}/_apis/wit/workitemtypes/{type}/fields?api-version=4.1

    Мы почти можем составлять запрос, ради которого всё и затевалось. Мы уже знаем, как узнать список возможных ворк айтемов и как у каждого из них спросить поддерживаемые им поля. Теперь нужно сравнить поддерживаемы поля с возможными значениями. А где взять возможные значения? И где взять способы их сравнений? Ведь какие-то поля сравниваются знаками типа «больше», «меньше», а какие-то имеют сравнения типа «включая» и «исключая». Вот как это выглядит в веб-интерфейсе:
Разные поля имеют разные виды сравнений
    Запросить операторы сравнений проще всего, они отдаются при разных запросах. Например, можно запросить их для всех ворк айтемов разом, хотя это и не очень правильно и по скорости, и по объёму трафика, и по памяти. Но делается это так (документация):
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/fields?&api-version=4.1
    Более правильно спрашивать операторы только для нужных полей (документация). Например вот так можно спросить, как сравнивать приоритеты:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-Safemoney-Mob/_apis/wit/fields/Priority?api-version=4.1
или важность:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-Safemoney-Mob/_apis/wit/fields/Severity?api-version=4.1
или даже Area Path:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-Safemoney-Mob/_apis/wit/fields/System.AreaPath?api-version=4.1
и будет ясный ответ (курсив в типе нам понадобится позже)
{
  "name": "Area Path",
  "referenceName": "System.AreaPath",
  "description": "The area of the product with which this bug is associated",
  "type": "treePath",
  "usage": "workItem",
  "readOnly": false,
  "supportedOperations": [{
    "referenceName": "SupportedOperations.Under",
    "name": "Under"
  }, {
    "referenceName": "SupportedOperations.NotUnder",
    "name": "Not Under"
  }, {
    "referenceName": "SupportedOperations.Equals",
    "name": "="
  }, {
    "referenceName": "SupportedOperations.NotEquals",
    "name": "<>"
  }, {
    "referenceName": "SupportedOperations.In",
    "name": "In"
  }, {
    "name": "Not In"
  }],
  "isIdentity": false,
  "isPicklist": false,
  "isPicklistSuggested": false,
  "url": "https://kaspersky.fake.url/tfs/DefaultCollection/77f51967-f584-40d5-8aba-a41abe76ccf9/_apis/wit/fields/System.AreaPath"
}
    Итого у нас осталось Value. Не у всех полей есть шаблонные значения для сравнений и это ожидаемо. Но всегда можно узнать их тип. Делается это подобным запросом (документация):
GET https://{instance}/{collection}/{project}/_apis/wit/workitemtypes/{type}/fields/{field}?$expand={$expand}&api-version=4.1
Где нужный нам $expand равен allowedValues. То есть вот так можно получить значения для сравнения важности баги:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/workitemtypes/Bug/fields/Severity?$expand=allowedValues&api-version=4.1
{
  "defaultValue": null,
  "allowedValues": ["1 - Critical", "2 - High", "3 - Medium", "4 - Low"],
  "helpText": "Assessment of the effect of the bug on the project",
  "alwaysRequired": false,
  "referenceName": "Microsoft.VSTS.Common.Severity",
  "name": "Severity",
  "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/fields/Microsoft.VSTS.Common.Severity"
}
А вот у таски Area Path особо не сравнишь, потому что шаблонные значения не возвращаются:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/workitemtypes/Task/fields/System.AreaPath?$expand=allowedValues&api-version=4.1
{
  "defaultValue": null,
  "allowedValues": [],
  "helpText": "The area of the product to which this task contributes",
  "alwaysRequired": false,
  "referenceName": "System.AreaPath",
  "name": "Area Path",
  "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/fields/System.AreaPath"
}
    Но мы помним по одному из запросов выше, что тип возвращаемого значения дла area path — это «"type": "treePath"» (тот самый курсив). Значит нужно вытащить пути. И это самое нудное из всей нудятины, которую я уже успел описать.
    Дело в том, что нужно сделать запрос классификации нод (документация). И если просто их спросить, то ответ будет коротким:
https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/classificationnodes?api-version=4.1
{
  "count": 2,
  "value": [{
    "id": 38507,
    "identifier": "qqqqq-cf8d722084f5",
    "name": "Cons-SafeMoney-Mob",
    "structureType": "area",
    "hasChildren": true,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqqq-a41abe76ccf9/_apis/wit/classificationNodes/Areas"
  }, {
    "id": 38503,
    "identifier": "qqqqq-6f734bf7d020",
    "name": "Cons-SafeMoney-Mob",
    "structureType": "iteration",
    "hasChildren": true,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/77f51967-f584-40d5-8aba-a41abe76ccf9/_apis/wit/classificationNodes/Iterations"
  }]
}
    Ну да, есть эрии, есть итерации. Нам нужно пойти глубже (дикаприо.жпег). Предлагаю не выёживаться и сразу на глубину 10 (хотя это перебор и такой реально не будет:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/classificationnodes?$depth=10&api-version=4.1
такой запрос вернул уже интересное:
{
  "count": 2,
  "value": [{
    "id": 38507,
    "identifier": "qqqq-cf8d722084f5",
    "name": "Cons-SafeMoney-Mob",
    "structureType": "area",
    "hasChildren": true,
    "children": [{
      "id": 53031,
      "identifier": "qqqq-e322ee60f9c8",
      "name": "SDK",
      "structureType": "area",
      "hasChildren": true,
      "children": [{
        "id": 67720,
        "identifier": "qqqqq-50160cdd7253",
        "name": "KSN Statistics",
        "structureType": "area",
        "hasChildren": true,
        "children": [{
          "id": 72172,
          "identifier": "qqqqq-b3dc50a52f36",
          "name": "Common",
          "structureType": "area",
          "hasChildren": false,
          "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqqq-a41abe76ccf9/_apis/wit/classificationNodes/Areas/SDK/KSN%20Statistics/Common"
        }, {
          "id": 72173,
          "identifier": "qqqqq-9e0f71f0225f",
          "name": "OAS",
          "structureType": "area",
          "hasChildren": false,
          "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqq-a41abe76ccf9/_apis/wit/classificationNodes/Areas/SDK/KSN%20Statistics/OAS"
        }, {.....
Это области. А ниже итерации:
    }, {
      "id": 95009,
      "identifier": "qqqqq-42275ada2123",
      "name": "SDK 4.1qqqq",
      "structureType": "iteration",
      "hasChildren": false,
      "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqq-a41abe76ccf9/_apis/wit/classificationNodes/Iterations/SDK%204.1qqqqqq"
    }, {
      "id": 95010,
      "identifier": "qqqqq-a8b33259ab97",
      "name": "SDK 5.3qqqq",
      "structureType": "iteration",
      "hasChildren": false,
      "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqqq-a41abe76ccf9/_apis/wit/classificationNodes/Iterations/SDK%205.3qqq"
    }, {
    Некоторые из итераций ещё и список дочерних вывалят. Значит вот так можно составить пути. Но нужно ли? В своём проекте вы, скорее всего, эти пути определите заранее и будете их сразу готовыми подставлять. Я, по крайней мере, буду делать так у себя.

    Разумеется, вы можете спросить отдельно области и отдельно итерации, это будет разумнее, смотрите здесь: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/classification nodes/get К примеру:
https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/classificationnodes/Areas?api-version=4.1&$depth=10

    Выше я выделил курсивом ID. Они могут пригодиться, чтобы запросить данные только по ним. Скажем, вы не делаете сумашедшую глубину, а только запросили для первого уровня вложенности данные. В нём нашли интересующую вас область, взяли оттуда ID и сделали запрос уже только по этой области, указав любую глубину.
    К примеру я хочу узнать, какие области у меня затрагиваются облаком при тестировании. Документация: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/classification%20nodes/get%20classification%20nodes?view=vsts-rest-tfs-4.1 Запрос:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/classificationnodes?ids=67720&api-version=4.1&$depth=5
Результат ровно нужный мне:
{
  "count": 1,
  "value": [{
    "id": 67720,
    "identifier": "qqqq-50160cdd7253",
    "name": "KSN Statistics",
    "structureType": "area",
    "hasChildren": true,
    "children": [{
      "id": 72172,
      "identifier": "qqqq-b3dc50a52f36",
      "name": "Common",
      "structureType": "area",
      "hasChildren": false,
      "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqq-a41abe76ccf9/_apis/wit/classificationNodes/Areas/SDK/KSN%20Statistics/Common"
    }, {
      "id": 72173,
      "identifier": "qqqq-9e0f71f0225f",
      "name": "OAS",
      "structureType": "area",
      "hasChildren": false,
      "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqqq-a41abe76ccf9/_apis/wit/classificationNodes/Areas/SDK/KSN%20Statistics/OAS"
    }, {
      "id": 72174,
      "identifier": "qqqq-21887d3a12dc",
      "name": "Healthcare",
      "structureType": "area",
      "hasChildren": false,
      "url": "https://kaspersky.fake.url/tfs/DefaultCollection/qqq-a41abe76ccf9/_apis/wit/classificationNodes/Areas/SDK/KSN%20Statistics/Healthcare"
    }, {

    Ладно. Мы, вроде, собрали всё, что нам нужно. И даже больше нужного, раз запросы будем далать сами.

    Я желаю собрать все открытые баги на моём проекте, которые заводил я сам, но только те, которые я нашёл на эмуляторе (я всегда стараюсь писать конфигурацию в шапку Repro steps), но нашёл не руками, а автотестами (сам для себя в Тайтле я пишу "[Autotest]") и только по Android Q (это я тоже в заголовке указываю).

Делаю простое (документация):
POST https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/wiql?api-version=4.1
которому в Body засовываю
{
  "query": "SELECT [Title], [State], [Repro Steps] FROM WorkItems WHERE [Team Project] = @Project AND [Work Item Type] = 'Bug' AND [State] NOT IN ('Closed', 'Removed') AND [Title] CONTAINS '[autotest]' AND [Title] CONTAINS '[android q]' AND [Repro Steps] CONTAINS 'emulator' AND [Created By] = @Me ORDER BY [Created Date] DESC"
}
    Здесь @Me — это макрос, означающий того, с чьим токеном мы пришли, а @Project — макрос для проекта, указанного в самом URL (помните с чего всё началось?). Если вы не вписали свой проект в URL, то этот макрос не сработает и нужно будет явно его прописать в теле: [Team Project] ='Cons-SafeMoney-Mob'

    Этот запрос вернул всего один результат:
  "workItems": [{
    "id": 3289592,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/3289592"
  }]
И да, так оно и должно быть. Давайте глянем на этот ID подробнее. Для обращения по ID есть такой запрос (документация):
GET: https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workitems/3289592?api-version=4.1&$expand=all
Конечно, параметр $expand=all не обязателен. Он нужен только если вам хочется сразу получить нужные ссылки, если они понадобятся, дабы не делать повторных запросов. Скажем, без $expand=relations нельзя узнать, что к баге прицеплены файлы. Впрочем, если вам только аттачи и нужны, то =all будет избыточным и хватит =relations.
    Если нужно запросить информацию по нескольким ID сразу, то запрос будет несколько иным (документация):
GET https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workitems?ids=3289592,2037578&api-version=4.1&$expand=relations
Собственно, его можно использовать и для одиночных запросов. Как видите, в адресе нет проекта (Cons-SafeMoney-Mob в моём случае). Понятия не имею, зачем он упомянут в документации. Возможно просто чтобы показать, что если он будет задан, то ничего плохого не случится.
    В этом запросе есть очень полезный параметр $fields=, который прнимает названия полей и отдаёт их в ответном json. Вот как это использовать:
GET https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workitems?ids=10000,2037578&fields=System.Title,System.State,System.WorkItemType&api-version=4.1
Возвращает только нужное:
{
  "count": 2,
  "value": [{
    "id": 10000,
    "rev": 5,
    "fields": {
      "System.WorkItemType": "Bug",
      "System.State": "Closed",
      "System.Title": "В конвенции <CUT>"
    },

    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/10000"
  }, {
    "id": 2037578,
    "rev": 3,
    "fields": {
      "System.WorkItemType": "Bug",
      "System.State": "Active",
      "System.Title": "AppControl WL <CUT>"
    },

    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/2037578"
  }]
}
    Очень полезный параметр. Уменьшает ответ, оставляя только нужное.
    Важно! Этот параметр несовместим с $expand. Собственно, это разумно, т.к. здесь мы точно говорим, что нам вернуть, а $expand накидывает пачкой всё, что встретит подходящего.

    Напомню, что вы всегда можете получить выгрузку по заранее созданному запросу. К примеру, у меня есть давно сохранённый шаблон с названием «Всё на мне» (у «ё» обязательно должны быть точки, я считаю!), куда попадает всё, что падает на меня, за исключением некоторых типов ворк айтемов. К примеру, здесь нет тестовых сценариев и шаред степов.

    Найдём эту кверю. Например, вот так (документация):
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/queries?api-version=4.1&$depth=1
Получил, среди прочего:
    "children": [{
      "id": "qqqq-e21f8a15c1a7",
      "name": "Всё на мне",
      "path": "My Queries/Всё на мне",

    От нечего делать можем посмотреть сам запрос внутри этой квери:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/queries/qqqq-e21f8a15c1a7?api-version=4.1&$expand=wiql
    В ответе:
  "wiql": "select [System.Id], [Microsoft.VSTS.Common.Priority], [System.State], [System.Title], [System.WorkItemType], [System.IterationPath] from WorkItems where [System.TeamProject] = @project and [System.WorkItemType] <> 'Shared Steps' and [System.WorkItemType] <> 'Test Case' and [System.WorkItemType] <> 'Test Suite' and [System.WorkItemType] <> 'Test Plan' and [System.AssignedTo] = @me and [System.State] <> 'Closed' order by [System.WorkItemType]",
      Ну да не важно. Фигачим сам запрос:
GET https://kaspersky.fake.url/tfs/DefaultCollection/Cons-SafeMoney-Mob/_apis/wit/wiql/82749e57-562c-4cc8-bc83-e21f8a15c1a7?api-version=4.1
    И видим, что на мне сейчас лишь 4 ворк айтема:
  "workItems": [{
    "id": 3023920,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/3023920"
  }, {
    "id": 3107950,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/3107950"
  }, {
    "id": 3199960,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/3199960"
  }, {
    "id": 3302694,
    "url": "https://kaspersky.fake.url/tfs/DefaultCollection/_apis/wit/workItems/3302694"
  }]
    Таким образом можно обращаться к любым кверям и даже самому создавать их POST запросом: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/queries/create?view=vsts-rest-tfs-4.1
   
    Ну а я получил, всё, что мне было нужно. Накидаю несколько методов в скрипт, которые будут делать заранее составленные запросы и можно прикручивать управление тестами и багами прямо из автотестов.

    Да, кстати, забыл сказать об этом по тексту выше, хотя это видно в КДПВ. У API 4.1 нельзя запросить нужное количество объектов. Часто можно ограничить (через top), но нельзя указать "дай мне 200 результатов". Такая возможность появилась позже. Так что возьмите себе за правило проверять возвращаемое количество объектов. Если оно равно 100 (обычно есть поле count) или же равно тому, что вы задали в top, то нужно делать новый запрос, передав skip=x, где x — сумма от прошлых. Т.к. по умолчанию выдаётся максимум 100 результатов для многих (не всех) запросов, то скип сначала 100, потом 200 и далее, пока count не будет меньше 100. Или меньше того top, что вы сказали в запросе.
    В пример можно привести запрос всех проектов в коллекции: https://docs.microsoft.com/en-us/rest/api/azure/devops/core/projects/list?view=vsts-rest-tfs-4.1 Он будет отдавать пачкой по 100 результатов и не больше. И в возвращаемом результате есть count (сколько выдало пунктов в этот раз), но нет total (сколько же их всего есть), даже в последних версиях API.

Комментариев нет:

Отправить комментарий