diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f1e9760 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# модули +* text=auto +*.os text eol=lf diff --git a/.gitignore b/.gitignore index ec6c24e..64afd8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -build/ -*.log -*.ospx +build/ +*.log +*.ospx +*.back diff --git a/README.md b/README.md index 6a5beaf..91fb0e5 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,24 @@ Часто возникают ситуации, когда в разных проектах используется общая кодовая база и есть потребность исключить замечания родительского проекта, т.к. исправление их в дочернем самостоятельно не планируется. Переносить руками все замечания достаточно проблематично. Для закрытия замечаний необходимо вызвать команду `issue-resolver` и передать ей информацию для авторизации, данные родительского и дочерних проектов. + +#### Логика закрытия замечаний + +SonarQube может создавать несколько замечаний по одному правилу на одну строку, на один символ / подстроку, что приводит к формированию одноко хэша строки. Примером таких замечаний например являются правила наличия пробелов у знаков математических операций и отсутствие описаний параметров методов. + +Для гарантии корректрого закрытия замечаний дочернего проекта ввене алгоритм, при котором закрытие в довернем проекте замечний возможно только при выполнении следующих условий: + +- количество одинаковых замечаний (с одинаковым хэшем и правилом) должно совпадать с родительским +- количество одинаковых незакрытых замечаний в дочернем проекте должны быть большим или равным количеству закрываемых замечаний из родительского проекта + +В дочернем проекте будет закрыто то количество замечаний, которое неоьбходимо для выравнивая по количеству с родительским. + +**Примеры:** + +**Пример 1:** В родительском проекте есть 3 замечания на строке, исправлено одно. В дочернем тоже 3 замечания, исправленных нет. В результате - бцдет закрыто одно замечание в дочернем проекте. + +**Пример 2:** В родительском проекте есть 3 замечания на строке, исправлено одно. В дочернем тоже 3 замечания, исправленных 2. В результате - в дочернем проекте не будет закрыто ни одного нового замечания. + +**Пример 3:** В родительском проекте есть 3 замечания на строке, исправлено два. В дочернем тоже 3 замечания, исправленных 1. В результате - в дочернем проекте будет закрыто еще одно замечание, и общее количество закрых станет 2. + +**Пример 4:** В родительском проекте есть 3 замечания на строке, исправлено два. В дочернем 4 замечания, исправленных 1. В результате - в дочернем проекте не будет закрыто ни одного нового замечния. diff --git a/examples/example.cfg-file.json b/examples/example.cfg-file.json index 004cae1..4f11a38 100644 --- a/examples/example.cfg-file.json +++ b/examples/example.cfg-file.json @@ -5,5 +5,6 @@ "--child-projects": "zarplataiupravleniepersonalomkorp:master,zarplataiupravleniepersonalomprof:develop", "--exclude-projects": "upravleniepredpriiatiem:develop", "--statuses": "CLOSED", - "--edt2cfg": false + "--edt2cfg": false, + "--tags": "vendor" } diff --git a/installlocalhost.bat b/installlocalhost.bat new file mode 100644 index 0000000..fb6f30a --- /dev/null +++ b/installlocalhost.bat @@ -0,0 +1,12 @@ +@echo off +call del "*.ospx" + +for /f %%i in ('"oscript -version"') do set result=%%i + +if %result%==1.0.19.105 ( + call opm build . -mf ./packagedef -out . +) else ( + call opm build -m ./packagedef -o . +) + +call opm install -f *.ospx diff --git a/packagedef b/packagedef index 8070ef4..50a269e 100644 --- a/packagedef +++ b/packagedef @@ -1,17 +1,16 @@ -ПутьКСценариюПараметров = ОбъединитьПути(ТекущийСценарий().Каталог, "src", "Модули", "ПараметрыСистемы.os"); +ПутьКСценариюПараметров = ОбъединитьПути(ТекущийСценарий().Каталог, "src", "Модули", "ПараметрыСистемы.os"); + ПараметрыСистемы_ЛокальнаяВерсия = ЗагрузитьСценарий(ПутьКСценариюПараметров); Описание.Имя(ПараметрыСистемы_ЛокальнаяВерсия.ИмяПродукта()) .Версия(ПараметрыСистемы_ЛокальнаяВерсия.ВерсияПродукта()) - .ВерсияСреды("1.0.20") + .ВерсияСреды("1.0.21") .ЗависитОт("logos", "1.2.0") .ЗависитОт("cmdline", "1.0.0") .ЗависитОт("1commands", "1.3.5") - .ВключитьФайл("src") .ВключитьФайл("packagedef") - .ВключитьФайл("readme.md") + .ВключитьФайл("README.md") .ВключитьФайл("LICENSE") .ВключитьФайл("examples") - .ИсполняемыйФайл("src/main.os", "sonar-helper") diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\272\321\200\321\213\321\202\321\214\320\230\321\210\321\203\320\267\321\213.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\272\321\200\321\213\321\202\321\214\320\230\321\210\321\203\320\267\321\213.os" index a53f8db..b78d09c 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\272\321\200\321\213\321\202\321\214\320\230\321\210\321\203\320\267\321\213.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\272\321\200\321\213\321\202\321\214\320\230\321\210\321\203\320\267\321\213.os" @@ -7,6 +7,8 @@ Перем ИсключаемыеПроекты; Перем Статусы; Перем ИзEDTВКонфигуратор; +Перем Теги; +Перем МаксимальноеКоличествоСтатусов; /////////////////////////////////////////////////////////////////////////////////////////////////// // Прикладной интерфейс @@ -32,6 +34,8 @@ | | По умолчанию выбираются только RESOLVED и CLOSED"); Парсер.ДобавитьПараметрФлагКоманды(ОписаниеКоманды, "--edt2cfg", "Необходимо использовать тогда, когда родительский проект в формате EDT, а дочерний в формате конфигуратора"); + Парсер.ДобавитьИменованныйПараметрКоманды(ОписаниеКоманды, "--tags", "Список тегов, которые будут добавлены к закрываемым замечаниям. + | Необходимые теги необходимо передавать строкой через запятую."); Парсер.ДобавитьИменованныйПараметрКоманды(ОписаниеКоманды, "--cfg-file", "Путь к конфигурационному файлу в формате json (utf-8), свойства объекта соответствуют ключам запуска. | Пример конфигурационного файла находится в каталоге examples"); @@ -65,7 +69,7 @@ ЗамечанияРодительскогоПроекта = Сонар.ПолучитьЗамечанияПроекта( АдресСервера, Токен, ОписаниеРодительскогоПроекта, - Статусы, + СтрСоединить(Статусы, ","), ИзEDTВКонфигуратор); Если НЕ ЗамечанияРодительскогоПроекта.Количество() Тогда @@ -74,7 +78,13 @@ Возврат МенеджерКомандПриложения.РезультатыКоманд().Успех; КонецЕсли; - + + Если Статусы.Количество() <> МаксимальноеКоличествоСтатусов Тогда + Сонар.ЗаполнитьКоличествоОдинаковыхЗамечанийПроекта( + АдресСервера, Токен, + ЗамечанияРодительскогоПроекта); + КонецЕсли; + Если ДочерниеПроекты.Найти("#all") <> Неопределено Тогда ДочерниеПроекты.Очистить(); Для Каждого ОписаниеПроекта Из ПроектыSQ Цикл @@ -93,7 +103,7 @@ Лог.Информация("Закрытие замечаний в дочерних проектах"); Для Каждого ДочернийПроект Из ДочерниеПроекты Цикл - Лог.Информация("Закрытие замечаний в проекте `%1`", ДочернийПроект); + Лог.Информация("Закрытие замечаний в проекте `%1`", ДочернийПроект); ОписаниеПроекта = ПроектыSQ.Получить(СокрЛП(ДочернийПроект)); Если ОписаниеПроекта = Неопределено Тогда Лог.Ошибка("Проект `%1` не обнаружен", ДочернийПроект); @@ -103,18 +113,21 @@ ЗакрываемыеЗамечания = Сонар.ПолучитьЗакрываемыеЗамечания(АдресСервера, Токен, ОписаниеПроекта, ЗамечанияРодительскогоПроекта); Если ЗакрываемыеЗамечания.Количество() Тогда Лог.Информация("Для проекта `%1` будет закрыто замечаний: `%2` ", ДочернийПроект, ЗакрываемыеЗамечания.Количество()); - Сонар.ЗакрытьЗамечания(АдресСервера, Токен, ЗакрываемыеЗамечания, "Привнесено '" + РодительскийПроект + "'"); + Сонар.ЗакрытьЗамечания(АдресСервера, Токен, ЗакрываемыеЗамечания, "Привнесено '" + РодительскийПроект + "'", Теги); + Иначе + Лог.Информация("Для проекта `%1` нет закрываемых замечаний", ДочернийПроект); КонецЕсли; КонецЦикла; Возврат МенеджерКомандПриложения.РезультатыКоманд().Успех; -КонецФункции +КонецФункции /////////////////////////////////////////////////////////////////////////////////////////////////// Функция ПрочитатьПараметрыЗапуска(ПараметрыКоманды) + МаксимальноеКоличествоСтатусов = 5; ОшибокНет = Истина; Токен = ПараметрыКоманды["--token"]; @@ -123,6 +136,7 @@ ДочерниеПроектыСтрокой = ПараметрыКоманды["--child-projects"]; ИсключаемыеПроектыСтрокой = ПараметрыКоманды["--exclude-projects"]; СтатусыСтрокой = ПараметрыКоманды["--statuses"]; + Теги = ПараметрыКоманды["--tags"]; ИзEDTВКонфигуратор = ПараметрыКоманды["--edt2cfg"]; ПутьККонфигурационномуФайлу = ПараметрыКоманды["--cfg-file"]; @@ -154,6 +168,9 @@ Если СтатусыСтрокой = Неопределено Тогда СтатусыСтрокой = ПараметрыИзФайла.Получить("--statuses"); КонецЕсли; + Если Теги = Неопределено Тогда + Теги = ПараметрыИзФайла.Получить("--tags"); + КонецЕсли; Если ИзEDTВКонфигуратор = Ложь И ПараметрыИзФайла.Получить("--edt2cfg") <> Неопределено Тогда ИзEDTВКонфигуратор = ПараметрыИзФайла.Получить("--edt2cfg"); КонецЕсли; @@ -171,6 +188,7 @@ Лог.Отладка(" Ключ дочерних проектов (строкой) = `%1`", ОбернутьЗначениеДляПечати(ДочерниеПроектыСтрокой)); Лог.Отладка(" Ключ исключаемых проектов (строкой) = `%1`", ОбернутьЗначениеДляПечати(ИсключаемыеПроектыСтрокой)); Лог.Отладка(" Статусы строкой = `%1`", ОбернутьЗначениеДляПечати(СтатусыСтрокой)); + Лог.Отладка(" Устанавливаемые теги = `%1`", ОбернутьЗначениеДляПечати(Теги)); Лог.Отладка(" Нужно переводить из EDT = `%1`", ОбернутьЗначениеДляПечати(ИзEDTВКонфигуратор)); Если Не ЗначениеЗаполнено(Токен) Тогда @@ -204,7 +222,24 @@ Если Не ЗначениеЗаполнено(СтатусыСтрокой) Тогда Статусы = СтрРазделить("RESOLVED,CLOSED", ",", Ложь); Иначе - Статусы = СтрРазделить(СтатусыСтрокой, ",", Ложь); + Статусы = Новый Массив(); + СтатусыВрем = СтрРазделить(ВРЕГ(СтатусыСтрокой), ",", Ложь); + ВсеСтатусы = СтрРазделить("OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED", ","); + Для Каждого Статус Из СтатусыВрем Цикл + Если ВсеСтатусы.Найти(Статус) <> Неопределено Тогда + Статусы.Добавить(Статус); + Иначе + Лог.Ошибка("Неопознанный статус `%1`", Статус); + КонецЕсли; + КонецЦикла; + Если Статусы.Количество() = 0 Тогда + Лог.Ошибка("Указанный фильтр по статусам не содержит корректных значений"); + ОшибокНет = Ложь; + КонецЕсли; + КонецЕсли; + + Если НЕ ЗначениеЗаполнено(Теги) Тогда + Теги = ""; КонецЕсли; Возврат ОшибокНет; diff --git "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\241\320\270\321\201\321\202\320\265\320\274\321\213.os" "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\241\320\270\321\201\321\202\320\265\320\274\321\213.os" index 8717ab5..23a4dcd 100644 --- "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\241\320\270\321\201\321\202\320\265\320\274\321\213.os" +++ "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\241\320\270\321\201\321\202\320\265\320\274\321\213.os" @@ -27,7 +27,7 @@ // Функция ВерсияПродукта() Экспорт - Версия = "1.0.1"; + Версия = "1.1.3"; Возврат Версия; КонецФункции // ВерсияПродукта() diff --git "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\276\320\275\320\260\321\200.os" "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\276\320\275\320\260\321\200.os" index 5bad647..01d9e26 100644 --- "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\276\320\275\320\260\321\200.os" +++ "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\276\320\275\320\260\321\200.os" @@ -6,6 +6,8 @@ // /////////////////////////////////////////////////////////////////// +Перем МаксимальныйРазмерПорцииДанных; + /////////////////////////////////////////////////////////////////// // Программный интерфейс /////////////////////////////////////////////////////////////////// @@ -63,7 +65,7 @@ // ОписаниеПроекта - Структура - Описание проекта SonarQube // * Идентификатор - Строка - Идентификатор проекта // * Код - Строка - Код (Ключ) проекта -// Статусы - Массив - Массив строковых идентификаторов статусов замечаний +// СтатусыСтрокой - Строка - Строка с идентификаторами статусов замечаний // ИзEDTВКонфигуратор - Булево - Признак необходимости преобразования замечаний между родительским проектом и дочерними // // Возвращаемое значение: @@ -71,28 +73,52 @@ // * Ключ - Строка - Относительный путь к файлу, в котором зафиксировано замечание // * Значение - Структура - Описание проекта // ** ПутьКФайлу - Строка - Относительный путь к файлу, в котором зафиксировано замечание -// ** Ошибки - Соответствие - Набор зарегистрированных замечаний (ошибок) -// *** Ключ - Строка - Хэш замечания -// *** Значение - Структура - Описание замечания -// **** ПутьКФайлу - Строка - Относительный путь к файлу, в котором зафиксировано замечание -// **** Код - Строка - Ключ замечания -// **** Хэш - Строка - Хэш замечания +// ** Правила - Соответствие - Набор правил с замечаниями +// *** Ключ - Строка - ключ правила +// *** Значение - Структура - Описание правила +// **** Правило - Строка - Ключ правила +// **** Ошибки - Соответствие - Набор зарегистрированных замечаний (ошибок) +// ***** Ключ - Строка - Хэш замечания +// ***** Значение - Структура - Описание замечания +// ****** Код - Строка - Ключ замечания +// ****** Хэш - Строка - Хэш замечания +// ****** Количество - Число - Количество закрываемых замечаний +// ****** КоличествоВсего - Число - Общее количество замечаний с данным хэшем // -Функция ПолучитьЗамечанияПроекта(АдресСервера, Токен, ОписаниеПроекта, Статусы, ИзEDTВКонфигуратор) Экспорт +Функция ПолучитьЗамечанияПроекта(АдресСервера, Токен, ОписаниеПроекта, СтатусыСтрокой, ИзEDTВКонфигуратор) Экспорт - URLШаблон = "issues/search?ps=500&statuses=%1&projectUuids=%2&p="; + КлючиКомпонентов = ПолучитьКлючиКомпонентов(АдресСервера, Токен, ОписаниеПроекта.Код, СтатусыСтрокой); Замечания = Новый Соответствие(); + URLШаблон = "issues/search?ps=500&componentKeys=%1%2"; + Если Не ПустаяСтрока(СтатусыСтрокой) Тогда + URLШаблон = URLШаблон + "&statuses=" + СтатусыСтрокой; + КонецЕсли; + URLШаблон = URLШаблон + "&p="; - Для Каждого Статус Из Статусы Цикл + КоличествоКомпонентов = КлючиКомпонентов.Количество(); + Для Ит = 0 По КоличествоКомпонентов - 1 Цикл + КлючКомпонента = КлючиКомпонентов[Ит]; НомерСтраницы = 1; + URL = СтрШаблон(URLШаблон, КлючКомпонента, ""); Пока Истина Цикл - URL = СтрШаблон(URLШаблон, ВРег(СокрЛП(Статус)), ОписаниеПроекта.Идентификатор); + Ответ = ВыполнитьЗапрос(АдресСервера, Токен, URL + Формат(НомерСтраницы, "ЧГ="), "GET"); + Если Ответ.total > МаксимальныйРазмерПорцииДанных Тогда + // прочитаем компоненты, перенесем их в общий список, а по текущем прочтем только те, что висят именно на нем + КлючиДочернихКомпонентов = ПолучитьКлючиКомпонентов(АдресСервера, Токен, КлючКомпонента, СтатусыСтрокой, Ложь); + Для Каждого КлючДочернегоКомпонента Из КлючиДочернихКомпонентов Цикл + КлючиКомпонентов.Добавить(КлючДочернегоКомпонента); + КонецЦикла; + КоличествоКомпонентов = КлючиКомпонентов.Количество(); + URL = СтрШаблон(URLШаблон, КлючКомпонента, "&onComponentOnly=true"); + Ответ = ВыполнитьЗапрос(АдресСервера, Токен, URL + Формат(НомерСтраницы, "ЧГ="), "GET"); + КонецЕсли; Для Каждого ОписаниеОшибки Из Ответ.issues Цикл ПутьКФайлу = СтрЗаменить(ОписаниеОшибки.component, ОписаниеПроекта.Код + ":", ""); + Если ИзEDTВКонфигуратор Тогда ПутьКФайлу = СтрЗаменить(ПутьКФайлу, "configuration/src/", "src/configuration/"); ПутьКФайлу = СтрЗаменить(ПутьКФайлу, "/ManagerModule.bsl", "/Ext/ManagerModule.bsl"); @@ -108,35 +134,92 @@ КонецЕсли; НовоеОписаниеОшибки = Новый Структура(); - НовоеОписаниеОшибки.Вставить("ПутьКФайлу", ПутьКФайлу); НовоеОписаниеОшибки.Вставить("Код", ОписаниеОшибки.key); - Хэш = ПолучитьХэшЗамечания(ОписаниеОшибки, ПутьКФайлу); + НовоеОписаниеОшибки.Вставить("Количество", 1); + НовоеОписаниеОшибки.Вставить("КоличествоВсего", 1); + Хэш = ПолучитьХэшЗамечания(ОписаниеОшибки); НовоеОписаниеОшибки.Вставить("Хэш", Хэш); - + ТекущийМодуль = Замечания.Получить(ПутьКФайлу); Если ТекущийМодуль = Неопределено Тогда - ТекущийМодуль = Новый Структура("ПутьКФайлу, Ошибки", ПутьКФайлу, Новый Соответствие()); + ТекущийМодуль = Новый Структура("ПутьКФайлу, Правила, Ключ", ПутьКФайлу, Новый Соответствие(), ОписаниеОшибки.component); + КонецЕсли; + ТекущееПравило = ТекущийМодуль.Правила.Получить(ОписаниеОшибки.rule); + Если ТекущееПравило = Неопределено Тогда + ТекущееПравило = Новый Структура("Правило, Ошибки", ОписаниеОшибки.rule, Новый Соответствие()); КонецЕсли; - ТекущийМодуль.Ошибки.Вставить(Хэш, НовоеОписаниеОшибки); + ТекущаяОшибка = ТекущееПравило.Ошибки.Получить(Хэш); + Если ТекущаяОшибка = Неопределено Тогда + ТекущаяОшибка = НовоеОписаниеОшибки; + Иначе + ТекущаяОшибка.Количество = ТекущаяОшибка.Количество + 1; + ТекущаяОшибка.КоличествоВсего = ТекущаяОшибка.КоличествоВсего + 1; + КонецЕсли; + + ТекущееПравило.Ошибки.Вставить(ТекущаяОшибка.Хэш, ТекущаяОшибка); + ТекущийМодуль.Правила.Вставить(ТекущееПравило.Правило, ТекущееПравило); Замечания.Вставить(ПутьКФайлу, ТекущийМодуль); - + КонецЦикла; Если БольшеНетДанных(Ответ) Тогда Прервать; КонецЕсли; - + НомерСтраницы = НомерСтраницы + 1; - + КонецЦикла; КонецЦикла; - Возврат Замечания; КонецФункции +// ЗаполнитьКоличествоОдинаковыхЗамечанийПроекта +// По сформированному набору замечаний дополняет общее количество замечаний со всеми статусами с тем же хэшем +// Параметры: +// АдресСервера - Строка - Адрес (хост) сервера SonarQube +// Токен - Строка - Токен пользователя, от имени которого выполняются запросы к API +// ЗамечанияРодительскогоПроекта - Соответствие - Замечания для закрытия из родительского проекта. См. ПолучитьЗамечанияПроекта +// +Процедура ЗаполнитьКоличествоОдинаковыхЗамечанийПроекта(АдресСервера, Токен, ЗамечанияРодительскогоПроекта) Экспорт + + URLШаблон = "issues/search?componentKeys=%1&rules=%2&ps=500&p="; + Для Каждого Файл Из ЗамечанияРодительскогоПроекта Цикл + Для Каждого Правило Из Файл.Значение.Правила Цикл + Для Каждого Замечание Из Правило.Значение.Ошибки Цикл + Замечание.Значение.КоличествоВсего = 0; + КонецЦикла; + + URL = СтрШаблон(URLШаблон, Файл.Значение.Ключ, Правило.Значение.Правило); + НомерСтраницы = 1; + + Пока Истина Цикл + Ответ = ВыполнитьЗапрос(АдресСервера, Токен, URL + Формат(НомерСтраницы, "ЧГ="), "GET"); + Для Каждого ОписаниеОшибки Из Ответ.issues Цикл + Хэш = ПолучитьХэшЗамечания(ОписаниеОшибки); + ТекущаяОшибка = Правило.Значение.Ошибки.Получить(Хэш); + Если ТекущаяОшибка = Неопределено Тогда + Продолжить; + КонецЕсли; + + ТекущаяОшибка.КоличествоВсего = ТекущаяОшибка.КоличествоВсего + 1; + + КонецЦикла; + + Если БольшеНетДанных(Ответ) Тогда + Прервать; + КонецЕсли; + + НомерСтраницы = НомерСтраницы + 1; + + КонецЦикла; + КонецЦикла; + КонецЦикла; + +КонецПроцедуры + // ПолучитьЗакрываемыеЗамечания // Возвращает набор открытых замечаний проекта, которые необходимо закрыть // Параметры: @@ -148,53 +231,72 @@ // ЗамечанияРодительскогоПроекта - Соответствие - Замечания для закрытия из родительского проекта. См. ПолучитьЗамечанияПроекта // // Возвращаемое значение: -// Соответствие - Коллекция замечаний -// * Ключ - Строка - Хэш замечания -// * Значение - Структура - Описание замечания -// ** ПутьКФайлу - Строка - Относительный путь к файлу, в котором зафиксировано замечание -// ** Код - Строка - Ключ замечания -// ** Хэш - Строка - Хэш замечания +// Массив - ключи закрываемых замечаний // Функция ПолучитьЗакрываемыеЗамечания(АдресСервера, Токен, ОписаниеПроекта, ЗамечанияРодительскогоПроекта) Экспорт - URLШаблон = "issues/search?ps=500&statuses=OPEN,CONFIRMED,REOPENED&projectUuids=%1&componentKeys=%2&p="; Замечания = Новый Соответствие(); - - Для Каждого МодульСЗмечаниями Из ЗамечанияРодительскогоПроекта Цикл - - URL = СтрШаблон(URLШаблон, ОписаниеПроекта.Идентификатор, ОписаниеПроекта.Код + ":" + МодульСЗмечаниями.Ключ); - НомерСтраницы = 1; - Пока Истина Цикл + Статусы = СтрРазделить("OPEN,CONFIRMED,REOPENED", ",", ЛОЖЬ); + + URLШаблон = "issues/search?componentKeys=%1&rules=%2&ps=500&p="; + Для Каждого Файл Из ЗамечанияРодительскогоПроекта Цикл + Для Каждого Правило Из Файл.Значение.Правила Цикл + URL = СтрШаблон(URLШаблон, ОписаниеПроекта.Код + ":" + Файл.Значение.ПутьКФайлу, Правило.Значение.Правило); + НомерСтраницы = 1; - Ответ = ВыполнитьЗапрос(АдресСервера, Токен, URL + Формат(НомерСтраницы, "ЧГ="), "GET"); - Для Каждого ОписаниеОшибки Из Ответ.issues Цикл - - ПутьКФайлу = СтрЗаменить(ОписаниеОшибки.component, ОписаниеПроекта.Код + ":", ""); - Хэш = ПолучитьХэшЗамечания(ОписаниеОшибки, ПутьКФайлу); + Пока Истина Цикл + Ответ = ВыполнитьЗапрос(АдресСервера, Токен, URL + Формат(НомерСтраницы, "ЧГ="), "GET"); + Для Каждого ОписаниеОшибки Из Ответ.issues Цикл + Хэш = ПолучитьХэшЗамечания(ОписаниеОшибки); + РодительскоеЗамечание = Правило.Значение.Ошибки.Получить(Хэш); + Если РодительскоеЗамечание = Неопределено Тогда + Продолжить; + КонецЕсли; + + КлючОшибки = ОписаниеОшибки.component + Хэш; + ПутьКФайлу = СтрЗаменить(ОписаниеОшибки.component, ОписаниеПроекта.Код + ":", ""); + ТекущаяОшибка = Замечания.Получить(КлючОшибки); + Если ТекущаяОшибка = Неопределено Тогда + ТекущаяОшибка = Новый Структура(); + ТекущаяОшибка.Вставить("КоличествоВсего", 0); + ТекущаяОшибка.Вставить("Количество", 0); + ТекущаяОшибка.Вставить("РодительскоеЗамечание", РодительскоеЗамечание); + ТекущаяОшибка.Вставить("Ключи", Новый Массив()); + КонецЕсли; + + ТекущаяОшибка.КоличествоВсего = ТекущаяОшибка.КоличествоВсего + 1; + Если Статусы.Найти(ОписаниеОшибки.status) <> Неопределено Тогда + ТекущаяОшибка.Количество = ТекущаяОшибка.Количество + 1; + ТекущаяОшибка.Ключи.Добавить(ОписаниеОшибки.key); + КонецЕсли; + + Замечания.Вставить(КлючОшибки, ТекущаяОшибка); + + КонецЦикла; - Если МодульСЗмечаниями.Значение.Ошибки.Получить(Хэш) = Неопределено Тогда - Продолжить; + Если БольшеНетДанных(Ответ) Тогда + Прервать; КонецЕсли; - - НовоеОписаниеОшибки = Новый Структура(); - НовоеОписаниеОшибки.Вставить("ПутьКФайлу", ПутьКФайлу); - НовоеОписаниеОшибки.Вставить("Код", ОписаниеОшибки.key); - НовоеОписаниеОшибки.Вставить("Хэш", Хэш); - Замечания.Вставить(Хэш, НовоеОписаниеОшибки); + + НомерСтраницы = НомерСтраницы + 1; КонецЦикла; - - Если БольшеНетДанных(Ответ) Тогда - Прервать; - КонецЕсли; - - НомерСтраницы = НомерСтраницы + 1; - КонецЦикла; - + КонецЦикла; + + ЗамечанияДляОбработки = Новый Массив(); + Для Каждого Замечание Из Замечания Цикл + Если Замечание.Значение.КоличествоВсего <> Замечание.Значение.РодительскоеЗамечание.КоличествоВсего Тогда + Продолжить; + КонецЕсли; + + МожноЗакрыть = Замечание.Значение.Количество - Замечание.Значение.РодительскоеЗамечание.КоличествоВсего + Замечание.Значение.РодительскоеЗамечание.Количество; + Для Ит = 0 По МожноЗакрыть - 1 Цикл + ЗамечанияДляОбработки.Добавить(Замечание.Значение.Ключи[Ит]); + КонецЦикла; КонецЦикла; - Возврат Замечания; + Возврат ЗамечанияДляОбработки; КонецФункции @@ -203,26 +305,35 @@ // Параметры: // АдресСервера - Строка - Адрес (хост) сервера SonarQube // Токен - Строка - Токен пользователя, от имени которого выполняются запросы к API -// ЗакрываемыеЗамечания - Соответствие - Замечания, которые привнесены из родительского проекта и требуют закрытия. См. ПолучитьЗакрываемыеЗамечания +// ЗакрываемыеЗамечания - Массив - Ключи замечаний для закрытия // Комментарий - Строка - Текстовое сообщение, которое будет добавлено как коментарий к закрываемому замечанию +// Теги - Строка - Список тегов, разделенных запятой, которые будут добавлены к закрываемым замечаниям. // -Процедура ЗакрытьЗамечания(АдресСервера, Токен, ЗакрываемыеЗамечания, Комментарий) Экспорт +Процедура ЗакрытьЗамечания(АдресСервера, Токен, ЗакрываемыеЗамечания, Комментарий, Теги = "") Экспорт URL = СтрШаблон("issues/bulk_change?do_transition=wontfix&comment=%1&issues=", Комментарий); + URLДобавленияТегов = СтрШаблон("issues/bulk_change?add_tags=%1&issues=", Теги); + ЗамечанияДляОбработки = Новый Массив(); Для Каждого Замечание Из ЗакрываемыеЗамечания Цикл Если ЗамечанияДляОбработки.Количество() = 100 Тогда - ВыполнитьЗапрос(АдресСервера, Токен, URL + СтрСоединить(ЗамечанияДляОбработки, ","), "POST"); + // Сначала отдельно устанавливаем теги, так как если это делать одновременно с закрытием замечаний, + // то теги не устанавливаются. + КлючиЗамечаний = СтрСоединить(ЗамечанияДляОбработки, ","); + ВыполнитьЗапрос(АдресСервера, Токен, URLДобавленияТегов + КлючиЗамечаний, "POST"); + ВыполнитьЗапрос(АдресСервера, Токен, URL + КлючиЗамечаний, "POST"); ЗамечанияДляОбработки.Очистить(); КонецЕсли; - ЗамечанияДляОбработки.Добавить(Замечание.Значение.Код); + ЗамечанияДляОбработки.Добавить(Замечание); КонецЦикла; Если ЗамечанияДляОбработки.Количество() Тогда - ВыполнитьЗапрос(АдресСервера, Токен, URL + СтрСоединить(ЗамечанияДляОбработки, ","), "POST"); + КлючиЗамечаний = СтрСоединить(ЗамечанияДляОбработки, ","); + ВыполнитьЗапрос(АдресСервера, Токен, URLДобавленияТегов + КлючиЗамечаний, "POST"); + ВыполнитьЗапрос(АдресСервера, Токен, URL + КлючиЗамечаний, "POST"); КонецЕсли; КонецПроцедуры @@ -237,12 +348,18 @@ КонецФункции -Функция ПолучитьХэшЗамечания(ОписаниеОшибки, ПутьКФайлу) - Возврат ПутьКФайлу + ОписаниеОшибки.rule - + ?(ОписаниеОшибки.Свойство("textRange"), +Функция ПолучитьХэшЗамечания(ОписаниеОшибки) + + Хэш = ОписаниеОшибки.rule; + Если ОписаниеОшибки.Свойство("hash") Тогда + Возврат Хэш + ОписаниеОшибки.hash + ?(ОписаниеОшибки.Свойство("line"), ОписаниеОшибки.line, ""); + Иначе + Возврат Хэш + ?(ОписаниеОшибки.Свойство("textRange"), "" + ОписаниеОшибки.textRange.startLine + ОписаниеОшибки.textRange.endLine + ОписаниеОшибки.textRange.startOffset + ОписаниеОшибки.textRange.endOffset, ""); + КонецЕсли; + КонецФункции Функция ВыполнитьЗапрос(АдресСервера, Токен, URL, Операция) @@ -264,8 +381,62 @@ Возврат ПрочитатьJson(json); КонецЕсли; - ВызватьИсключение - "Код ответа: " + ОтветHTTP.КодСостояния + Символы.ПС - + "Ответ: " + ОтветHTTP.ПолучитьТелоКакСтроку(); + ТекстИсключения = СтрШаблон("Код ответа: %1 + |Ответ: %2 + |URL: %3", + ОтветHTTP.КодСостояния, ОтветHTTP.ПолучитьТелоКакСтроку(), URL); + ВызватьИсключение ТекстИсключения; КонецФункции + +/////////////////////////////////////////////////////////////////// + +Функция ПолучитьКоличествоЗамечанийКомпонента(АдресСервера, Токен, КлючКомпонента, СтатусыСтрокой = "") + + URL = "issues/search?ps=1&componentKeys=" + КлючКомпонента; + Если Не ПустаяСтрока(СтатусыСтрокой) Тогда + URL = URL + "&statuses=" + СтатусыСтрокой; + КонецЕсли; + ОтветСервера = ВыполнитьЗапрос(АдресСервера, Токен, URL, "GET"); + + Возврат ОтветСервера.total; + +КонецФункции + +Функция ПолучитьКлючиКомпонентов(АдресСервера, Токен, КлючКомпонента, СтатусыСтрокой, ТолькоКаталоги = Истина) + + КлючиКомпонент = Новый СписокЗначений(); + КоличествоЗамечанийПроекта = ПолучитьКоличествоЗамечанийКомпонента(АдресСервера, Токен, КлючКомпонента, СтатусыСтрокой); + Если КоличествоЗамечанийПроекта > МаксимальныйРазмерПорцииДанных Тогда + + URLШаблон = СтрШаблон("components/tree?component=%1%2&ps=500&p=", КлючКомпонента, ?(ТолькоКаталоги, "&qualifiers=DIR", "")); + НомерСтраницы = 1; + Пока Истина Цикл + URL = URLШаблон + Формат(НомерСтраницы, "ЧГ="); + Ответ = ВыполнитьЗапрос(АдресСервера, Токен, URL, "GET"); + + Для Каждого ОписаниеКомпонента Из Ответ.components Цикл + КлючиКомпонент.Добавить(ОписаниеКомпонента.key); + КонецЦикла; + + Если БольшеНетДанных(Ответ) Тогда + Прервать; + КонецЕсли; + + НомерСтраницы = НомерСтраницы + 1; + + КонецЦикла; + + Иначе + + КлючиКомпонент.Добавить(КлючКомпонента); + + КонецЕсли; + КлючиКомпонент.СортироватьПоЗначению(); + Возврат КлючиКомпонент.ВыгрузитьЗначения(); + +КонецФункции + +/////////////////////////////////////////////////////////////////// + +МаксимальныйРазмерПорцииДанных = 10000;