Я поддерживаю собственный сервер Gitlab для своей компании, и всякий раз, когда объявляется о новом выпуске, я проверяю Блог выпусков Gitlabs, чтобы узнать, что нового и что влияет на мой уровень. Но нет возможности отфильтровать функции по моему уровню, поэтому я теряю значительное количество времени, читая хорошие функции, которые на самом деле не применимы к моей реальности.

Оглядываясь назад

В Части 1 мы проанализировали проблему и проверили, достаточно ли информации на странице релиза, чтобы запустить наш проект.

И мы определили следующие направления работы:

  • Найдите узел в DOM с информацией об уровне (SaaS и Self-Managed).
  • Для каждого уровня оцените значки для каждой лицензии и найдите те, которые доступны, с учетом действительного значения лицензии (БЕСПЛАТНО, ПРЕМИУМ, ULTIMATE).
  • Наконец, найдите функцию, которой принадлежит этот значок, и скройте ее на странице, не нарушая макеты страниц.
  • Создайте функцию, которая получает два параметра, уровень (Saas и Self-Managed) и лицензию (FREE, PREMIUM, ULTIMATE), и использует их для фильтрации функций с помощью кода, созданного ранее.
  • Убедитесь, что мы можем отменить фильтрацию и снова отобразить все функции на странице, добавить уровень и лицензию под названием All.

Теперь давайте код

Найдите уровень

Оглядываясь назад на структуру DOM, мы видим, что уровни и уровни лицензий содержатся в badge-container , а сама информация об уровне находится внутри div с классом badge-container-type.

Таким образом, с помощью следующего запроса мы можем легко найти все узлы:

document.querySelectorAll('.badge-container-type')

Но что произойдет, если мы хотим получить только узлы для определенного уровня? Используя querySelector, поскольку у нас нет выражения для оценки innerText узла, нам нужно будет отфильтровать NodeList после того, как он будет возвращен из DOM, примерно так:

Array.from(document.querySelectorAll('.badge-container-type')).filter(x => x.innerText === 'SaaS')

Поскольку NodeList является итерируемым списком, нам нужно сначала преобразовать его в массив, после чего мы сможем получить доступ к методу фильтра и определить наш фильтр.

Но есть еще один способ получения узлов, который позволяет нам определить все это без необходимости выполнять преобразования или фильтрацию, он называется XPath, так как же вышеприведенная строка будет выглядеть с выражением XPath?

document.evaluate(`//div[contains(@class, 'badge-container-type') and text()='SaaS']`, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)

Это вернет Iterable XPathResult, и, вызвав iterateNext(), мы сможем пройти через затронутые узлы.

Фильтрация лицензии

Теперь, когда у нас есть все уровни, мы хотим также вернуть только те уровни, которые соответствуют искомой лицензии, поэтому нам нужно изменить наш код, чтобы справиться с этим. Итак, вернемся к структуре DOM, все наши значки находятся внутри div с классом top-row или bottom-row, они тоже не вложены под тиром, а родственны ему, аналогично тиру, уровню лицензии находится в свойстве innerText, и то, что определяет, доступна ли эта функция для этой лицензии, — это класс available, присутствующий в div:

//div[contains(@class, 'badge-container-type') and text()='SaaS']/following-sibling::div/a/div[text()='FREE' and not(contains(@class,"available"))]

Но мы не хотим возвращать список значков, мы хотим знать, какой уровень затронут, и вот где сияет XPath, поскольку он позволяет двунаправленный запрос структуры DOM, мы можем написать запрос, который может выглядеть в свойствах родственного элемента, но по-прежнему возвращают разные узлы, возвращаясь к тем, с которыми мы действительно хотим работать:

//div[contains(@class, 'badge-container-type') and text()='SaaS']/following-sibling::div/a/div[text()='FREE' and not(contains(@class,"available"))]/ancestor::div[contains(@class, "badge-container")]/div[contains(@class, 'badge-container-type') and text()='SaaS']

Теперь у нас будут все уровни SaaS, для которых БЕСПЛАТНАЯ лицензия недоступна.

Расположение объекта

В конце концов, мы хотим найти элемент функции, чтобы мы могли скрыть/показать его, не нарушая макет страницы, так как наш запрос XPath мы нашли все объекты уровня, которые соответствуют нашему запросу, теперь мы нужно изменить то, что мы возвращаем, чтобы мы могли получить реальную функцию.

К счастью, поскольку их DOM так хорошо организован, Gitlab позволяет нам легко запрашивать DOM, чтобы найти эти элементы, поэтому мы можем просто:

document.querySelectorAll('.release-row')

Это дает нам все узлы, имеющие класс release-row.

Итак, если мы хотим получить все выпуски, мы можем просто добавить это в запрос:

//div[contains(@class, 'badge-container-type') and text()='SaaS']/following-sibling::div/a/div[text()='FREE' and not(contains(@class,"available"))]/ancestor::div[contains(@class, "release-row")

Сделанный! Верно? На самом деле нет, потому что, если вы прокрутите немного вниз, мы найдем вторичные выпуски, они содержатся в своем собственном столбце внутри release-row , поэтому, если мы хотим иметь список вторичных функций, мы можем' Чтобы не полагаться на поиск строки, нам нужно запросить столбец:

document.querySelectorAll('.secondary-column-feature');

Если мы снова проанализируем DOM, мы увидим закономерность: первичные выпуски находятся в пределах release-row , вторичные выпуски также находятся в пределах release-row, но разница заключается в добавлении класса divider, поэтому с этим знанием мы можем различать первичные и вторичные строки. Итак, для вторичных функций мы хотим нацелиться на столбец с удобным именованным классом secondary-column-feature , к счастью, XPath позволяет нам использовать операторы OR и AND, поэтому теперь мы можем изменить наш запрос следующим образом:

//div[contains(@class, 'badge-container-type') and text()='SaaS']/following-sibling::div/a/div[text()='FREE' and not(contains(@class,"available"))]/ancestor::div[contains(@class, "release-row") and not(contains(@class, "divider")) or contains(@class, "secondary-column-feature")]

Давайте разберем каждый элемент нашего запроса, ancestor::divсообщает XPath, что мы хотим найти первого предка из точки, где мы находимся, это div и этот contains(@class, “release-row”), который отфильтровывает только те, которые имеют класс release-row и and not(contains(@class, “divider”)), у которых нет класса divider или or contains(@class, “secondary-column-feature”)], имеет класс secondary-column-feature.

Большой! Это даст нам список всех функций, которые соответствуют нашим критериям фильтра!

Наконец-то немного кодинга

Со всеми этими исследовательскими запросами теперь мы можем написать функцию, которая использует этот запрос для поиска всех недоступных функций:

Эта функция вернет наш XPathResult со списком недоступных узлов. Итак, теперь все, что осталось сделать, это скрыть функции, недоступные для нашего уровня/лицензии:

Отлично, теперь мы скрываем все функции, которые нам недоступны, все, что осталось, — это позволить нам отменить нашу фильтрацию, чтобы мы снова могли видеть все функции:

Итак, разбивая последнюю функцию, что мы делаем, так это в самой первой строке показываем все функции на странице, это возвращает все, что мы могли скрыть на странице. Затем мы проверяем, не равны ли уровень и лицензия all , затем нам нужно определить фильтры, если они не равны all, мы должны их отфильтровать.
Затем мы можем собрать строку XPath и продолжить скрытие любого узла, возвращенного из запроса.

Таким образом, вы можете просто открыть консоль в хроме на одной из страниц выпуска Gitlab, например Выпуск Gitlab 15.3, и вставить суть, изменить параметры на то, что вы хотите и вуаля, все функции, которые не относятся к фильтру, будут скрыты от глаз.

Но что мы можем сделать, чтобы сделать этот опыт лучше??!!!

Расширения Chrome FTW! Увидимся в части 3