Исполнитель, который здесь больше не работает

Product

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

Функция небольшая. Выпадающий список людей из рабочего пространства, id в колонке, чип на карточке. Полдня работы.

А вот вопрос, который никто не задаёт, пока это не случится, совсем не маленький: Марта уходит. Что станет с сорока заметками, на которых стоит её имя?

Участник — это свойство, а не поле

Первое решение было скучным, и скучным намеренно.

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

Разница видна везде, кроме кода, который это хранит. Раз участник — это свойство:

  • Таких свойств может быть несколько. Исполнитель и Проверяющий — два свойства, а не изменение схемы.

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

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

Вся функция — это один новый вариант примерно в дюжине конструкций switch, и каждая отказывается компилироваться, пока этот вариант не обработан. Чек-лист за меня написал компилятор.

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

Где хранится id

Значение участника — это id пользователя. Был соблазнительный короткий путь: у значений свойств уже есть колонка valueText, а id пользователя — это текст. Положить туда и выпустить на день раньше.

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

Аккаунты удаляются. Когда кто-то удаляет свой аккаунт в Notez, его строка исчезает, и все ссылки на неё должны исчезнуть вместе с ней. Иначе остаётся значение, которое указывает на несуществующего пользователя, и каждое чтение вынуждено на всякий случай отфильтровывать призраков. С внешним ключом и on delete cascade база данных делает это сама, правильно и навсегда, одной строкой схемы. С valueText это было бы на мне: ручная очистка в хуке удаления аккаунта, плюс фильтр висячих id на каждом пути чтения, плюс баг, который рано или поздно уедет в релиз, когда кто-нибудь добавит двенадцатый путь чтения и забудет об этом.

CHECK-ограничение, которое требует, чтобы ровно одна колонка с данными была не пустой, выросло с пяти колонок до шести. Это вся цена.

Главный вопрос

Теперь самое интересное. Марту удаляют из рабочего пространства, или она уходит сама. Её имя стоит на сорока заметках. Вариантов ровно три:

Запретить удаление. Не давать удалить человека, пока с него не сняты все назначения. Это худший вариант, и чтобы понять почему, хватит секунд десяти: Марта хочет уйти из рабочего пространства, но не может, пока владелец вручную не отредактирует сорок заметок. Защитный механизм, который держит человека в заложниках, — не защитный механизм.

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

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

Я выбрал третий вариант и отказался от плана с надгробиями, пока его записывал.

Почему здесь надгробия проигрывают

Для заметки в корзине надгробие — правильное решение. Заметка всё ещё существует, лежит в корзине, и её можно восстановить в течение тридцати дней. Надгробие на что-то указывает и говорит правду: цель в корзине.

Ушедший участник не в корзине. Восстанавливать нечего. И ломаются две конкретные вещи:

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

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

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

Прецедент для удаления нашёлся прямо в том же файле. Удаление варианта из списка уже каскадом стирает его значения со всех заметок. Диалог подтверждения при удалении участника уже говорит: Участник потеряет доступ ко всем заметкам этого рабочего пространства. Снятие назначений согласуется и с тем, и с другим.

Гонка, которую транзакция не ловит

Есть и четвёртый сценарий, который транзакция не покрывает, и о нём стоит сказать честно.

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

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

Что намеренно не сделано

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

  • Доски, сгруппированные по исполнителю. Доска привязана к собственному свойству-колонке. Группировка по произвольному свойству — изменение крупнее этого.

  • «Назначено мне». Фильтр по своему имени в один клик был очевидным следующим шагом, и он уже вышел: включите Показывать в боковой панели у свойства-участника, и боковая панель покажет всех в рабочем пространстве с числом их заметок. Один клик по своему имени — и остаются только ваши.

  • Уведомления. Назначение вас никак не оповещает. Скажите коллеге сами.

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

Где была настоящая работа

Добавить поле легко. Функцией поле делает решение о том, как оно ведёт себя в дни, когда всё идёт не по плану: когда человек уходит, когда удаляется аккаунт, когда две записи идут наперегонки.

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

Читайте также