Как мы разрабатывали сервис расчета стоимости доставки для ритейлера

Представьте: вы – крупный производитель и ритейлер товаров для дома, который обрабатывает в сутки тысячи заказов по всей стране. Но при этом у вас нет единого алгоритма расчета стоимости доставки – из-за этого суммы почти всегда разные, иногда завышенные. Менеджеры вручную корректируют стоимость доставки до значений, которые не отпугнули бы клиента. При этом покупатели часто слышат разные цены от специалистов колл-центра и офлайн-магазинов. А после – оставляют жалобы или даже отказываются от покупки.

Всем привет, меня зовут Артём Кияшко, я руковожу группой аналитиков в одной крупной ИТ-компании, специализирующейся в том числе на разработке омниканальных решений. В этой статье расскажу о том, как мы разработали и внедрили сервис расчёта стоимости доставки. 

Как было раньше

Процесс расчёта доставки ритейлера не был системным, но это было и не нужно. Компания работала всего в двух городах присутствия – в Москве и Санкт-Петербурге, – и количество заказов позволяло менеджерам подсчитывали стоимость вручную, что тем не менее занимало немало времени. В своих расчётах сотрудники опирались на габаритность, вес заказа и дальность поездки.

Но с развитием бизнеса в регионах и открытием локальных складов отсутствие единого алгоритма и стандарта расчета стало проблемой. Иногда суммы за доставку получались слишком большими, особенно если адрес находился за чертой города. Расчет велся так: длину по прямой от склада до покупателя измеряли линейной в «Яндекс.Картах», умножали на фиксированную стоимость за километр и потом вручную снижали получившееся число до «приемлемого уровня». Единой системы ценообразования у менеджеров не было.

Ещё один нюанс – сезонность. Весной многие едут за город – на дачи или в загородный дом – и оформляют доставку туда. Количество дальних заказов с началом сезона заметно увеличивается – в 5-7 раз. Поэтому менеджеры тратят еще больше времени на ручной расчет стоимости доставки.

Ещё одна проблема, которая появилась с развитием бизнеса, – отсутствие единого механизма расчета доставки для всех сотрудников компании. Из-за этого были ситуации, когда менеджеры офлайн-магазинов озвучивали клиентам одну цену за услугу (предварительно снизив её до минимально возможного уровня, чтобы заказ был более привлекательным), а сотрудники колл-центра – другую, которая выше той, что назвали ранее. Реакция понятна: покупатели часто оставляли жалобы или отказывались от заказа.

Компания начала думать над решением проблемы. Одним из вариантов было подключить функциональность «Яндекс.Карт» к собственным сервисам через API. Это позволило бы менеджерам получать данные о протяженности маршрута, после чего система сама рассчитывала бы стоимость доставки аналогично калькулятору: протяженность в километрах, умноженная на стоимость (руб/км). Но в таком случае оставался открытым вопрос ценообразования – у персонала по-прежнему не было возможности объяснять покупателю, почему именно такая цена за доставку, а у его соседа по даче другая. «Так посчитала программа» – такой ответ недопустим с точки зрения менеджмента и клиентского сервиса.

Вместе с ритейлером стали изучать, как подходят к ценообразованию другие розничные сети (например, СТД «Петрович», Leroy Merlin и IKEA). Нам понравилось, что они разбивают территорию доставки на зоны. Готовых решений, которые бы соответствующим образом автоматизировали процесс, не нашли, поэтому выбрали собственную разработку.

Как создавали сервис 

  • Сбор требований

При разработке и внедрении сервиса ритейлер определил для себя следующие технические особенности:

  1. ускорение расчета для сотрудников, которые формируют заказы (менеджеры колл-центра и офлайн-магазинов);

  2. понятность ценообразования для персонала и покупателя, которая зависит от региона, габаритности заказа и формата доставки («до двери» или самовывоз из ПВЗ);

  3. простой интерфейс, понятный без инструкций, подсказки при вводе адреса, лёгкая навигация;

  4. возможность использовать сервис без привлечения внешней ИТ-команды;

  5. интеграция с внутренними ИТ-решениями (кассовой системой, интернет-магазином и др), чтобы рассчитывать стоимость по их запросам;

  6. подключение к классификатору адресов и внешним сервисам малогабаритной доставки в пункты выдачи;

  7. высокая доступность сервиса (24/7); 

  8. быстрая скорость отклика – не более 0,6 – 0,8 секунды при 100 одномоментных запросах на расчет;

  9. возможность масштабирования.

Все эти требования соответствуют запросам современных высоконагруженных интернет-магазинов не только для В2С-сегмента, но и рынка В2В.

  • Выбор ИТ-архитектуры

Для сервиса мы выбрали следующий технологический стек: фреймворк Symfony, СУБД PostgreSQL, хранилище данных Redis и UI-библиотеку React. Было несколько предпосылок такого выбора: 

  1. ИТ-предпочтения производителя. На тот момент у компании уже было несколько сервисов на подобном стеке, и её это вполне устраивало. Решили не расширять набор языков программирования, фреймворков и ПО, чтобы не создавать проблемы с наймом и содержанием команды.

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

Мы планировали сервис минималистичным и максимально простым – в виде одностраничного (SPA) веб-приложения, поэтому у него достаточно стандартная архитектура.

  • Разработка сервиса

Чтобы этот сервис можно было как использовать самостоятельно, так и встроить в прочие ИТ-продукты компании, мы создали два вида интерфейсов:

  1. простой графический, с картой и возможностью визуального выбора параметров доставки – можно использовать как готовое решение для менеджеров торговых точек и специалистов колл-центра;

  2. API, которое позволяет использовать этот сервис в любых продуктах как источник знаний о доставке и её стоимости, оставляя свободу для создания визуальных интерфейсов.

Также мы продумали в сервисе небольшую панель администрирования. Она позволяет оперировать всем набором исходных данных, которые влияют на расчёты.

  • Интеграции

Ритейлер подключил к сервису интернет-магазин, благодаря чему покупатели видят те же цены на доставку, что и менеджеры и сотрудники колл-центра. 

Заложенная изначально ИТ-архитектура позволила компании самостоятельно «прикрутить» ИТ-решение к сервисам доставки мелкогабаритных заказов в пункты выдачи.

Благодаря интеграции на карте отображаются пункты выдачи, где покупатели могут забрать малогабаритные заказы

Благодаря интеграции на карте отображаются пункты выдачи, где покупатели могут забрать малогабаритные заказы
  • Нагрузочное тестирование

Завершающий этап – тестирование для проверки соответствия сервиса требованиям быстродействия и работы под нагрузкой. На этом этапе мы изменили несколько алгоритмов работы с картами, чтобы повысить скорость ответов и обработки входящих запросов. 

Сняли показатели с использованием Redis и без него. Наглядно подтвердили целесообразность применения этого хранилища

Сняли показатели с использованием Redis и без него. Наглядно подтвердили целесообразность применения этого хранилища
Без Redis

Без Redis

Для тестирования использовали самый простой и доступный на тот момент инструмент – Apache Benchmark (ab). Запустили серию из 100 000 запросов к сервису при 100 единовременных (строго по заданию). Отчёт показал, что мы укладываемся в запрошенные 0,6 – 0,8 сек даже с запасом.

Сложности 

  • Отрисовка зон доставки

Сначала мы хотели сами рисовать зоны доставки без основы (только хардкор!) с детальным совпадением с границами регионов, как того хотел ритейлер. Но вскоре поняли, что отрисовка зон происходит не так быстро, как хотелось бы. Чтобы её ускорить, мы использовали свободно распространяемые зоны регионов и специализированное ПО для работы с картами Scribble Maps. Кстати, этот сервис оказался очень удобным – в нём много инструментов (линейка, ластик, слои, перемещение и объединение точек, поддержка карт KML и многое другое), которые сильно облегчают работу. В результате нам удалось сократить время отрисовки около трёхсот зон доставки почти в два раза. 

Отрисовка зон в сервисе Scribble Maps

Отрисовка зон в сервисе Scribble Maps
  • Попадание точки в полигон

Была сложная задача с точки зрения математики – сделать так, чтобы сервис корректно соотносил адрес и район доставки («полигон»).

Дело в том, что вся зона доставки – это своего рода полигон с множеством точек (адресов). Некоторые из них могут входить в состав друг друга (они называются «вложенными», или концентрическими), пересекаться или «перекрывать» другие полигоны (как в случае города Клин на скриншоте). Несмотря на то, что это, скорее, частные случаи, система должна определять для точки, которая относится сразу к нескольким полигонам, наименьшую стоимость. Это важно, потому что цена доставки в каждую из зон разная.

Например, город Ногинск, куда нужно доставить заказ, входит сразу в четыре полигона: маленький оранжевый вокруг самого города, а также в зелёный, светло-синий и большой оранжевый вокруг Москвы. Из этого перечня система должна определять только самый маленький, оранжевый – сам Ногинск. В этом случае ИТ-решение рассчитает корректную для покупателя стоимость доставки. Обеспечить эту функциональность и было нашей задачей.

Мы взяли за основу готовое Open Source решение. Но был нюанс: оно помогало ответить лишь на один вопрос – входит ли точка в конкретный замкнутый полигон. Этого было недостаточно. Из-за того, что математический алгоритм был сложный и ресурсоёмкий, решение не удовлетворяло требованиям по скорости ответа. Поэтому мы «допилили» и расширили это решение, оптимизировали код. И получили нужный нам результат: сервис корректно и быстро находит нужный полигон.

Результаты

Сервис позволяет менеджерам в магазине и колл-центре всегда называть правильную сумму стоимости доставки и легко объяснить, почему по одному адресу такая цена, а по другому – другая. Работа сотрудников стала быстрее: они просто вводят адрес в сервисе или сразу в интернет-магазине (зависит от того, где оформляют заказ) и моментально видят информацию о стоимости.

Сроки проекта – 6 месяцев, от запроса до демонстрации решения, готового к пилоту. Первый MVP показали уже через месяц. Бизнес отреагировал позитивно. Функциональным руководителям больше всего понравилось то, насколько менеджерам легко работать с сервисом (без обучения и инструкций), как быстро они получают информацию о стоимости доставки, а также универсальность интеграции с интернет-магазином и кассовым решением.


ссылка на оригинал статьи https://habr.com/ru/articles/738996/

Obsidian — Мой сетап

Вот я и дописал свою четвёртую статью на хабр (А ведь в начале года поставил себе цель написать хотя бы одну статью, а тут аппетит пришёл во время еды и вот четвёртая). Предыдущие раз, два и три.

Вообще бесит когда в современном мире пишут статьи-гайды или снимают видео-гайды, где самое интересное в конце. «Вы сначала дайте посмотреть что я приобрету прочитав вашу статью или посмотрев видео, а я уже приму решение смотреть или нет».

Поэтому вот сразу ссылка на мой сетап хранилища Обсидиана на гитхабе (о котором и пойдёт речь в данной статье), можно сразу его качать и тыкаться самому и если что-то не понятно подглядывать в статью. (Надо распаковать zip-файл в папку, а потом открыть открыть обсидиан и при выборе хранилища выбрать эту папку, куда распаковали zip-файл. Если у вас одно хранилище, то тогда жмём в левом нижнем углу кнопку сейфа)

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

В этом хранилище используются 10 плагинов, основные:

  • Calendar — для календаря справа.

  • Dataview — для статистики и для проектов.

  • Tasks — для задач.

  • Templater — для шаблонов и чтобы нужные заметки с запросами создавались в нужных папках и с нужными данными в запросах.

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

Disclaimer1: Мой сетап не претендует на «идеальность», в нём найдутся минусы и неудобности. Я выношу его на общее обсуждение в том числе для того, чтобы кто-то мог предложить ту или иную доработку тут в комментариях, а так же для того, чтобы новички могли сходу вкатиться в этот чудесный обсидиановый мир.
Disclaimer2: Обычно обсидиан ассоциируют с Zettelkasten, графами и прочими атомарными заметками. Я в своём подходе этого не использую, возможно еще не дорос, возможно мой подход немного про другое. В этой статье я пишу не про это.

Про роли

Первый раз столкнулся с темой ролей и сферами жизни в видео Евгении Стрелецкой, меня эта тема заинтересовала и я начал думать как приладить всё это к моей существующей системе и жизни. Но отторгало в этой системе то, что она предлагает предопределённое ограниченное кол-во сфер жизни, а именно 8. После прочтения книги 7 навыков высокоэффективных людей я понял, что это в моей системе должно называться не сферы жизни, а роли. И я определил для себя 20 ролей, которые я выполняю в своей жизни. Основные роли: муж, отец, здоровый, родственник, руководитель, водитель, отдыхатель, организатор своих дел, увлекающийся.
В обсидиане на каждую роль создана заглавная заметка роли:

  • В этой заметке есть ссылка на личную миссию (это опять же из книги 7 навыков, про личную миссию будет чуть дальше).

  • Есть ссылка на шаблон проекта по этой роли (это чтобы иметь быстрый доступ к шаблону, если вдруг требуется что-то изменить)

  • Есть кнопка создать новый проект. Она создаёт новый проект по шаблону этой роли.

  • Есть список проектов по этой роли (Это ключевой список, который позволяет визуально оценить все свои проекты по конкретной роли).

  • Есть список выполненных задач за последние 3 дня (Этот список даёт возможность оценить что было сделано по выбранной роли в ближайшее время, при необходимости период в 3 дня можно увеличить изменив параметр dur(3 day) на то кол-во дней, которые вам необходимы).

Про личную миссию

Книга «7 навыков выскооэффективных людей» (на которую я кстати набрёл в этой статье, отдельное спасибо @kostyaBro за статью и за упоминание книги) меня очень вдохновила, а в особенности идея написания ключевых принципов моей жизни. Личная миссия позволяет двигаться по намеченному вектору, не отходить от целей и помогает сверять курс. В книге Стивен Кови предлагает интересный способ составления личной миссии, а именно представить свои похороны (у них там, в этих ваших америках, на похоронах собираются все знакомые и родственники и толкают разные речи об умершем) и представить КАКИЕ значимые люди и ЧТО будут о вас говорить. Что о вас будут говорить ваши друзья, родственники, пассажиры, которые едут с вами в машине, сотрудники, коллеги, ваш супруг? Исходя из этого наметить себе значимые для вас роли, а затем написать свод принципов для самих себя, по которым вы живёте. Например: «Я как водитель понимаю, что моя цель довезти меня и моих пассажиров из точки А в точку Б в целости и безопасности».

Некоторым людям данный подход может показаться избыточным излишеством, отсутствием спонтанности, «А когда жить?» и т.д. Я безусловно рад за таких людей, но лично мне данный подход зашёл и сильно помог и до сих пор регулярно помогает, надеюсь кого-то данный подход зацепит так же как и меня и вдохновит на великие свершения.

Про проекты

Проекты хранятся в папке «Projects» и создаются по специальному шаблону. Для каждой роли свой шаблон для того, чтобы автоматом проставлялся тег с нужной ролью. Это конечно не очень удобно, если нужно внести изменения сразу в несколько шаблонов разных ролей, этот момент я бы с радостью оптимизировал, но из-за того, что изменения в шаблоны проектов я вношу не так часто => меня это не особо напрягает.
В заметке проекта хранятся:

  • Предстоящие задачи. Я расположил их сверху для того, чтобы не нужно было скроллить до них, открываешь проект и сразу видно, что надо делать, какие следующие действия.

  • Ссылки. Ну тут всё логично, очень часто по проекты надо сохранять какие-то ссылки, в закладках браузера всё это обрастает огромной свалкой ссылок, в которых потом очень просто запутаться, а тут всё стройно и логично.

  • Мысли и разное. Бывает придёт какая-то мысль по проекту, это и не задача и не ссылка, но мысль полезная, вот для этого и был создан данный раздел.

  • Мета-информация. Т.к. я добавляю новые задачи вверх заметки, то formatter мне сразу не подошёл, поскольку добавляя задачу сверху заметки он сразу ломается и я решил спрятать всю мета-информацию под отдельный коллаут, тут тег роли, тег даты и тег статуса.

  • Сделанные задачи. Сюда я временами скидываю выполненные задачи сверху вручную, это может показаться долго и муторно, но мне это позволяет дополнительно провести краткую ретроспективу. Ну и что-то медитативное в этом тоже есть.

Про-тип про Callout’ы:

Совершенно недавно познакомился с Callout’ами в обсидиан и теперь не понимаю как раньше без них жил, они помогли визуально отделить контент везде в обсидиане, что повысило юзабельность всей этой системы. С колаутами появилась красота и своеобразный феншуй. После их внедрения я прям каждый день хотел что-то делать в обсидиане, т.к. это доставляло помимо всего прочего еще и эстетическое удовольствие.

Про задачи (или следующие действия)

Все предстоящие задачи лежат сверху каждого проекта. Задача — это чёткое сформулированное следующее действие. При этом, чем чётче будет сформулировано следующее действие, тем выше шанс его сделать. Например проект «Чекап 2023» будет начинаться со следующего действия «Записаться к терапевту + ссылка на сайт клиники». У Максима Дорофеева в Джедайских техниках про это очень хорошо, на мой взгляд, написано. «Чекап 2023» — это для обезьянки сиюминутного удовольствия очень сложная непосильная задача, которая в себе объединяет такие вещи как поездка в клинику, сидение в очереди, сдавание анализов и т.д. При чём всё это надо делать сразу и это очень долго, сложно и трудозатратно. Как же заставить обезьянку не залипать в ютубчике или в играх, а заставить что-то сделать? Надо просто сформулировать обезьянко-понятную задачу, которая будет занимать минимум времени, а именно «Записаться к терапевту» и огромным бустом будет сразу к задаче прилепить ссылку на клинику, потому что даже обезьянка сможет ткнуть пару кнопок и записаться. Тем самым само собой запустится процесс и проект сдвинется с мёртвой точки. После того как эта простая задача будет выполнена, в проект добавиться следующая задача «Ехать в клинику ГГГГ-ММ-ДД ЧЧ:мм» уже со сроком выполнения. И так далее на каждое действие — задача.

При этом иногда возникает желание определить сразу все следующие действия по проекту и навесить им конкретные даты и т.д. Тем самым загнать себя в рамки, в которых потом будет неуютно, изменятся обстоятельства, потеряется гибкость. Я смирился с тем, что по некоторым проектам в моей жизни достаточно определить хотя бы одно следующее действие, а там потом само всё пойдёт и поедет, главное начать — это самое важное.

Про просроченные задачи

В большинстве тудушек просроченные задачи красятся в красный и говорят как бы «ТЫ МЕНЯ НЕ СДЕЛАЛ!!! Я ВАЖНЕЕ ЧЕМ ВСЕ ОСТАЛЬНОЕ», но у меня это чаще всего не так. У меня чаще всего так, что я ХОТЕЛ сделать их вчера, но даже если я её сделаю позже или вообще не сделаю — ничего страшного не произойдёт.

Вообще я на себе, на своих близких и знакомых стал замечать, что очень часто люди назначают задачам срок, когда на самом деле этого срока нет. Людям ХОТЕЛОСЬ БЫ сделать это в этот срок, но если задача будет сделана через месяц или год, а не сейчас — от этого ничего плохого не произойдёт. И я поставил себе цель-привычку — подмечать за собой моменты, когда я ставлю срок у задачи, но на самом деле этот срок не такой. Получилось это сделать следующим образом — я ежедневной заметке я сделал ссылку на страницу «Накопилось» — туда попадают все просроченные задачи. При этом при разгребании этого списка я чекаю — реальный ли я срок назначил задаче и чаще всего при этом процессе выясняется, что срок я назначил неадекватный, в таком случае я откладываю задачу на более адекватный срок, либо вообще на месяц или на год (Об этом ниже).

Планы на год и на месяц

Долгое время я хотел внедрить у себя систему планирования на год и месяц. И в этом году у меня начало получаться. План на год и на месяц я строю на основании inline-поля Date в проекте. В начале года я проставляю во всех проектах, котоыре хочу сделать за год в поле Date «#2023-» (тегом специально, чтобы потом можно было легко выбирать из выпадающего списка).

Затем в начале каждого месяца я начинаю просматривать все проекты на год и начинаю в некоторых добавлять какой-то конкретный месяц, например #2023-05 — это сразу помещает этот проект в месячную заметку мая.

Ну и собственно у меня есть периодическая задача-привычка хотя бы раз в неделю смотреть планы на месяц. Во время выполнения этой задачи я актуализирую месячные проекты, меняю у них статус. Статус — это такое же inline-поле в проекте как дата.

Статусов у меня 6 штук (теги в статусе нужны так же для того, чтобы выбирать статусы из выпадающего списка):

  • #tasks/10NEW — Это статус нового проекта. Это означает, что проект есть, а следующее действие по нему еще не определено.

  • #tasks/20WIP — Это статус Work In Progress. Это означает, что проект есть, и по нему определено следующее действие, т.е. есть задача с каким-то сроком выполнения.

  • #tasks/30REC — Это статус периодических задач. Это обычно проекты-привычки или то, что скорее всего, никогда не кончится.

  • #tasks/40DON — Это завершённые проекты. Приятно видеть в том или ином списке парочку таких. Это означает что кто-то хорошо потрудился.

  • #tasks/50CAN — Это отменённые проекты. Иногда стоит перестать мучать и заставлять себя что-то сделать, нужно найти мужество и отменить проект, признать что он потерял актуальность.

  • #tasks/60SEL — Это саморассосы. Т.е. проекты, о которых нужно помнить, но скорее всего они сами рассосуться.

Задачи на год и на месяц

Во всех заметочниках и тудушках есть поля с датой и везде надо заполнять и год и месяц и день, а мне нужно было например указать 2023-05. Это бы означало что задачу нужно сделать в мае, в любой день когда смогу, но в мае. А часть дел нужно вообще выполнить в 2023 году, без привязки к месяцу, а тем более дню. При чём есть периодические задачи, которые нужно делать каждый месяц, но не в конкретный день, а просто каждый месяц. В обсидиане в плагинах Tasks и Dataview тоже не воспринимаются обрезанные даты, но я нашёл выход — я решил ставить срок задачам в 3023 году на любой день месяца, а потом запросом Tasks фильтровать задачи за нужный месяц но только в 3023 году. Таким образом когда я закончил выполнять задачи на день (А их не оч много) я открываю список задач на месяц и начинаю делать их. При этом если есть периодические задачи, то они тоже со сроком в 3023 году и при выполнении задачи со сроком 3023-05-01 создаётся следующая задача со сроком 3023-06-01, которую я уже буду делать в следующем месяце, не первого числа, а вообще когда-то когда руки дойдёт, но в июне. Тоже самое сделать для задач на год, только срок там выставляю 4023 год, а запросом Tasks фильтрую задачи с любым сроком в 4023 — это задачи на год. Если вдруг так случилось, что я выполнил все задачи на день и на месяц, то перехожу к самой древней задаче на год.

Плюс дополнительно в месячной заметке отбираются проекты, которые я хотел бы сделать в этот месяц, раз в неделю я просматриваю месячную заметку и форсирую задачи по этим проектам.

Планы на месяц — мысли

Есть такая сущность как «Мысли», вроде бы по ним надо регулярно что-то делать, а вроде бы просто в них смотреть. Я решил, что надо пришедшие мысли выводить в месячную заметку и хотя бы 4 раза в месяц их читать каждый понедельник. Реализовал так же задачами, когда приходит мысль — делается выполненная задача с тегом #мысль и потом в ежемесячную заметку отбираются все мысли за месяц

Про одну задачу

После прочтения книги Максима Дорофеева про обезьянку сиюминутного удовольствия и рационального типа я проникся этой идей и пробовал разные подходы. В одной из итераций у меня был такой подход — сначала делаешь одну среднюю или сложную задачу, а потом делаешь пять мелких задач. Это не очень спасало, всё равно часть сложных или средних задач я продолжал прокастинировать. Реализовано это было через LIMIT 5 на лёгкие задачи, и LIMIT 1 на средние и сложные задачи. И в один прекрасный день мне пришла идея, а что если вообще на все списки задач поставить лимит = 1. Так и случилось и это принесло колоссальный результат.

Теперь моя внутренняя обезьянка сиюминутного удовольствия не пыталась перепрыгнуть с непосредственного выполнения задач на анализ «А какую задачу всё таки нужно первее сделать» или «А какая задача важнее». Теперь я не вижу всех задач на сегодняшний день, я вижу только одну задачу, с которой надо что-то делать. Её можно выполнить, можно отложить. И в финальной версии моего сетапа я оставил конечно же для себя возможность быстро пробежаться по всем задачам запланированных на сегодня, но только на предмет задач, которые жёстко привязаны ко времени. В остальном работа ведётся не в этом списке, а в списке «одна задача».

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

Про инбокс и тг2обс бота

Как-то случайно наткнулся в телеге на блог Дмитрия, подписался и через какое-то время Дмитрий выложил бота, который всю отправленную ему информацию в телеграме сохраняет в обсидиан. Мы попереписовались с Дмитрием и он в боте даже реализовал пару моих хотелок, которые упростили мне всю мою задачно-проектную рутину в 10 раз.

Т.к. телеграм — это основной для меня мессенджер и не только — это для меня очень удобный способ пополнения инбокса. Все вводные, все мысли, все задачи, все статьи, картинки или документы — тут же отправляются боту в 2 клика. Нет тебе никаких задержек, никаких 10 кликов и т.д., которые возникают когда пытаешься всё это отправить в обсидиан.

Плюс в той же книге Максима Дорофеева есть хорошая иллюстрация про то как одно письмо в себя включает много разной информации. Часть их которых это задачи, часть проекты, часть справочная инфа. После того как я поставил бота любой текст что мне пишут коллеги, друзья или жена, если в нём от меня требуются осуществить какие-то действия — тут же отправляется в телеграм, а затем при чистке инбокса это сообщение разбивается на задачи, проекты или справочную инфу.

Разгребание инбокса

Вот есть инбокс, в который сваливаются всё и раз в какой-то промежуток полезно его чистить (в идеале раз в 1-2-3 дня).

Чистка инбокса происходит так:

  • Я открываю заметку инбокс, где каждая строчка — это задача (тг2обс бот настроен так, что он переносит в инбокс текст из телеграма сразу с — [ ] в начале).

  • Затем я назначаю задаче срок через плагин Tasks хоткеем ALT + S.

  • Хоткеем ALT + E выделяю строчку с задачей.

  • Хоткеем ALT + R эту задачу перемещаю в нужный мне проект.

Ни в одном другом заметочнике или тудушке у меня не получалось разгребать инбокс так быстро как я это делаю в обсидиане, иногда это просто неприлично быстро.

Шаблон ежедневной заметки

Ежедневная заметка — это то, где происходит основная работа.

  • Сверху быстрые доступы к разному избранному (Все заметки, к которым нужен быстрый доступ я переношу в этот блок, мне так удобнее, чем лазить по файловому менеджеру в обсидиане).

  • Затем «Что сделает этот день успешным». Об этой технике я узнал из блога Армена Петросяна. Суть этой техники в том, чтобы в начале дня написать шорт-лист задач, сделав которые можно будет считать, что день успешный. Из-за этой техники иногда приходится дублировать текст задачи из какого-то проекта, но эта, якобы, двойная работа позволяет мне снизить тревожность и спланировать день так, чтобы потом не было чувства, что прожил день зря, поэтому я готов на подобные жертвы.

  • Стата за день в очках. Об этом напишу чуть ниже.

  • Одна запланированная задача. В этот блок попадает одна задача со сроком выполнения сегодня, отсортированная по дате создания.

  • Одна задача из накопилось. В этот блок попадает одна задача из списка «накопилось», т.е. задачи, у которых срок выполнения вчера или раньше.

  • Выполненное. Этот блок вмещает в себя все задачи выполненные за сегодня. Этот блок полезен для подведения итогов дня, для наглядности «Вот я какой молодец столько всего сделал сегодня».

  • Созданное. Всем задачам попадающим в телеграм бота присваивается дата создания этой задачи. Это позволяет так же наглядно увидеть сколько за сегодня задач было создано.

Про статистику по ролям

Р1, Р2, Р3, Р4 - это роль один, два, три и четыре, а последняя строчка - это сумма всех очков за месяц

Р1, Р2, Р3, Р4 — это роль один, два, три и четыре, а последняя строчка — это сумма всех очков за месяц

Изучая статьи, блоги и книги про цели, целеполагание, привычки, задачи и тайм-менеджемент и не только, я постоянно тут и там встречал разные реализации некой статистики своей жизни (Вообще я контрол-фрик и люблю всевозможную статистику, например измерение веса умными весами, или измерение сна фитнесс-браслетом и т.д.). Например Вастрик в своём доме-дурачке реализовал Bar Chart, в котором трекер на основании четырёх метрик: здоровье/спорт, саморазвитие/креативность, работа/дом, наслаждения. Дмитрий Смирнов сделал сервис HWYD, в котором можно трекать ежедневную активность по разным сферам жизни. Ну и конечно же куча статей в стиле «Как я мотивирую себя табличкой в екселе».

В итоге я пришёл к следующему:

  • У меня есть разные по трудозатратам задачи: лёгкие, средние и тяжёлые. При чём сначала это разделение было по времени 15 минут, 60 минут, 120 минут. Потом я понял, что есть не очень долгие задачи, но они отнимают много сил. И я решил делить задачи по цветам: зелёные задачи — лёгкие или быстрые, жёлтые — средние, они и в Африке средние, красные — сложные или долгие. И я стал помечать задачи соответствующими эмодзями 🟩🟨🟥.

  • Дальше к статистике. Я ввёл такое понятие как очки (Павел Комаровский по-моему писал про геймификацию жизни, почему кому-то нравится качать своего персонажа в какой-то игре, а в реальной жизни нет, ведь реальный ты — это тоже крутой чувак и круто качать себя). И за лёгкую задачу я стал начислять себе условно 1 очко, за жёлтую задачу 5 очков, а за красную задачу 10 очков.

  • Теперь надо как-то вплести всё это в текущую систему. И я решил смотреть статистку в разрезе ролей, погулял час — 5 очков в роль здоровый, посмотрел часок стендап на ютубчике — 5 очков в роль отдыхатель и так далее.

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

  • Это в конечном итоге позволяет наглядно видеть перекосы по ролям и сферам жизни, «в этом месяце я много работаю и мало отдыхаю, надо бы отдохнуть», а подтверждение в цифрах как бы автоматом оправдывает это решение и заглушает трудоголизм, который как демон на плече сидит и говорит: «Да нееее, сделай еще пару задач, ты не достаточно поработал». Или так же можно увидеть наглядно, что например в этом месяце мало очков заработал в роли «Родственник» — это означает, что надо съездить к родителям или каким-то иным образом пообщаться с родственниками.

Заключение

Я дописываю эти строки и понимаю что, если я не закончу статью сейчас, то буду дописывать её еще месяц (а то и вообще заброшу это дело) и она увеличится в 2-3 раза. Поэтому на этом хватит. Всё то, что не вошло в эту статью, допишу в следующей статье.

В планах написать про:

  • плагины, которые я использую

  • про свою настройку тг2обс бота

  • про трекинг привычек с помощью телеграма, бота и плагина Tracker

  • про отдельный инстанс бота для списка Read Later

  • про всякие мелкие свистоперделки типа PuntoSwitcher и PowerToys

Заключение заключения

Если сетап понравился и появилось желание перенести все свои проекты в обсидиан, то крайне рекомендую делать это дозированно и постепенно. В противном случае можно быстро выгореть и быстро растерять энтузиазм. Можно поставить периодическую задачу раз в день «Перенести один-два-три проекта в Обсидиан» или «Потратить 15 минут на перенос проектов в Обсидиан» и таким образом постепенно мелкими шажками мигрировать в Обсидиан. Относитесь к этому процессу как к марафону, а не как к спринту.

Не стесняйтесь задавать вопросы, если что-то не получается или не понятно.

Так же советую вступать в телеграм-группу по Обсидиану, там тепло и лампово и часто помогают советами, примерами и своими кейсами.


ссылка на оригинал статьи https://habr.com/ru/articles/735858/

Как сделать автодеплой ui-kit на npm с помощью ci/cd gitlab

Всем привет!

В этой статье я хочу рассказать, как я сделал удобный автодеплой нашего ui-kit на npm с помощью pipeline’s gitlab.

Что хотелось сделать?

  • Автоматическое обновление версии пакета при пуше изменений.

  • Автоматический деплой новой версии на npm.

Автоматическое обновление версии пакета при пуше изменений

Начнем с создания файла, который будет обновлять версию нашего пакета. Тут все просто:

  1. Возьмем нашу прошлую версию из package.json

  2. Добавим к ней 1

  3. Положим обратно

Я использовал версии до 100 на каждом уровне, т.е от 0.0.1 до 99.99.99.

bumpVersion.js

import fs from 'fs'; // Берем наш package.json file const packageFile = JSON.parse(fs.readFileSync('./package.json', 'utf8')); // Берем оттуда его версию const version = packageFile.version; // разбиваем на уровни const [l3, l2, l1] = version.split('.').map(v => Number(v)); // Далее смотрим сколько уже было версий, считаем из расчета  const oldVersion = l1 + l2 * 100 + l3 * 100 * 100; // Инкрементим версию const newVersion = oldVersion + 1; // Записываем новую версию const [newL3, newL2, newL1] = [Math.floor(newVersion / (100 * 100)), Math.floor(newVersion % (100 * 100) / 100), newVersion % 100]; packageFile.version = `${newL3}.${newL2}.${newL1}`; // Кладем файл обратно fs.writeFileSync('./package.json', JSON.stringify(packageFile)); 

Далее нам нужно вызвать этот скрипт перед деплоем и запушить изменения. Для этого нам нужно написать конфигурацию для ci/cd.

Но для начала нам нужно разобраться с тем, как мы будем пушить изменения из пайплайна. Для этого нам понадобятся креды gitlab, чтобы gitlab мог идентифицировать нас как своих)

Я решил действовать через Access Tokens gitlab. Здесь у нас два варианта либо использовать Access Tokens своего аккаунта, либо использовать Access Token конкретного репозитория.

Где получить Access Token своего аккаунта

  1. Переходим по ссылке.

  2. Создаем токен с правами на чтение и запись, не забываем сохранить сам токен, потом его нельзя будет посмотреть.

Где получить Access Token конкретного репозитория

  1. Заходим в наш репозиторий.

  2. Выбираем раздел Settings -> Access Tokens, создаем токен с правами на чтение и запись, также не забываем его сохранить, так как потом его нельзя будет посмотреть.

Далее переходим к написанию самой джобы для ci/cd.

.gitlab-ci.yml

stages:   - deploy  deploy:   stage: deploy   image: node:16.17.0   before_script:     - git remote remove origin     # Создаем авторизованный remote     - git remote add origin https://<Access Token Name>:<Access Token Token>@gitlab.com/<Your Repository Name>.git   script:     # Прокидываем name + email, чтобы gitlab не сыпал ошибки     - git config --global user.name "YourName"     - git config --global user.email "YourEmail"     # Вызываем наш скрипт, который инкрементирует версию     - node bumpVersion.js     # Добавляем и пушим наши изменения в ветку откуда стригерился pipeline     - git add ./package.json     - git commit -m "bump package.json version"     # Используем опцию gitlab -o ci.skip, для того, чтобы наш коммит не тригерил новый pipeline     - git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip   rules:     - when: manual   allow_failure: false

Версия пакета обновляется, можем перейти к следующему этапу.

Автоматический деплой новой версии на npm

Начнем с того, что получим токен на https://www.npmjs.com/.

  • Заходим на https://www.npmjs.com/

  • Тыкаем на свой профиль в правом верхнем углу и выбираем Access Tokens

  • Далее Generate New Token -> Classic Token и там создаем publish токен, также не забываем его сохранить, так как его потом нельзя будет посмотреть

Дописываем нашу ci/cd джобу.

.gitlab-cy.yml

stages:   - deploy  deploy:   stage: deploy   image: node:16.17.0   before_script:     - git remote remove origin     # Создаем авторизованный remote     - git remote add origin https://<GitLab Access Token Name>:<GitLab Access Token Token>@gitlab.com/<Your Repository Name>.git     # Подставляем наш полученный npm token     - npm config set //registry.npmjs.org/:_authToken <NPM Access Token>   script:     # Прокидываем name + email, чтобы gitlab не сыпал ошибки     - git config --global user.name "YourName"     - git config --global user.email "YourEmail"     # Вызываем наш скрипт, который инкрементирует версию     - node bumpVersion.js     # Добавляем и пушим наши изменения в ветку откуда стригерился pipeline     - git add ./package.json     - git commit -m "bump package.json version"     # Используем опцию gitlab -o ci.skip, для того, чтобы наш коммит не тригерил новый pipeline     - git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip     # Публикуем наш пакет     - npm publish   rules:     - when: manual   allow_failure: false

Финальный штрих

Добавим стадию build в нашу ci/cd конфигурацию, чтобы получить полный цикл.

.gitlab-ci.yml

stages:   - deploy   - build  build:   stage: build   image: node:16.17.0   script:     - npm install     - npm run build   artifacts:     paths:       - ./dist/**   allow_failure: false   deploy:   stage: deploy   image: node:16.17.0   before_script:     - git remote remove origin     - git remote add origin https://<GitLab Access Token Name>:<GitLab Access Token Token>@gitlab.com/<Your Repository Name>.git     - npm config set //registry.npmjs.org/:_authToken <NPM Access Token>   script:     - git config --global user.name "Your Name"     - git config --global user.email "Your Email"     - node bumpVersion.js     - git add ./package.json     - git commit -m "bump package.json version"     - git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip     - npm publish   rules:     - when: manual   allow_failure: false
Полученный результат

Полученный результат

Наслаждаемся пайплайном, который все делает за нас! 🙂


ссылка на оригинал статьи https://habr.com/ru/articles/739000/

Бизнес хочет свой блог. Стоит ли начинать? Наша прощальная статья на Хабр

Привет! Меня зовут Оля, и я представитель корпоративного мира, который генерирует до 31,7% статей на Хабре. Я расскажу, стоит ли компаниям  открывать свой блог. Спойлер: это совсем не обязательно. 

Мы в Quadcode мечтали сюда попасть целых 8 лет, но все не находилось времени, а 3 года назад компания наняла бренд-менеджера (меня), чтобы сдвинуть желание иметь свой блог с мертвой точки. Так мы и начали свою блогерскую карьеру на Хабре на русском языке, а теперь переезжаем на англоязычный Medium, где будем продолжать делиться своей экспертизой и экспериментами. Это наша финальная статья, где мы не только подводим итоги нашего Хабр-путешествия, но и рассказываем про то, как начать вести собственный корпоративный блог и что вас ждет на этом пути. В статье будет много рефлексии, поэтому, если ваше настроение располагает, – добро пожаловать под кат! 

Шаг №1: Все взрослые люди, сказали: “надо”, значит надо!

Если руководитель говорит, что очень хочет блог, то можно, конечно, сразу бежать переводить деньги и выбирать красивую картинку для первой статьи “Ура, мы на Хабре”, но все-таки лучше конвертировать это “надо” в конкретное ТЗ. Тут, как и во всем: какое ТЗ – такой код/дизайн/блог (нужное подчеркнуть) и получится. 

https://www.slideshare.net/Tech_Media/

https://www.slideshare.net/Tech_Media/

Для старта достаточно ответить на один ключевой вопрос: зачем вам блог?  

Хотите закрыть вакансии?  Тогда лучше потратить деньги на бонусы, премиум-доступы в LinkedIn или оформить подписку на доставку пиццы в офис. Так у вас будет больше шансов, что вакансии закроются посредством пицца-рекомендации от коллег.

Хотите продать свой продукт? Менеджеры по продажам и перфоманс-маркетинг помогут вам быстрее. А если очень хочется именно блог, то сделайте его у себя на сайте: это и в SEO вам больше поможет, и читателей будет проще догонять ретаргетом. А на Хабре можете делать разовые публикации в коллаборации с крупными профилями.

Хотите развить сотрудников внутри? Отправьте их на обучение, устройте хакатон, что же сразу заставлять писать статьи.

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

В общем, блог – это вообще не первая необходимость, к тому же не дешевая. Подумайте о других методах, и убедитесь, что именно блог на этой площадке решает вашу задачу. 

Шаг №2: Залечь на дно

Отлично! Вы определись, зачем вам блог. Теперь давайте проверим, а точно ли у инициатора идеи блога хватит энтузиазма двигаться через все кочки дальше?

Для этого затеряйтесь с задачей на пару недель и посмотрите, бросится ли вас кто-то искать (увольнять). Идея для рискованных, но вполне может быть, что инициатора блога уже увлечет другой ивент,  и формат Хабра потеряет для него свою актуальность. 

Шаг №3: Пустите в ход Хабр-страшилки 

Если все же после шага 2 вам уже грозят раскаленной указкой (флешбэки из школы) и требуют скорее запустить блог, самое время расчехлить Хабр-страшилки, которые могут подстерегать вас на площадке.  Вот они:

  • Аудитория здесь опаснее мафии в Мексике и не прощает ошибок, вы потом бренд не отмоете.

  • Про все темы уже написано, мы вообще не можем рассказать про что-то новое.

  • Развлекательный формат под запретом. И вообще мы корпорация, нам шутить нельзя.

  • Официозный формат под запретом, а за канцеляриты здесь разнесут под самое основание.

  • Расшифровки выступлений не превратить в Хабр-статью без дополнительных недель работы эксперта: добавления в нее технической фактуры (в тексте только за счет харизмы далеко не уедешь), перепроверке данных (во время произнесения речи ошибку может и не заметят, а в тексте уж точно все найдут).

  • Формат интервью лучше оставить для подкастов, если, конечно,  герой не турбозвезда и про него в любом формате хотят узнать и прочитать.

  • Переводы можно использовать только в экстренных случаях. Если вы только начинаете, эта дорожка скользкая. За 2 года мы скрыли из ленты 1 статью, которую заминусовали. Это как раз и был наш эксперимент с переводом – брать успешные в англоязычной среде тексты и переводить их на русский язык. Так, оригинальная статья про Docker собрала на dev.to более 500 позитивных реакций, а мы на Хабре сразу же отхватили минусы.
    Мы, кстати, восстановили эту статью, вот она.
     

    #626262;">Картинка из «Луркоморья» https://www.cossa.ru/211/115997/

    #626262;»>Картинка из «Луркоморья» https://www.cossa.ru/211/115997/

Шаг №4: Новому блогу быть!

Если же вы прошли все шаги выше, а идея о создании блога так и не угасла… Ну, что же, давайте создадим свой корпоративный Хабр блог! 

Начать, конечно, стоит с найма копирайтера и деврела, чтобы помогать авторам создавать контент. В этой статье наш копирайтер написала о механике того как она это делала, с какими трудностями сталкивалась и как их преодолевала. 

Наша компания 8 лет не бралась за создание блога, потому что перед нами стояли другие приоритеты, ведь ведение блога это очень энергозатратный процесс. Перед тем как оплатить свой первый тариф на Хабре проверьте, проходит ли ваша компания под критерии ниже:

  • Деврел, копирайтер или другой специалист, кто собирается вести блог, четко понимает кому и зачем в компании он необходим. У вас есть сформированное ТЗ от CTO, HR, маркетинга или лидера мнения из IT-ветки. С этим бывают проблемы, так как если блога у вас не было, то ни вы, ни заказчики еще толком не понимают, какую заварушку вы начинаете.

  • Уже на старте у вас есть больше 4 технических экспертов, которые готовы вписываться в создание статей. Этого числа достаточно, чтобы авторы отдыхали, в блоге публиковались по 2 статьи в месяц, и параллельно у деврела/копирайтера хватало времени привлекать новых экспертов. 

  • У вас готовы и согласованы с автором, юристами, IT безопасностью, и др. первые 2-3 статьи. На этом этапе может всплыть разные вопросы и ограничения. Тут лучшее заранее обкатать процесс.

  • Важно еще заранее подумать, кто будет делать обложки и оформлять схемы/скриншоты в статьях. Это необязательно человек в штате, тут может у вас самого есть скрытые дизайн таланты или справится AI.

Гениальный пример обложки про фишинговое письмо, которую сгенерил AI

Гениальный пример обложки про фишинговое письмо, которую сгенерил AI

В зависимости от ваших задач и возможностей стоит выбрать и контент-стратегию вашего блога:

  • Писать больше фановых статей, которые продают продукт и развлекают.

  • Писать больше новостей индустрии и генерить большой поток контента.

  • Привлекать технических экспертов и создавать контент, который показывает изнанку продукта или уровень скиллов ваших сотрудников.

По каждому из вариантов лучше провести исследование тем, интересных читателям на основе статистики Хабра, SEO и др. Но сколько бы вы не анализировали, у любого варианта есть плюсы и минусы, любая активность может вызвать как позитив, так и хейт. Тут если каждого дизлайка бояться, то лучше в блогерство не ходить. Главное обрабатывать получаемый фидбек под ваши задачи.

Хорошей идеей будет пригласить ваших авторов-экспертов (или просто всех интересующихся богом) на вебинар Хабра по созданию статей. Они проходят регулярно или можно поискать их и в записи. 

Итого, в этом моменте вы уже понимаете, кому, зачем и для чего нужен блог, а также есть ли у вашей компании все необходимое для его создания. Далее разработка первых материалов, их согласование, покупка блога и торжественный день первой публикации, поздравляем, теперь вы компания-блогер! 

Что получилось у нас за 2 года на Хабре

Итак, за 2 года мы:

  • написали и опубликовали 43 статьи;

  • привлекли 30 авторов-экспертов;

  • получили более 327 800 просмотров.

За это время нам помогали 3 чудесных копирайтера: Карина, Женя и Максим, а также в компании появилась позиция деврела (супервуман Аня), ибо без деврела очень сложно идти в экспертностный контент. 

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

Визуальные метафоры от наших чудесных дизайнеров Юли и Ксюши

Визуальные метафоры от наших чудесных дизайнеров Юли и Ксюши

Что можно сказать: звезд с неба мы не хватали, в рейтингах локтями не дрались, но старались вовлекать наших экспертов, которые никогда до этого ничем подобным не занимались. В итоге нам удалось привлечь 31% технической аудитории к созданию материалов и 14% сконвертировать в публикацию. 

Мы создавали статьи руководствуясь тем, о чем было бы интересно рассказать нашим коллегам. Не уверена, что мой босс счастлива от такого подхода, так как его сложнее презентовать и обосновывать бизнесу. Изредка, все же, мы добавляли материалы общей тематики – истории технологий или занимательными фактами из индустрии, – чтобы потестить их охваты. Очевидный и грустный для меня спойлер: попсовые темы популярнее хардкорных.

Топ 3 самых просматриваемых статьи

Топ 4 хардкорных статьи, которые увидело мало людей

Хардкорные статьи мы могли создавать до 4 месяцев, а парочка таких материалов так и осталась на нашем кладбище несбывшихся публикаций. Помимо того, что такие материалы долго и сложно создавать, их читает небольшое число людей, но тут, я надеюсь, что главное не количество, а экспертность этих читателей и они (в отличие от меня) поняли все слова в этих статьях: 

Топ 3 самых непросматриваемых статьи: трилогия про менторство

Под финал блога мы решили поделиться историей про менеджерско-образовательные штуки, но они залетели в самые непопулярные статьи за все 2 года нашего пребывания на Хабре. Впредь будем знать, что про обучение нужно рассказывать как-то иначе или тестить их на других площадках:

Чему научились за это время

Мы 8 лет шли к созданию технического блога Quadcode, чтобы делиться своей экспертизой в финтех-сфере, и наконец-то пришли на Хабр 2 года назад. Нам не хотелось подходить к задаче несерьезно – опубликовать пару статей и потом исчезнуть. Поэтому мы выбрали подходящий для старта момент и старались выпускать новые материалы регулярно. За время ведения нашего аккаунта на Хабре, мы убедились, как сильно блог помогал нам ассимилировать тот опыт и достижения, которые есть у нас. Ведь очень часто глаз замыливается, хочется бежать вперед, достигать новых результатов. Блог как раз помогал нам остановиться, выдохнуть и порадоваться своим достижениям прямо сейчас. Психологи говорят, что это помогает бороться с выгоранием, но это не точно.

Спасибо сообществу Хабра, что помогли нам начать этот блогерский путь и читали нас здесь. Надеемся, наш опыт оказался кому-то полезен и позволил стать лучше.

Александр Иванов, CTO.

Все это мы делали, чтобы выводить экспертизу сотрудников вовне и формировать экспертный бренд компании. Помимо этого для сотрудников мы делаем ивенты, обучения, а для компании ведем соцсети, покупаем платное продвижение и т.д. Блог – это только один из каналов и возможностей работать с образом компании. Спасибо Хабру за возможность начать наш путь блогера. Сейчас компании важно развивать узнаваемость среди англоязычных кандидатов, а сотрудникам чаще практиковать свой английский, поэтому мы переезжаем на Medium. Увидимся там! 


ссылка на оригинал статьи https://habr.com/ru/companies/quadcode/articles/738490/

Простое должно быть простым: Палки в разметку

В предыдущей статье мы говорили об импорте кода, но подобное веселье поджидает нас и в веб разработке. Читаем, улыбаемся и берем на заметку.

Как то так получилось, что несмотря на некоторый интерес к «фронтовой» теме, основные мои задачи долгое время были только в плоскости брутального windows-десктопа. Но, тем не менее изредка приходилось что-то делать с помощью html/css/React, и каждый раз удивляться извилистым путям, ведущим к решению простейших задач.

Html/css и его вариации, предлагают могучий инструментарий для воплощения самых смелых фантазий дизайнеров. И, наверное, при регулярном и глубоком погружении в тему верстки, весь этот инструментарий «прокэширован в подкорке» и «вертится на кончиках пальцев»…

А как быть тем, кто заходит в сияющий мир CSS лишь изредка — по необходимости?

Либо нужно реализовать что-то небольшое по объему работ и усилия по изучению тонкостей просто не окупятся?

Более того, даже на восьмидесятом этаже наслоений абстракций под названием React, блуждая по кабинетам React библиотекам «MUI», «AntD» и т.д. как то не обнаруживается ожидаемая интуитивная простота в решении всех простых задач верстки!

В попытке ответить на этот вопрос, попробуем с помощью различных подходов решить задачу по разметке, схематически изображенной на этом рисунке:

Эталонная разметка (Chrome 113)

Эталонная разметка (Chrome 113)

Так как речь о приложении, а не о неком html документе, то очевидно, что разметка должна занимать ровно 100% ширины и высоты области просмотра браузера.

В описаниях кода ниже, число в круглых скобках будет означать отсылку к соответствующей строке кода.

Эпизод первый, разминочный: HTML

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

<div style="display: flex; flex-direction: column; height: 100%;">   <div style="background-color: lightblue;">Header</div>    <div style="display: flex; flex-grow: 1;"> <div style="background-color: bisque; flex-basis: content;">Left panel</div>  <div style="overflow-y: auto; flex-grow: 1;">       Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>       Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/> </div>  <div style="background-color: bisque; flex-basis: content;">Right panel</div>   </div>      <div style="background-color: lightblue;">Footer</div> </div>

Пройдемся по коду:

  • Разметку начинает flex блок(1), ориентирующий контент в виде вертикальной колонки и занимающий 100% высоты браузера. Колонку составляет Header(2), блок(4) с неким контентом требующем прокрутку и Footer(15)

  • Flex блок(4) в свою очередь занимает все свободное пространство в родительском блоке (flex‑grow: 1) и ориентирует контент в виде строки. В эту строку входят снова три блока: левая(5) и правая(12) панели и собственно контент со скроллом;

  • Left panel(5) и Right panel(12) нужно пометить flex-basis: content, чтобы они были не меньше контента.

Вроде все складно и должно заработать как надо? Мечты, все мечты…

Это не то, чем кажется!

Это не то, чем кажется!

Что здесь не так?

У блока с контентом(7) задано overflow-y: auto, что вроде бы должно создать область прокрутки для этого блока и ограничить его же видимый размер. А теперь еще раз смотрим на скриншот — выглядит так, будто overflow-y: auto применен к корневому блоку в первой строке.

Я когда увидел это чудо, уверовал в теорию заговора, будто разработчики html решили объявить войну очевидным и как следствие простым решениям. Только хардкор.

Благо, на связи был матерый «фронтовик», он посоветовал решение костыль:

<div style="display: flex; flex-direction: column; height: 100%;">   <div style="background-color: lightblue;">Header</div>    <div style="display: flex; flex-grow: 1;"> <div style="background-color: bisque; flex-basis: content;">Left panel</div>  <div style="overflow-y: auto; flex-grow: 1;">       <div style="height: 0">         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>       </div> </div>  <div style="background-color: bisque; flex-basis: content;">Right panel</div>   </div>      <div style="background-color: lightblue;">Footer</div> </div>

Смотрим на строки 8 и 11…

Еще раз смотрим…

Нет, не костыль. Магия! Зато работает. Скриншот в начале как раз снят с этого кода 🙂

Итого: нас убеждают, что:

  • явно заданная нулевая высота блока не должна сделать его невидимым;

  • overflow-y как старое ружье — стреляет куда попало, а для точной привязки к нужному блоку, нужно примотать его скотчем обернуть контент блоком с нулевой высотой;

Про то, что ширина и высота в html живут по разным правилам, даже и не заикаюсь: заметили, что высоту 100% для flex контейнера(1) нужно было указывать, а ширину — нет? Откуда здесь ноги растут понятно (ориентация на прокрутку высоченных страниц и т.д.), но так ли нужно было ломать законы логики?

Вообще тема с прокруткой, весьма широка.
  • Проблема с overflow-y не решена и в tailwindcss с тегом overflow-y-auto;

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

  • Также популярно решение, когда заголовок, хотя и находится визуально в области действия скроллбара, на самом деле не управляется им #рукалицо;

  • Про «дикие танцы» баннеров, живущих своей странноскролльной жизнью во время прокрутки сайтов, просто нет сил уже…

Помимо вышеописанных «шероховатостей», о которых нужно помнить (а вы о них забудете, если подобные вещи не делать каждый день), код выше несколько «рыхловат», много букв на «квадратный блок».

Эпизод второй, позитивный: CSS

Но что это за придирки к «старичку» html? Попробуем вынуть палки из html, создав вспомогательный файл layout.css:

html, body {    margin: 0; }  [col], [row] { display: flex; flex-basis: content;  }  [col] { flex-direction: column; height: 100%; }  [row] { flex-direction: row; }  [expand] {   flex-grow: 1; }  [scroll] { overflow-y: auto; background: white; }  [scroll] > * { height: 0; }  [bar] {     background-color: lightblue; }  [panel] {     background-color: bisque; }  [action] { background-color: aqua; }

Здесь мы определили несколько аттрибутов, каждый из которых будет задавать свой аспект поведения блоков в разметки:

  • col — блок размещающий контент в виде вертикальной колонки;

  • row — блок размещающий контент в виде горизонтальной строки;

  • expand — расширение дочернего блока у row или col до максимально возможного;

  • scroll — добавление области прокрутки (включая необходимую магию(25), задающую свойства дочернего блока);

  • bar, panel, action — просто примеры неких прочих пользовательских аттрибутов стилизующих блоки.

Диалог за кадром

— «Такой способ использования аттрибутов не является стандартным. Также кастомные аттрибуты поддерживаются не во всех браузерах! А вдруг IE дремучей версии случится?!»;

— «Ну да, 100501-й способ усложнить жизнь разработчику… А что предлагаешь?»;

— «Классы, конечно!»

Теперь пишем новый html, не забывая подключать наш css:

<link rel="stylesheet" href="layout.css" />  <div col>   <div bar>Header</div>    <div row expand> <div panel>Left panel</div>  <div scroll expand>   <div>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>     </div> </div>  <div panel>Right panel</div>   </div>    <div bar>Footer</div> </div>

Да, это рабочий html, генерирующий картинку как на первом скриншоте. Код короче, и вся магия ушла в css, помнить о ней не нужно (почти).

Проясним использование атрибутов:

  • Корневой блок(3) помечаем атрибутом col, ибо он колонка;

  • Дочерний блок(4) помечаем bar — просто оформление цветом;

  • Дочерний блок(6) — контейнер, помечаем row, чтобы он был горизонтальным и expand, чтобы занял все свободное место между панелями Left panel и Right panel;

  • Блок с контентом(9) помечаем как scroll и он становится областью прокрутки, а добавление expand, позволяет занять все доступное пространство;

  • и т.д…

Единственное, что не удалось решить в рамках css, это требование наличия блока вокруг контента scroll. Просто текст не получится. Но это не беда, далее все будет!

И хотя поставленная задача решена, простота полученного кода так и призывает «навернуть» что-нибудь еще:

<link rel="stylesheet" href="layout.css" />  <div col>   <div bar>Header</div>    <div row expand> <div col panel>Left panel</div>  <div scroll expand>   <div>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>     </div> </div>  <div panel col>       <div>         Right panel       </div>        <div scroll expand>         <div>           Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>           Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>         </div>       </div>              <div>         Right footer       </div> </div>   </div>      <div bar row>     <div expand>       Footer     </div>      <div action>       [Icon]     </div>      <div action>       [Action]     </div>   </div> </div>

Здесь мы доработали следующее:

  • Right panel(16) добавлением атрибута col, легко превращается в вертикальный контейнер, также добавляем область прокрутки(21) с контентом по образцу в строке 9. Обратите внимание: эти два блока с контентом в вертикальном и горизонтальном контейнере настраиваются абсолютно одинаково;

  • Footer(34) также просто становится горизонтальным контейнером с помощью атрибута row. На десерт, в этом блоке пробуем максимально наглядный способ размещение блоков в контейнере: если некий блок(35) с помощью expand занимает все доступное пространство, остальные(39, 43) — сиротливо теснятся в стороне (справа в данном случае). Куда еще «материальнее» ?

Вот окончательный рендер:

Усложненный пример

Усложненный пример

Понятно, что такой «дизайн» нужен далеко не всем. И вообще, творить визуальную нетленку, дело утонченных дизайнеров, а не суровых разработчиков. Хотя, местами, они жертвуют юзабилити какому-то своему божеству. Но, имея простой инструмент, логику которого не нужно вспоминать (привет блоку с нулевой высотой), будет гораздо легче воспринимать вашего дизайнера.

Эпизод третий, фееричный: MUI и Ant design

Разумеется, на чистом html + css мало кто сейчас работает. Поэтому посмотрим, что предлагают в обсуждаемой области две популярнейших библиотеки MUI и Ant design:

Что ожидаешь, обращаясь к популярным продуктам с «именем»?

Ну вероятно, что они то умеют в разметку. Сделают все удобно и лаконично. Продумают все варианты. Это были мечты. Далее суровая реальность.

Компоненты разметки MUI:

  • Container — просто центрирование по горизонтали. Это тег, не атрибут. Странно, что нет тегов Right и Left;

  • Grid — похоже мимо. Больше подходит для контента с массивом элементов;

  • Stack — горизонтальный\вертикальный контейнер, это что-то близкое к задаче. Функционал компонента Stack ограничен только выстраиванием дочерних блоков в столбик или линию. Все. Внезапно. Занавес.

Компоненты разметки AntD:

  • Space — более продвинуты аналог Container из MUI;

  • Layout — глядя на примеры, кажется, что это 100% попадание. Кажется. Механизм заполнения блоком клиентской области не продуман, т.е. без инжектирования css повторить разметку подобную целевой, не получится;

  • Grid — более продвинутый (вероятно) аналога Grid в MUI, тоже мимо;

Другими словами повторить целевую разметку без использования css не получается!

Где аналог expand на основе flex-grow или вроде того?

И\или выравнивание элементов контейнеров лево-центр-право?

До последнего момента не ожидал такого фиаско.

Итого:

Готовой функциональности по разметке для приложений, сложнее «пролайкать котиков» нам не дают, оставляя лазейку в виде низкоуровневого html + css.

Где-то это уже было? Может в html + css?

А для чего тогда эти библиотеки? Ах, да — контролы и документо-ориентированная разметка…

#совсем-рука-лицо

Я очень хочу заблуждаться, в надежде, что уважаемое комьюнити ткнет меня, как слепого котенка, в нужный компонент из состава MUI или Ant Design.

В противном случае, базовая вещь — разметка, остается не простой. И дело не в особой сложности flex контейнеров, а в том, что css содержит множество фич, объединение которых в одной задаче, может быть весьма нетривиальным делом. Поэтому наличие простой согласованной высокоуровневой компонентной обертки над html+css считаю залогом качественной React библиотеки. Такой библиотеки, которая не будет заставлять меня гадать, какие из фич css конфликтуют друг с другом вместо предоставления набора «рычагов» каждый из которых будет работать ожидаемо вне зависимости от других.

Эпизод последний: Box, just Box

В начале разработки UI в проекте GeekLoad, сразу была заложена возможность смены библиотеки компонентов (вдруг не подойдет). А так как теги разметки, самые частые в UI, то хотелось избежать массовой переделки всего и вся в этом случае.

Это, а также неудовлетворительная проработка компонентов-контейнеров в рассматриваемых библиотеках побудили к реализации очередного «велосипеда» — React компонента <Box/>.

Главное требование к компоненту было: не вставлять палки в колеса разметку, а конструировать ее понятным и предсказуемым образом.

Посмотрим, как данный компонент, реализует принцип «Простое должно быть простым…» и как можно реализовать различные кейсы в разметке с его помощью.

Для начала — аналог компонента MUI:Container:

<Box class='bar'>Content</Box>
Использование классов здесь - только для подсветки блоков, а для разметки совсем не обязательно!

Использование классов здесь — только для подсветки блоков, а для разметки совсем не обязательно!

Добавлением атрибута expand — занимаем всю клиентскую область браузера:

<Box expand class='bar'>Content</Box>
Контент в центре видимой области браузера

Контент в центре видимой области браузера

Чтобы получить прямой аналог AntD:Space, нужно просто воспользоваться свойствами hAlign и vAlign, не думая о том какая ориентация контейнера использована: горизонтальная или вертикальная (по умолчанию — горизонтальная, как в примере выше):

<Box hAlign = {Align.End}      vAlign = {Align.End}      class = 'bar' >Content</Box>

Независимость выравнивания от ориентации важна еще в тех случаях, когда ориентация задается динамически, например при переключении между мобильным или десктопным view.

Пример позиционирования дочерних элементов. Да, их может быть несколько.

Пример позиционирования дочерних элементов. Да, их может быть несколько.

Теперь кратко поясним логику компонента Box:

  • Aтрибут col распределяет контент и растягивает сам блок по вертикали, если его нет, то контент распределяется и сам блок растягивается по горизонтали;

  • Body html документа «за кадром» ведет себя как <Box col> дополнительно растянутый по горизонтали;

  • Атрибут expand растягивает Box по направлению его контейнера. Если таких блоков более одного, они делят доступное пространство поровну;

  • Атрибут scroll автоматически применяет col блоку и превращает его в область прокрутки, без дополнительных требований к контенту;

  • Атрибут gap включает отступы между блоками контента, может быть значением отступа в единицах ‘rem’. По умолчанию = 1;

  • Для выравнивания контента служат атрибуты hAlign и vAlign:

    • Align.Start — выравнивание по верхнему или левому краю;

    • Align.Center (по умолчанию) — выравнивание по центру;

    • Align.End — выравнивание по нижнему или правому краю;

    • Align.Fill — растягивание блоков отличных от Box перпендикулярно направлению блока.

  • Атрибут class — задает необходимые css классы блоку.

И затем, реализуем «целевую разметку»:

<Box expand>   <Box col expand>     <Box class = 'bar'>Header</Box>      <Box expand>       <Box col class = 'panel'>Left panel</Box>        <Box scroll expand>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>       </Box>        <Box col class = 'panel'>Right panel</Box>     </Box>      <Box class = 'bar'>Footer</Box>   </Box> </Box>

В этом примере следующая логика:

  • Корневой Box(1) служит для растягивания используемого пространства по горизонтали. Это понадобилось сделать, чтобы не нарушать логику компонента в остальных случаях.

  • Box(2) col, распределяет блоки Header(3), Content(5) и Footer(13) вертикально, а expand захватывает пространство по направлению контейнера, т.е. горизонтально;

  • Box(3) — горизонтальный блок «Header»;

  • Box(5), распределяет блоки Left panel(6), Content(8) и Right panel(13) горизонтально

  • Left panel(6) и Right panel(13) помечены атрибутом col, чтобы быть вертикально растянутыми;

Отличие от целевой разметки - только в выравнивании по умолчанию по центру.

Отличие от целевой разметки — только в выравнивании по умолчанию по центру.

И, на последок, усложняем немного задачу:

<Box expand>   <Box col expand>     <Box gap = {2} class = 'bar'>       <Box>Header</Box>       <Box expand = {2} class = 'action'>[Menu]</Box>       <Box expand class = 'action'>[Login]</Box>     </Box>      <Box expand>       <Box col class = 'panel'>Left panel</Box>        <Box scroll expand>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>         Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>       </Box>        <Box col>           <Box class = 'panel'>Right header</Box>            <Box scroll expand>             Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>             Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>           </Box>            <Box class = 'panel'>Right footer</Box>       </Box>     </Box>      <Box gap class = 'bar'>         <Box expand class = 'bar'>Footer</Box>          <Box class = 'action'>[Icon]</Box>         <Box class = 'action'>[Action]</Box>     </Box>   </Box> </Box>

Надеюсь, этот пример после предыдущих уже понятен. Если не особо — пишите в комментариях — поправлю статью.

Дизайнеры за такое убьют. Но мы им не покажем ;)

Дизайнеры за такое убьют. Но мы им не покажем 😉

Ниже, под катом, исходники упрощенной версии описываемого компонента. Разумеется какие-то решения покажутся спорными, но, уверен, кому то идея зайдет, как минимум в качестве прототипа.

Исходники <Box/>

Box.css:

#root, html, body {     margin: 0;     width: 100%;     height: 100%;     overflow: hidden; }  #root {     display: flex;     flex-direction: column;     align-items: center; }  .box {     flex-basis: content;     display: flex; }  .row {     flex-direction: row;     width: 100%; }  .col {     flex-direction: column;     height: 100%; }  .scroll {     overflow-y: auto;     overflow-x: hidden;     width: 100%;     height: 100%; }  .bar {     background-color: lightblue; }  .panel {     background-color: bisque; }  .action {     background-color: aqua; }

Box.tsx:

import {ReactNode} from 'react'; import './Box.css'  export enum Align {     Start = 'start',     End = 'end',     Center = 'center',     Fill = 'stretch' }  export default (props: {     class?: string     scroll?: boolean     hAlign?: Align     vAlign?: Align     col?: boolean     expand?: boolean | number     children?: ReactNode     gap?: boolean | number }) => {     const hAlign = props.scroll ? Align.Fill : props.hAlign || Align.Center     const vAlign = props.scroll ? Align.Fill : props.vAlign || Align.Center     const gap = (props.gap || false) ? (((typeof props.gap) == 'boolean' ? 1 : props.gap) + 'rem') : undefined     const expand = Number((props.expand || false) ? (((typeof props.expand) == 'boolean' ? 1 : props.expand)) : undefined)      return <div className = {         ('box ' +             (props.col ? 'col ' : 'row ') +             (props.scroll ? 'scroll ' : '') +             (props.class ? props.class : '')         ).trim()     }                 style = {{                     flexGrow: expand,                     justifyContent: props.col ? vAlign : hAlign,                     alignItems: props.col ? hAlign : vAlign,                      rowGap: props.col ? gap : undefined,                     columnGap: props.col ? undefined : gap                 }}     >         {props.scroll             ? <div style = {{height: 0}}>{props.children}</div>             : props.children}     </div> }  export function AppScreen(props: { children: ReactNode }) {     return <div style = {{height: '100%'}}>{props.children}</div> }

— «А в чем была суть вообще?»

На самом деле, различных усложнений в html + css разметке можно насобирать еще порядочно, но хотелось взять какой то кейс и довести его до решения, которое показалось бы достаточным.

Так как считаю разметку в большинстве случаев простой задачей, ожидаю от инструментов для ее реализации — простоты. И в ситуациях, когда Box перестает «дружить» с каким то вариантом разметки, он дорабатывается так, чтобы и предыдущие и новые кейсы лаконично поддерживались.

Здесь и кроется ответ на вопрос в начале статьи: Если в некой предметной области вы не частый гость, то хорошим подходом будет консолидация «сложности&магии» в неких точках. Box — как раз один из таких. Вместо того, чтобы по всему коду приложения раскидывать некие решения задач разметки, лучше собрать их в одном месте, а далее везде использовать краткие и понятные атрибуты. Даже если вы забудете суть найденных решений, логичный фасад вокруг них, избавит вас от мучительных попыток «вспомнить все».

Очень жаль, что мэтры компонентостроения множат сущности там, где это не нужно, но не прорабатывают некоторые очевидные кейсы.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Стоит ли продолжить цикл статей в подобном ключе, по разным темам?
33.33% Да 2
33.33% Нет 2
33.33% Сначала писать научись 2
Проголосовали 6 пользователей. Воздержались 5 пользователей.

ссылка на оригинал статьи https://habr.com/ru/articles/730342/