[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"question-jira-kak-sozdat-custom-field-type":3},{"id":4,"slug":5,"topicId":6,"topicSlug":7,"topicName":8,"topicEmoji":9,"question":10,"answer":11,"codeLang":12,"codeSrc":12,"important":12,"commonMistakes":12,"modernUsage":12,"difficulty":13,"tags":14,"related":18,"progress":19,"seo":20},926,"kak-sozdat-custom-field-type",27,"jira","Jira","📋","Как создать Custom Field Type?","Custom Field Type позволяет создать собственный тип поля задачи с кастомной логикой ввода, отображения, валидации и поиска.\n\n### Объявление и реализация\n\n\u003Cdetails>\n\u003Csummary>atlassian-plugin.xml и PriorityScoreField\u003C\u002Fsummary>\n\n```xml\n\u003Ccustomfield-type key=\"priority-score-field\"\n                  class=\"com.example.plugin.field.PriorityScoreField\">\n    \u003Cname>Priority Score\u003C\u002Fname>\n    \u003Cdescription>Числовой приоритет с расчётом на основе severity и impact\u003C\u002Fdescription>\n    \u003Cresource name=\"view\" type=\"velocity\"\n              location=\"templates\u002Ffields\u002Fpriority-score-view.vm\"\u002F>\n    \u003Cresource name=\"edit\" type=\"velocity\"\n              location=\"templates\u002Ffields\u002Fpriority-score-edit.vm\"\u002F>\n    \u003Cresource name=\"xml\" type=\"velocity\"\n              location=\"templates\u002Ffields\u002Fpriority-score-xml.vm\"\u002F>\n\u003C\u002Fcustomfield-type>\n```\n\n```java\n@Named\npublic class PriorityScoreField extends AbstractSingleFieldType\u003CDouble> {\n\n    private static final Logger log = LoggerFactory.getLogger(PriorityScoreField.class);\n\n    @Inject\n    public PriorityScoreField(\n            @ComponentImport CustomFieldValuePersister persister,\n            @ComponentImport GenericConfigManager configManager) {\n        super(persister, configManager);\n    }\n\n    @Override\n    protected PersistenceFieldType getDatabaseType() {\n        return PersistenceFieldType.TYPE_DECIMAL;\n    }\n\n    @Override\n    protected Object getDbValueFromObject(Double value) {\n        return value;\n    }\n\n    @Override\n    protected Double getObjectValue(String dbValue) {\n        if (dbValue == null) return null;\n        try {\n            return Double.parseDouble(dbValue);\n        } catch (NumberFormatException e) {\n            log.warn(\"Невозможно распарсить значение: {}\", dbValue);\n            return null;\n        }\n    }\n\n    @Override\n    public String getStringFromSingularObject(Double value) {\n        return value != null ? value.toString() : \"\";\n    }\n\n    @Override\n    public Double getSingularObjectFromString(String s)\n            throws FieldValidationException {\n        if (s == null || s.trim().isEmpty()) return null;\n        try {\n            double value = Double.parseDouble(s);\n            if (value \u003C 0 || value > 100) {\n                throw new FieldValidationException(\n                        \"Priority Score должен быть от 0 до 100\");\n            }\n            return value;\n        } catch (NumberFormatException e) {\n            throw new FieldValidationException(\"Некорректное числовое значение: \" + s);\n        }\n    }\n\n    @Override\n    public Map\u003CString, Object> getVelocityParameters(Issue issue,\n            CustomField field, FieldLayoutItem fieldLayoutItem) {\n        Map\u003CString, Object> params = super.getVelocityParameters(\n                issue, field, fieldLayoutItem);\n\n        Double value = (Double) issue.getCustomFieldValue(field);\n        if (value != null) {\n            String cssClass;\n            if (value >= 80) cssClass = \"aui-lozenge-error\";\n            else if (value >= 50) cssClass = \"aui-lozenge-current\";\n            else cssClass = \"aui-lozenge-success\";\n            params.put(\"cssClass\", cssClass);\n            params.put(\"formattedValue\", String.format(\"%.1f\", value));\n        }\n        return params;\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Velocity-шаблоны\n\n```html\n\u003C!-- templates\u002Ffields\u002Fpriority-score-edit.vm -->\n\u003Cinput type=\"text\" id=\"$customField.id\" name=\"$customField.id\"\n       value=\"$!{value}\" class=\"text short-field\" placeholder=\"0-100\"\u002F>\n\u003Cdiv class=\"description\">Введите числовое значение от 0 до 100\u003C\u002Fdiv>\n```\n\n```html\n\u003C!-- templates\u002Ffields\u002Fpriority-score-view.vm -->\n#if($formattedValue)\n    \u003Cspan class=\"aui-lozenge $cssClass\">$formattedValue\u003C\u002Fspan>\n#else\n    \u003Cspan class=\"aui-lozenge\">Не задано\u003C\u002Fspan>\n#end\n```\n\n### Частые ошибки\n\n- Не реализовать Searcher — поле нельзя использовать в JQL и фильтрах\n- Не обрабатывать null\u002Fпустые значения в `getSingularObjectFromString()` — NPE при создании задачи\n- Тяжёлая логика в `getVelocityParameters()` — вызывается при каждом отображении задачи\n- Изменение `getDatabaseType()` после деплоя — несовместимость с существующими данными\n\n### Как используется в 2026\n\n- Custom Field Type остаётся мощным инструментом для DC\n- В Cloud кастомные поля создаются через Forge UI Kit или Connect (iframe)\n- Тренд на использование JSON-полей (StringLength.UNLIMITED) для сложных структур данных\n\n> **На собеседовании:** покажите понимание жизненного цикла кастомного поля: edit (ввод) -> validate -> persist -> view (отображение). Для простых типов наследуйтесь от AbstractSingleFieldType, для множественных значений — от AbstractMultiCFType. Searcher обязателен для доступа поля в JQL.","","senior",[15,16,17,7],"custom-field","velocity","jira-plugin",[],null,{"title":21,"description":22,"ogTitle":23,"ogDescription":24,"keywords":25,"schemaAnswer":34,"featuredSnippetReady":35},"Как создать Custom Field Type в Jira DC — Gymterview","Custom Field Type для Jira DC: AbstractSingleFieldType, валидация, Velocity-шаблоны (view\u002Fedit), Searcher для JQL. Полный пример PriorityScoreField.","Custom Field Type: кастомные поля в Jira DC — Gymterview","Как создать кастомное поле Jira: наследование от AbstractSingleFieldType, валидация, view\u002Fedit шаблоны, Searcher для JQL.",[26,27,28,29,30,31,32,33],"Custom Field Type","Jira кастомное поле","AbstractSingleFieldType","Velocity","Searcher","JQL","Jira DC plugin","Java","Custom Field Type создаётся через наследование от AbstractSingleFieldType (одиночное значение) или AbstractMultiCFType (множественное). Методы: getDatabaseType (тип хранения), getSingularObjectFromString (валидация), getVelocityParameters (данные для шаблонов). Шаблоны: view, edit, xml — обязательные. Searcher обязателен для доступа в JQL.",true]