Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

일정 차트의 인라인 지표 함수

인라인 지표(Inline metrics)는 차트 안에 값을 표시합니다. Header Metrics는 시간축에 맞춰 일정 전체의 집계를 보여 주고, Row Metrics는 자원 행별 값을 표시합니다.

반환 형식과 버전

함수는 range와 value를 가진 사용자 지정 구조체의 목록을 반환합니다. 인터페이스 이름은 임의로 정할 수 있지만 두 필드 이름은 맞아야 합니다.

항목TypeScript v1TypeScript v2
시간 범위IRange<Timestamp>Range<TimestampISOString>
날짜 범위IRange<LocalDate>Range<DateISOString>
정수 범위IRange<Integer>Range<Integer>
시작/끝 필드min, maxstartValue, endValue
값string, Integer 또는 Doublestring, Integer 또는 Double
함수 정의내보낸 클래스의 @Function() 메서드파일의 export default 함수

TSv1 타입은 @foundry/functions-api, TSv2 타입은 @osdk/functions에서 가져옵니다. 정수 범위의 값은 epoch milliseconds를 의미하지만 함수의 Integer는 32비트 범위로 제한됩니다. 현대 날짜의 epoch milliseconds는 이 범위를 넘으므로 시간이나 날짜 타입을 사용해야 합니다. 아래 예제는 Timestamp 계열을 사용합니다.

예제의 입력과 버킷 정의

헤더 예제는 가상의 SchedulingMaintenanceTask 유형에서 시작 시각이 주어진 범위에 속하는 작업 수를 셉니다. TSv1 검색 메서드는 schedulingMaintenanceTask(), TSv2 SDK 정의는 SchedulingMaintenanceTask이며 속성은 startTime입니다. TSv1의 startTime에는 Searchable 설정이 필요합니다.

행 예제는 TSv1의 SchedulingTechnician_1, TSv2의 SchedulingTechnician 객체 집합 하나를 입력으로 받고 fullName과 0부터 시작하는 버킷 번호를 표시합니다. 업무 성과를 계산하는 지표가 아니라 행 입력과 문자열 반환 형식을 보여 주는 예제입니다. 실행 입력은 정확히 한 자원 객체이며 fullName을 조회할 수 있어야 합니다.

객체 유형을 프로젝트의 Resource imports에 추가해야 합니다. TSv2는 필요한 객체와 링크를 가져온 뒤 OSDK를 생성하고 설치해야 합니다.

기간은 [startTime, endTime)이고 인접 버킷도 시작 포함, 끝 제외입니다. 마지막 버킷은 endTime에서 자릅니다. step은 24시간을 하루로 계산한 간격이며 소수 일도 사용할 수 있지만 양의 정수 밀리초로 표현되어야 합니다. 잘못된 시간 범위나 간격은 오류로 처리합니다.

공통 버킷 계산

TSv1 소스 디렉터리에는 bucketPlan.ts로, TSv2에는 typescript-functions/src/bucketPlan.ts로 다음 파일을 둡니다. 공개 함수로 등록할 파일이 아니라 계산을 공유하는 모듈입니다.

export interface BucketPlan {
    start: number;
    end: number;
    step: number;
    count: number;
}

export function makeBucketPlan(start: number, end: number, stepDays: number): BucketPlan {
    const step = stepDays * 86_400_000;
    if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start ||
        !Number.isSafeInteger(step) || step <= 0) {
        throw new Error("시작보다 늦은 종료와 양의 정수 밀리초 간격이 필요합니다.");
    }
    return { start, end, step, count: Math.ceil((end - start) / step) };
}

TypeScript v1의 헤더와 행 함수

같은 소스 디렉터리의 inlineMetrics.ts에 작성합니다. .allAsync()로 읽는 객체에는 TSv1의 로드 한도가 적용됩니다.

import { Double, Function, IRange, Timestamp } from "@foundry/functions-api";
import { Objects, ObjectSet, SchedulingTechnician_1 } from "@foundry/ontology-api";
import { makeBucketPlan } from "./bucketPlan";

interface InlineMetricBucketV1Double {
    range: IRange<Timestamp>;
    value: Double;
}

interface InlineMetricBucketV1String {
    range: IRange<Timestamp>;
    value: string;
}

export class InlineMetricFunctions {
    @Function()
    public async getInlineMetricsV1WithObjectCounts(
        startTime: Timestamp,
        endTime: Timestamp,
        step: Double
    ): Promise<InlineMetricBucketV1Double[]> {
        const plan = makeBucketPlan(startTime.getTime(), endTime.getTime(), step);
        const buckets: InlineMetricBucketV1Double[] = Array.from(
            { length: plan.count }, (_, index) => ({
                range: {
                    min: Timestamp.fromISOString(new Date(plan.start + index * plan.step).toISOString()),
                    max: Timestamp.fromISOString(new Date(Math.min(
                        plan.start + (index + 1) * plan.step, plan.end
                    )).toISOString())
                },
                value: 0
            })
        );
        const tasks = await Objects.search().schedulingMaintenanceTask()
            .filter(task => task.startTime.range().gte(startTime).lt(endTime))
            .allAsync();
        for (const task of tasks) {
            if (task.startTime === undefined) {
                throw new Error("조회한 작업의 시작 시각을 읽을 수 없습니다.");
            }
            const index = Math.floor((task.startTime.getTime() - plan.start) / plan.step);
            buckets[index]!.value += 1;
        }
        return buckets;
    }

    @Function()
    public async getInlineMetricsV1StringWithObject(
        techs: ObjectSet<SchedulingTechnician_1>,
        startTime: Timestamp,
        endTime: Timestamp,
        step: Double
    ): Promise<InlineMetricBucketV1String[]> {
        const plan = makeBucketPlan(startTime.getTime(), endTime.getTime(), step);
        const technicians = await techs.allAsync();
        if (technicians.length !== 1) {
            throw new Error("행 지표에는 자원 객체 하나가 필요합니다.");
        }
        const name = technicians[0]?.fullName;
        if (name === undefined) {
            throw new Error("자원의 fullName을 읽을 수 없습니다.");
        }
        return Array.from({ length: plan.count }, (_, index) => ({
            range: {
                min: Timestamp.fromISOString(new Date(plan.start + index * plan.step).toISOString()),
                max: Timestamp.fromISOString(new Date(Math.min(
                    plan.start + (index + 1) * plan.step, plan.end
                )).toISOString())
            },
            value: `${name}-${index}`
        }));
    }
}

index.ts에서 클래스를 내보냅니다.

export { InlineMetricFunctions } from "./inlineMetrics";

TypeScript v2 헤더 함수

함수는 typescript-functions/src/functions 아래에 두고 파일 이름을 함수 이름과 맞춰야 합니다. 다음 파일은 getInlineMetricsWithObjectCounts.ts입니다. 클라이언트와 생성된 SDK 정의를 사용하여 startTime만 선택하고 비동기 순회하므로 모든 객체를 별도 배열에 모으지 않습니다.

TSv2의 속성 조건에는 연산자 키를 하나만 지정할 수 있습니다. 시작 포함과 끝 제외 조건을 각각 작성하고 $and로 결합합니다.

import { Client } from "@osdk/client";
import { Double, Range, TimestampISOString } from "@osdk/functions";
import { SchedulingMaintenanceTask } from "@ontology/sdk";
import { makeBucketPlan } from "../bucketPlan";

interface InlineMetricBucketDouble {
    range: Range<TimestampISOString>;
    value: Double;
}

export default async function getInlineMetricsWithObjectCounts(
    client: Client,
    startTime: TimestampISOString,
    endTime: TimestampISOString,
    step: Double
): Promise<InlineMetricBucketDouble[]> {
    const plan = makeBucketPlan(Date.parse(startTime), Date.parse(endTime), step);
    const buckets: InlineMetricBucketDouble[] = Array.from(
        { length: plan.count }, (_, index) => ({
            range: {
                startValue: new Date(plan.start + index * plan.step).toISOString(),
                endValue: new Date(Math.min(
                    plan.start + (index + 1) * plan.step, plan.end
                )).toISOString()
            },
            value: 0
        })
    );
    const tasks = client(SchedulingMaintenanceTask).where({
        $and: [
            { startTime: { $gte: new Date(plan.start).toISOString() } },
            { startTime: { $lt: new Date(plan.end).toISOString() } }
        ]
    });
    for await (const task of tasks.asyncIter({ $select: ["startTime"] })) {
        if (task.startTime == null) {
            throw new Error("조회한 작업의 시작 시각을 읽을 수 없습니다.");
        }
        const index = Math.floor((Date.parse(task.startTime) - plan.start) / plan.step);
        buckets[index]!.value += 1;
    }
    return buckets;
}

TypeScript v2 행 함수

같은 functions 디렉터리의 getInlineMetricsStringWithObject.ts입니다. 입력 객체 집합을 비동기로 순회하여 행 객체가 정확히 하나인지 확인합니다.

import { ObjectSet } from "@osdk/client";
import { Double, Range, TimestampISOString } from "@osdk/functions";
import { SchedulingTechnician } from "@ontology/sdk";
import { makeBucketPlan } from "../bucketPlan";

interface InlineMetricBucketString {
    range: Range<TimestampISOString>;
    value: string;
}

export default async function getInlineMetricsStringWithObject(
    techs: ObjectSet<SchedulingTechnician>,
    startTime: TimestampISOString,
    endTime: TimestampISOString,
    step: Double
): Promise<InlineMetricBucketString[]> {
    const plan = makeBucketPlan(Date.parse(startTime), Date.parse(endTime), step);
    let name: string | undefined;
    for await (const tech of techs.asyncIter({ $select: ["fullName"] })) {
        if (name !== undefined) {
            throw new Error("행 지표에는 자원 객체 하나가 필요합니다.");
        }
        if (tech.fullName == null) {
            throw new Error("자원의 fullName을 읽을 수 없습니다.");
        }
        name = tech.fullName;
    }
    if (name === undefined) {
        throw new Error("행 지표의 자원 객체가 없습니다.");
    }
    return Array.from({ length: plan.count }, (_, index) => ({
        range: {
            startValue: new Date(plan.start + index * plan.step).toISOString(),
            endValue: new Date(Math.min(
                plan.start + (index + 1) * plan.step, plan.end
            )).toISOString()
        },
        value: `${name}-${index}`
    }));
}

위젯에 연결

헤더 지표는 전체 시간축의 함수이므로 특정 객체 입력이 반드시 필요한 것은 아닙니다. 행 지표에서는 자원 객체 입력을 Runtime input으로 지정하면 위젯이 해당 행을 전달합니다.

위젯의 Metrics에서 Header Metrics 또는 Row Metrics에 함수를 선택합니다.

지표마다 제목, 아이콘과 조건부 색을 지정할 수 있습니다.