カスタム レポート

このページの内容

お困りですか?

アトラシアン コミュニティをご利用ください。

コミュニティに質問

このプロジェクトのコードは、こちらの公開リポジトリから入手できます。

ここでは例として、給与情報を表示する新しいレポート タイプを作成します。このカスタム レポートは、ユーザーからパラメーターを受け入れて定義されたデータを明確に出力し、そのデータに基づいてレポートを生成します。 

結果は次のようになります。

To create the custom report, we will start by creating a new Jira plugin following the instructions provided by Atlassian. Next, we will implement the Insight Widget Framework in the basic plugin, which allows us to use the report interfaces that are exposed. Finally, we will implement the Widget Module in the plugin descriptor, which registers our custom report widget as a plugin within Assets.

新しい Jira アプリの作成

アトラシアンが提供するガイドに従って、新しい Jira プラグインを作成します。この例に従うと、基本的な POM ファイルが生成されます。このファイルには、レポートに必要なさまざまな依存関係やプラグインを追加できます。

以下のサンプル POM には、今回の例に使用したレポートに必要な依存関係とプラグインが含まれています。これは参照用として作成されているため、以下に示すすべてのコードを実装することはお勧めしません。

サンプル プロジェクトを作成したら、基本プラグインに Insight Widget Framework を実装します。これによって、公開されているレポート インターフェイスを使用できるようになります。ウィジェット フレームワークは次の 3 部構成です。

  • ウィジェット パラメーター (入力)
  • ウィジェット データ (出力)
  • ウィジェット モジュール (エンジン)

ウィジェット パラメーターの実装

ウィジェット パラメーターは、レポートの生成に使用されるパラメーター (フィールド) を表します。

これを実現するには、WidgetParameters クラスを実装します。

WidgetParameters の例...
package com.riadalabs.jira.plugins.insight.reports.payroll;
 
import com.google.common.collect.Lists;
import io.riada.jira.plugins.insight.widget.api.WidgetParameters;
 
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
 
/**
 * This is what is sent from the frontend as expected inputs
 */
public class PayrollReportParameters implements WidgetParameters {
 
    private Value schema;
    private Value objectType;
 
    private Value numAttribute;
    private Value dateAttributeStartDate;
    private Value dateAttributeEndDate;
 
    private String period;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
 
    private String iql;
 
    public PayrollReportParameters() {
    }
 
    public Value getSchema() {
        return schema;
    }
 
    public void setSchema(Value schema) {
        this.schema = schema;
    }
 
    public Value getObjectType() {
        return objectType;
    }
 
    public void setObjectType(Value objectType) {
        this.objectType = objectType;
    }
 
    public Value getNumAttribute() {
        return numAttribute;
    }
 
    public void setNumAttribute(Value numAttribute) {
        this.numAttribute = numAttribute;
    }
 
    public Value getDateAttributeStartDate() {
        return dateAttributeStartDate;
    }
 
    public void setDateAttributeStartDate(Value dateAttributeStartDate) {
        this.dateAttributeStartDate = dateAttributeStartDate;
    }
 
    public Value getDateAttributeEndDate() {
        return dateAttributeEndDate;
    }
 
    public void setDateAttributeEndDate(Value dateAttributeEndDate) {
        this.dateAttributeEndDate = dateAttributeEndDate;
    }
 
    public String getPeriod() {
        return period;
    }
 
    public Period period() {
 
        return Period.from(getPeriod());
    }
 
    public void setPeriod(String period) {
        this.period = period;
    }
 
    public LocalDateTime getStartDate() {
        return startDate;
    }
 
    public void setStartDate(LocalDateTime startDate) {
        this.startDate = startDate;
    }
 
    public LocalDateTime getEndDate() {
        return endDate;
    }
 
    public void setEndDate(LocalDateTime endDate) {
        this.endDate = endDate;
    }
 
    public String getIql() {
        return iql;
    }
 
    public void setIql(String iql) {
        this.iql = iql;
    }
 
    public LocalDate determineStartDate(Boolean doesWeekBeginOnMonday) {
        return this.period() == Period.CUSTOM ? getStartDate().toLocalDate()
                : this.period().from(LocalDate.now(), doesWeekBeginOnMonday);
    }
 
    public LocalDate determineEndDate(Boolean doesWeekBeginOnMonday) {
        final LocalDate today = LocalDate.now();
 
        return this.period() == Period.CUSTOM ? Period.findEarliest(getEndDate().toLocalDate(), today)
                : this.period().to(today, doesWeekBeginOnMonday);
    }
 
    public List<Value> getDateAttributes() {
        return Lists.newArrayList(getDateAttributeStartDate(), getDateAttributeEndDate());
    }
 
    public List<Value> getNumericAttributes() {
        return Lists.newArrayList(getNumAttribute());
    }
}

また、atlassian-plugin.xml に insight-widget モジュール型パラメーター要素を実装する必要があります。プラグイン記述子によって、パラメーターを画面上に表示させます。


パラメーターを持つウィジェットのモジュール タイプの例...
<?xml version="1.0" encoding="UTF-8"?>
 
<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}"/>
        <param name="plugin-icon">images/pluginIcon.png</param>
        <param name="plugin-logo">images/pluginLogo.png</param>
    </plugin-info>
 
    <!-- add our i18n resource -->
    <resource type="i18n" name="i18n" location="insight-report-payroll"/>
 
    <!-- resources to be imported by reports iframe -->
    <web-resource key="insight-report-payroll" i18n-name-key="Payroll Report Resource">
        <resource type="download" name="insight-report-payroll.css"
                  location="/css/insight-report-payroll.css"/>
        <resource type="download" name="insight-report-payroll.js"
                  location="/js/insight-report-payroll.js"/>
        <resource type="download" name="chart.js"
                  location="/js/lib/chart.js"/>
        <context>insight-report-payroll</context>
    </web-resource>
 
    <!-- implement insight-widget module type -->
    <insight-widget key="insight.example.report.payroll"
                    class="com.riadalabs.jira.plugins.insight.reports.payroll.PayrollReport"
                    category="report"
                    web-resource-key="insight-report-payroll"
                    name="insight.example.report.payroll.name"
                    description="insight.example.report.payroll.description"
                    icon="diagram"
                    background-color="#26a9ba">
        <!-- Map and display data -->
        <renderers>
            <renderer mapper="BarChart.Mapper"
                      view="BarChart.View"
                      label="insight.example.report.view.chart.bar"/>
            <renderer mapper="AreaChart.Mapper"
                      view="AreaChart.View"
                      label="insight.example.report.view.chart.area"
                      selected="true"/>
        </renderers>
        <!-- Export data to file -->
        <exporters>
            <exporter transformer="Transformer.JSON"
                      extension="json"
                      label="insight.example.report.exporter.json"/>
        </exporters>
        <!-- Parameters to show up in report form -->
        <parameters>
            <parameter key="period"
                       label="insight.example.report.period"
                       type="switch"
                       required="true"
                       default="CURRENT_WEEK">
                <configuration>
                    <options>
                        <option label="insight.example.report.period.current.week" value="CURRENT_WEEK"/>
                        <option label="insight.example.report.period.last.week" value="LAST_WEEK"/>
                        <option label="insight.example.report.period.current.month" value="CURRENT_MONTH"/>
                        <option label="insight.example.report.period.last.month" value="LAST_MONTH"/>
                        <option label="insight.example.report.period.current.year" value="CURRENT_YEAR"/>
                        <option label="insight.example.report.period.last.year" value="LAST_YEAR"/>
                        <option label="insight.example.report.period.custom" value="CUSTOM"/>
                    </options>
                </configuration>
            </parameter>
            <parameter key="startDate"
                       type="datepicker"
                       label="insight.example.report.period.custom.start"
                       required="true">
                <configuration>
                    <dependency key="period">
                        <value>CUSTOM</value>
                    </dependency>
                </configuration>
            </parameter>
            <parameter key="endDate"
                       type="datepicker"
                       label="insight.example.report.period.custom.end"
                       required="true">
                <configuration>
                    <dependency key="period">
                        <value>CUSTOM</value>
                    </dependency>
                </configuration>
            </parameter>
            <parameter key="schema"
                       type="schemapicker"
                       label="insight.example.report.schema"
                       required="true">
            </parameter>
            <parameter key="objectType"
                       type="simpleobjecttypepicker"
                       label="insight.example.report.objecttype"
                       required="true">
                <configuration>
                    <dependency key="schema"/>
                </configuration>
            </parameter>
            <parameter key="numAttribute"
                       type="objecttypeattributepicker"
                       label="insight.example.report.attribute.numeric"
                       required="true">
                <configuration>
                    <dependency key="objectType"/>
                    <filters>
                        <value>INTEGER</value>
                        <value>DOUBLE</value>
                    </filters>
                </configuration>
            </parameter>
            <parameter key="dateAttributeStartDate"
                       type="objecttypeattributepicker"
                       label="insight.example.report.attribute.date.start"
                       required="true">
                <configuration>
                    <dependency key="objectType"/>
                    <filters>
                        <value>DATE</value>
                        <value>DATE_TIME</value>
                    </filters>
                </configuration>
            </parameter>
            <parameter key="dateAttributeEndDate"
                       type="objecttypeattributepicker"
                       label="insight.example.report.attribute.date.end"
                       required="true">
                <configuration>
                    <dependency key="objectType"/>
                    <filters>
                        <value>DATE</value>
                        <value>DATE_TIME</value>
                    </filters>
                </configuration>
            </parameter>
            <parameter key="iql"
                       type="iql"
                       label="insight.example.report.iql">
                <configuration>
                    <dependency key="schema"/>
                </configuration>
            </parameter>
        </parameters>
    </insight-widget>
</atlassian-plugin>


ウィジェット データの実装

ウィジェット データは、フロントエンド レンダラーによってレポートが使用されるフォームを表します。これを実装するには、WidgetData クラスを使用します。

WidgetData クラスの例...
package com.riadalabs.jira.plugins.insight.reports.payroll;
 
import com.fasterxml.jackson.annotation.JsonInclude;
import io.riada.jira.plugins.insight.widget.api.WidgetData;
import io.riada.jira.plugins.insight.widget.api.WidgetMetadata;
 
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
 
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
 
/**
 * This is what is sent to the frontend as wrapper for the generated reports output data
 */
public class PayrollReportData implements WidgetData {
 
    private final Map<LocalDate, List<Expenditure>> expendituresByDay;
    private final boolean hasData;
    private final boolean useIso8601FirstDayOfWeek;
 
    public static PayrollReportData empty() {
        return new PayrollReportData(Collections.EMPTY_MAP, false, false);
    }
 
    public PayrollReportData(Map<LocalDate, List<Expenditure>> expendituresByDay,
            boolean hasData,
            boolean useIso8601FirstDayOfWeek) {
        this.expendituresByDay = expendituresByDay;
        this.hasData = hasData;
        this.useIso8601FirstDayOfWeek = useIso8601FirstDayOfWeek;
    }
 
    @Override
    public boolean hasData() {
        return this.hasData;
    }
 
    @Override
    public WidgetMetadata getMetadata() {
 
        WidgetMetadata metadata = new WidgetMetadata(hasData(), getNotice());
        metadata.addOption("useIso8601FirstDayOfWeek", useIso8601FirstDayOfWeek);
 
        return metadata;
    }
 
    @JsonInclude (NON_EMPTY)
    public Map<LocalDate, List<Expenditure>> getExpendituresByDay() {
        return expendituresByDay;
    }
}


ウィジェット モジュールの実装

ウィジェット モジュールがレポートを生成します。これを実現するには、WidgetModule GeneratingDataByIQLCapability の各クラスを実装します。

WidgetModule と GeneratingDataByIQLCapability の各クラスの例...
package com.riadalabs.jira.plugins.insight.reports.payroll;
 
import com.atlassian.jira.config.properties.APKeys;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.google.common.collect.Maps;
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectSchemaFacade;
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade;
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeFacade;
import com.riadalabs.jira.plugins.insight.reports.payroll.builder.IQLBuilder;
import com.riadalabs.jira.plugins.insight.reports.payroll.builder.ReportDataBuilder;
import com.riadalabs.jira.plugins.insight.reports.payroll.validator.PayrollReportValidator;
import com.riadalabs.jira.plugins.insight.services.model.ObjectAttributeBean;
import com.riadalabs.jira.plugins.insight.services.model.ObjectBean;
import com.riadalabs.jira.plugins.insight.services.model.ObjectTypeAttributeBean;
import com.riadalabs.jira.plugins.insight.services.model.ObjectTypeBean;
import com.riadalabs.jira.plugins.insight.services.progress.model.ProgressId;
import io.riada.core.service.model.ServiceError;
import io.riada.core.service.Reason;
import io.riada.core.service.ServiceException;
import io.riada.jira.plugins.insight.widget.api.WidgetModule;
import io.riada.jira.plugins.insight.widget.api.capability.GeneratingDataByIQLCapability;
import org.jetbrains.annotations.NotNull;
 
import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Named;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
 
@Named
public class PayrollReport implements WidgetModule<PayrollReportParameters>,
        GeneratingDataByIQLCapability<PayrollReportParameters, PayrollReportData> {
 
    private final ObjectSchemaFacade objectSchemaFacade;
    private final ObjectTypeFacade objectTypeFacade;
    private final ObjectTypeAttributeFacade objectTypeAttributeFacade;
    private final ApplicationProperties applicationProperties;
 
    @Inject
    public PayrollReport(@ComponentImport final ObjectSchemaFacade objectSchemaFacade,
                         @ComponentImport final ObjectTypeFacade objectTypeFacade,
                         @ComponentImport final ObjectTypeAttributeFacade objectTypeAttributeFacade,
                         @ComponentImport final ApplicationProperties applicationProperties) {
        this.objectSchemaFacade = objectSchemaFacade;
        this.objectTypeFacade = objectTypeFacade;
        this.objectTypeAttributeFacade = objectTypeAttributeFacade;
        this.applicationProperties = applicationProperties;
    }
 
    @Override
    public void validate(@NotNull PayrollReportParameters parameters) throws Exception {
        Set<ServiceError> validationErrors = PayrollReportValidator.validate(parameters, objectSchemaFacade,
                objectTypeAttributeFacade);
 
        if (!validationErrors.isEmpty()) {
            throw new ServiceException(validationErrors, Reason.VALIDATION_FAILED);
        }
    }
 
    @NotNull
    @Override
    public String buildIQL(@Nonnull PayrollReportParameters parameters) throws Exception {
        final IQLBuilder iqlBuilder = new IQLBuilder(objectTypeAttributeFacade);
 
        final Integer objectTypeId = parameters.getObjectType().getValue();
        final ObjectTypeBean objectTypeBean = objectTypeFacade.loadObjectTypeBean(objectTypeId);
 
        iqlBuilder.addObjectType(objectTypeBean)
                .addDateAttributes(parameters.getDateAttributes(), parameters)
                .addNumericAttributes(parameters.getNumericAttributes())
                .addCustomIQL(parameters.getIql());
 
        return iqlBuilder.build();
    }
 
    @NotNull
    @Override
    public PayrollReportData generate(@NotNull PayrollReportParameters parameters, List<ObjectBean> objects,
            @NotNull ProgressId progressId) {
 
        if (objects.isEmpty()) {
            return PayrollReportData.empty();
        }
 
        final boolean doesWeekBeginOnMonday = applicationProperties.getOption(APKeys.JIRA_DATE_TIME_PICKER_USE_ISO8601);
 
        final LocalDate startDate = parameters.determineStartDate(doesWeekBeginOnMonday);
        final LocalDate endDate = parameters.determineEndDate(doesWeekBeginOnMonday);
 
        final Map<Integer, String> numericAttributeNames = createAttributeIdToNameMap(parameters.getNumericAttributes());
        final LinkedHashMap<Integer, ObjectAttributeBean> dateAttributesMap = createEmptyObjectTypeAttributeIdMap(parameters.getDateAttributes());
 
        final ReportDataBuilder reportDataBuilder =
                new ReportDataBuilder(startDate, endDate, doesWeekBeginOnMonday, numericAttributeNames,
                        dateAttributesMap);
 
        return reportDataBuilder.fillData(objects)
                .build();
    }
 
    private Map<Integer, String> createAttributeIdToNameMap(List<Value> attributes) {
        return attributes.stream()
                .map(Value::getValue)
                .map(id -> uncheckCall(() -> objectTypeAttributeFacade.loadObjectTypeAttributeBean(id)))
                .collect(Collectors.toMap(ObjectTypeAttributeBean::getId, ObjectTypeAttributeBean::getName));
    }
 
    private LinkedHashMap<Integer, ObjectAttributeBean> createEmptyObjectTypeAttributeIdMap(List<Value> attributes) {
        final LinkedHashMap emptyValuedMap = Maps.newLinkedHashMap();
 
        attributes.stream()
                .map(Value::getValue)
                .forEach(id -> emptyValuedMap.put(id, null));
 
        return emptyValuedMap;
    }
 
    private <T> T uncheckCall(Callable<T> callable) {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}


Insight とのインターフェイスに使用される (現時点で) 公開されているコンポーネントは、次のとおりです。

  • ObjectSchemaFacade
  • ObjectTypeFacade
  • ObjectTypeAttributeFacade
  • ObjectFacade
  • ConfigureFacade
  • IqlFacade
  • ObjectAttributeBeanFactory
  • ImportSourceConfigurationFacade
  • InsightPermissionFacade
  • InsightGroovyFacade
  • ProgressFacade

ウィジェット フレームワークのカスタマイズ

上記の例を見てみると、ウィジェット フレームワーク内でカスタマイズできる場所が 3 箇所あることがわかります。

  • 検証
  • クエリの構築
  • レポートの生成

これらを詳しく見ていきましょう。

パラメーターの検証

ウィジェット パラメーターが正しく作成されていることを検証することが重要です。

public void validate(@NotNull WidgetParameters) throws Exception

 上の例では、オブジェクト属性タイプが実際のオブジェクト属性に対応していることを確認していることがわかります。たとえば、数値型として想定されていた従業員の給与属性がテキスト形式になった場合は、例外がスローされます。

IQL クエリの構築

また、ウィジェット パラメーターからクエリを構築する必要があります。このクエリはオブジェクトのフェッチに使用されます。上記の例では、次のようになります。

public String buildIQL(@NotNull WidgetParameters parameters) throws Exception

レポートの生成

最後に、返されたオブジェクトからウィジェット データを生成できます。上記の例では、次のようになります。

public WidgetData generate(@NotNull WidgetParameters parameters, List<ObjectBean> objects,
            @NotNull ProgressId progressId)

ProgressId は、現在のレポート ジョブの進捗に対応します。ProgressFacade を使用して、関連する情報を抽出します。

ウィジェット モジュールを記述子に追加する

Now that we've implemented classes for the WidgetParameters, WidgetData, and WidgetModule, we need to modify the descriptor to register your custom report widget as a plugin within Assets.

完全に機能するレポートを作成するには、ModuleType を変更してマッパーとビューの各関数を指定し、レンダラーとエクスポーターを定義する必要があります。 

すべてのラベル名は一意である必要があることにご注意ください。


ModuleType を指定する

WidgetModule を指定します。

<insight-widget class="com.riadalabs.jira.plugins.insight.reports.payroll.PayrollReport"

レンダラー

レポートの表示方法を指定します。グラフィック表示は iFrame 内でレンダリングされます。

<renderers>
    <renderer mapper="BarChart.Mapper"
              view="BarChart.View"
              label="Bar Chart"/>
</renderers>

棒グラフ自体には、バックエンドによって生成されたデータを変換して表示する JS コンポーネントが含まれています。以下の例をご確認ください。

マッパーの例...
var BarChart = {};
 
BarChart.Mapper = function (data, parameters, baseUrl) {
 
    var mapper = new PayrollMapper(data, parameters);
 
    var expenditureIn = {
        label: "IN",
        data: [],
        backgroundColor: 'rgba(255,57,57,0.5)',
        borderColor: 'rgb(255,57,57)',
        borderWidth: 1
    };
 
    var expenditureOut = {
        label: "OUT",
        data: [],
        backgroundColor: 'rgba(45,218,181,0.5)',
        borderColor: 'rgb(45,218,181)',
        borderWidth: 1
    };
 
    var expenditureTotal = {
        label: "TOTAL",
        data: [],
        backgroundColor: 'rgba(111,158,255,0.5)',
        borderColor: 'rgb(111,158,255)',
        borderWidth: 1
    };
 
    return mapper.asTimeSeries(expenditureIn, expenditureOut, expenditureTotal);
 
};
 
BarChart.View = function (mappedData) {
 
    var containingElement = document.querySelector('.js-riada-widget');
    if (!containingElement) return;
 
    var canvas = new Canvas("myChart");
 
    var canvasElement = canvas.appendTo(containingElement);
 
    var myChart = new Chart(canvasElement, {
        type: 'bar',
        data: mappedData,
        options: {
            scales: {
                xAxes: [{
                    ticks: {
                        beginAtZero: true
                    },
                    stacked: true
                }]
            },
            animation: {
                duration: 0
            },
            responsive: true,
            maintainAspectRatio: false
        }
    });
 
    return containingElement;
};
 
var AreaChart = {};
 
AreaChart.Mapper = function (data, parameters, baseUrl) {
 
    var mapper = new PayrollMapper(data, parameters);
 
    var expenditureIn = {
        label: "IN",
        data: [],
        backgroundColor: 'rgba(255,57,57,0.5)',
        steppedLine: true,
        pointRadius: 2,
    };
 
    var expenditureOut = {
        label: "OUT",
        data: [],
        backgroundColor: 'rgba(45,218,181,0.5)',
        steppedLine: true,
        pointRadius: 2
    };
 
    var expenditureTotal = {
        label: "TOTAL",
        data: [],
        backgroundColor: 'rgba(111,158,255, 0.5)',
        steppedLine: true,
        pointRadius: 2
    };
 
    return mapper.asTimeSeries(expenditureIn, expenditureOut, expenditureTotal);
 
};
 
AreaChart.View = function (mappedData) {
 
    var containingElement = document.querySelector('.js-riada-widget');
    if (!containingElement) return;
 
    var canvas = new Canvas("myChart");
 
    var canvasElement = canvas.appendTo(containingElement);
 
    var myChart = new Chart(canvasElement, {
        type: 'line',
        data: mappedData,
        options: {
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: true
                    }
                }]
            },
            elements: {
                line: {
                    tension: 0
                }
            },
            animation: {
                duration: 0
            },
            responsive: true,
            maintainAspectRatio: false
        }
    });
 
    return containingElement;
};
 
var Canvas = function (id) {
    this.id = id;
 
    this.appendTo = function (containingElement) {
 
        clearOldIfExists(containingElement);
 
        var canvasElement = document.createElement("canvas");
        canvasElement.id = this.id;
 
        containingElement.appendChild(canvasElement);
 
        return canvasElement;
    };
 
    function clearOldIfExists(containingElement) {
        var oldCanvas = containingElement.querySelector('#myChart');
        if (oldCanvas) oldCanvas.remove();
    }
};
 
var PayrollMapper = function (data, parameters) {
    this.data = data;
    this.parameters = parameters;
 
    var EXPENDITURE_IN = "IN";
    var EXPENDITURE_OUT = "OUT";
    var EXPENDITURE_TOTAL = "TOTAL";
 
    this.asTimeSeries = function (dataIn, dataOut, dataTotal) {
        var mappedData = {};
 
        if (!this.data.metadata.hasData || this.parameters.numAttribute == null) {
            return mappedData;
        }
 
        mappedData.labels = Object.keys(data.expendituresByDay);
        mappedData.datasets = [];
 
        var attributeMap = createAttributeMap(this.parameters, dataIn, dataOut, dataTotal);
 
        Object.entries(data.expendituresByDay).forEach(function (entry, index) {
 
            var expenditures = entry[1];
 
            if (expenditures === undefined || expenditures.length === 0) {
 
                Object.entries(attributeMap).forEach(function (entry) {
                    var expenditure = entry[1];
 
                    fillData(expenditure, EXPENDITURE_IN, 0.0);
                    fillData(expenditure, EXPENDITURE_OUT, 0.0);
 
                    var previousTotal = index === 0 ? 0.0 : expenditure[EXPENDITURE_TOTAL].data[index - 1];
                    fillData(expenditure, EXPENDITURE_TOTAL, previousTotal);
                });
 
            }
 
            expenditures.forEach(function (expenditure) {
                if (attributeMap.hasOwnProperty(expenditure.name)) {
                    fillData(attributeMap[expenditure.name], EXPENDITURE_IN, expenditure.typeValueMap[EXPENDITURE_IN]);
                    fillData(attributeMap[expenditure.name], EXPENDITURE_OUT, 0.0 - expenditure.typeValueMap[EXPENDITURE_OUT]);
 
                    var currentTotal = expenditure.typeValueMap[EXPENDITURE_IN] - expenditure.typeValueMap[EXPENDITURE_OUT];
                    var previousTotal = index === 0 ? 0.0 : attributeMap[expenditure.name][EXPENDITURE_TOTAL].data[index - 1];
                    fillData(attributeMap[expenditure.name], EXPENDITURE_TOTAL, currentTotal + previousTotal)
                }
            });
 
        });
 
        mappedData.datasets = flatMap(attributeMap);
 
        return mappedData;
 
    };
 
    var createAttributeMap = function (parameters, dataIn, dataOut, dataTotal) {
        var map = {};
 
        map[parameters.numAttribute.label] = {};
 
        dataIn.label = parameters.numAttribute.label + "-" + dataIn.label;
        dataOut.label = parameters.numAttribute.label + "-" + dataOut.label;
        dataTotal.label = parameters.numAttribute.label + "-" + dataTotal.label;
 
        map[parameters.numAttribute.label][EXPENDITURE_IN] = dataIn;
        map[parameters.numAttribute.label][EXPENDITURE_OUT] = dataOut;
        map[parameters.numAttribute.label][EXPENDITURE_TOTAL] = dataTotal;
 
        return map;
    };
 
    function fillData(expenditure, dataType, value) {
        expenditure[dataType].data.push(value);
    }
 
    function flatMap(attributeMap) {
        var flattened = [];
        Object.values(attributeMap).forEach(function (valuesByAttribute) {
            Object.values(valuesByAttribute).forEach(function (value) {
                flattened.push(value);
            });
        });
 
        return flattened;
    }
 
};
 
var Transformer = {};
 
Transformer.JSON = function (mappedData) {
    if(!mappedData) return null;
 
    mappedData.datasets.forEach(function(dataset){
       ignoringKeys(['_meta'], dataset);
    });
 
    return JSON.stringify(mappedData);
};
 
function ignoringKeys(keys, data){
    keys.forEach(function(key){
        delete data[key];
    })
}


マッパーとビューの各関数

マッパーとビューの各関数は、以下のシグネチャに従う必要があります。

Mapper = function (data, parameters, baseUrl) { .. }

  • data: ウィジェット データ
  • parameters: ウィジェット パラメーター
  • baseUrl: ...
  • return: 変換されたデータ

View = function (mappedData, params, containerElementSelector){ ... }

  • mappedData: マッパーの出力
  • containerElementSelector: jsRiadaWidget と等しくなる
  • return: 無効

ビュー関数で DOM に何でも追加して、親要素が次のとおりであることを確認します。

<div id="riada" class="js-riada-widget">

ダウンロードするリソースをプラグイン記述子のタグに配置します。

<web-resource key="insight-report-payroll" i18n-name-key="Payroll Report Resource">
    <resource type="download" name="insight-report-payroll.css"
              location="/css/insight-report-payroll.css"/>
    <resource type="download" name="insight-report-payroll.js"
              location="/js/insight-report-payroll.js"/>
    <context>insight-report-payroll</context>
</web-resource>

エクスポーターを定義する

次の構造を使用して、データのエクスポーターを定義します。

<exporters>
   <exporter transformer="Transformer.JSON"
             extension="json"
             label="insight.example.report.exporter.json"/>
</exporters>

エクスポートされるデータは WidgetData ではなく、マッパーの出力になります。

Transformer.JSON = function (mappedData) { ... }

  • mappedData: マッパーの出力
  • return: 拡張型に変換されたデータ

エクスポーターは作成されたレポートには表示されますが、プレビューには表示されません。


パラメーター

レポート パラメーター フォームに表示されるオプションは次のとおりです。キーはウィジェット パラメーターのフィールド名に対応します。

<parameter key="numAttribute"
           type="objecttypeattributepicker"
           label="insight.example.report.attribute.numeric"
           required="true">
    <configuration>
        <dependency key="objectType"/>
        <filters>
            <value>INTEGER</value>
            <value>DOUBLE</value>
        </filters>
    </configuration>
</parameter>

現在のパラメーター タイプ オプションは次のとおりです。

  • チェックボックス
  • 日付ピッカー
  • 日時ピッカー
  • IQL
  • jql
  • Number (番号)
  • objectpicker
  • objectschemapicker
  • objectsearchfilterpicker
  • objecttypeattributepicker
  • objecttypepicker
  • プロジェクト ピッカー
  • ラジオ ボタン
  • schemapicker
  • [切り替え] を選択すると、
  • simpleobjecttypepicker
  • スイッチ
  • text
  • timepicker
  • ユーザー ピッカー

依存関係は、どのタイプを返せるかについて、他のパラメーターやフィルターと関連性を持っています。

さらなる開発の可能性...

Insight Widget Framework を使用して Jira プラグインを構築することで、Insight でほぼすべての種類のカスタム レポートを作成できます。

上の例からわかるように、入力 (ウィジェット パラメーター)、出力 (ウィジェット データ)、レポート エンジン (ウィジェット モジュール) の各関数を提供するフレームワークによって、ほぼすべてのニーズに適合するレポート/エクスポート機能を提供できます。 

最終更新日 2022 年 9 月 26 日

この内容はお役に立ちましたか?

はい
いいえ
この記事についてのフィードバックを送信する
Powered by Confluence and Scroll Viewport.