Kintone plugin 実装

This commit is contained in:
2023-10-24 09:08:45 +09:00
parent 25f05ab018
commit f6d677b51f
27 changed files with 1477 additions and 20251 deletions

View File

@@ -0,0 +1,2 @@
import { IAction } from "../types/ActionTypes";
export const actionAddins :Record<string,IAction>={};

View File

@@ -0,0 +1,53 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty } from "../types/ActionTypes";
interface IMustInputProps{
field:string;
message:string;
}
export class MustInputAction implements IAction{
name: string;
actionProps: IActionProperty[];
props:IMustInputProps;
constructor(){
this.name="必須チェック";
this.actionProps=[];
this.register();
this.props={
field:'',
message:''
}
this.register();
}
process(actionNode:IActionNode,event:any):IActionResult {
let result={
canNext:true,
result:false
};
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IMustInputProps;
const record = event.record;
const value = record[this.props.field]?.value;
if(value===undefined || value===''){
record[this.props.field].error=this.props.message;
return result;
}
result= {
canNext:true,
result:true
}
return result;
}
register(): void {
actionAddins[this.name]=this;
}
}
new MustInputAction();

View File

@@ -0,0 +1,27 @@
// export const sum = (a: number, b: number) => {
// if ('development' === process.env.NODE_ENV) {
// console.log('boop');
// }
// return a + b;
// };
import $ from 'jquery';
import { ActionProcess } from './types/action-process';
import { ActionFlow } from './types/ActionTypes';
import { FlowInfo } from './types/flowSetting';
declare const alcflow : {
[key:string]:FlowInfo
};
$(function (){
const events=Object.keys(alcflow);
kintone.events.on(events,(event:any)=>{
const flowinfo = alcflow[event.type];
const flow=ActionFlow.fromJSON(flowinfo.content);
if(flow!==undefined){
const process = new ActionProcess(event.type,flow,event);
process.exec();
}
return event;
});
});

402
plugin/kintone-addins/src/kintone.d.ts vendored Normal file
View File

@@ -0,0 +1,402 @@
/**
* Kintone APIタイプ定義
*/
declare namespace kintone {
namespace events {
function on(event: string | string[], handler: (event: any) => any): void;
function off(
event: string | string[],
handler: (event: any) => any
): boolean;
function off(event: string | string[]): boolean;
function off(): boolean;
}
namespace api {
function url(path: string, detectGuestSpace?: boolean): string;
function urlForGet(
path: string,
params: any,
detectGuestSpace?: boolean
): string;
function getConcurrencyLimit(): Promise<{
limit: number;
running: number;
}>;
}
function api(pathOrUrl: string, method: string, params: any): Promise<any>;
function api(
pathOrUrl: string,
method: string,
params: any,
callback: (resp: any) => void,
errback: (err: any) => void
): void;
function getRequestToken(): string;
function proxy(
url: string,
method: string,
headers: any,
data: any
): Promise<any>;
function proxy(
url: string,
method: string,
headers: any,
data: any,
callback: (resp: any) => void,
errback: (err: any) => void
): void;
class Promise<T> {
constructor(
callback: (
resolve: (resolved: T) => any,
reject: (rejected: any) => any
) => void
);
then(callback: (resolved: T) => any): Promise<any>;
catch(callback: (rejected: any) => any): Promise<any>;
static resolve(resolved: any): Promise<any>;
static reject(rejected: any): Promise<any>;
static all(listOfPromise: Array<Promise<any>>): Promise<any>;
}
namespace proxy {
function upload(
url: string,
method: string,
headers: any,
data: any,
callback: (resp: any) => void,
errback: (err: any) => void
): void;
function upload(
url: string,
method: string,
headers: any,
data: any
): Promise<any>;
}
namespace app {
function getFieldElements(fieldCode: string): HTMLElement[] | null;
function getHeaderMenuSpaceElement(): HTMLElement | null;
function getHeaderSpaceElement(): HTMLElement | null;
function getId(): number | null;
function getLookupTargetAppId(fieldCode: string): string | null;
function getQuery(): string | null;
function getQueryCondition(): string | null;
function getRelatedRecordsTargetAppId(fieldCode: string): string | null;
namespace record {
function getId(): number | null;
function get(): any | null;
function getHeaderMenuSpaceElement(): HTMLElement | null;
function getFieldElement(fieldCode: string): HTMLElement | null;
function set(record: any): void;
function getSpaceElement(id: string): HTMLElement | null;
function setFieldShown(fieldCode: string, isShown: boolean): void;
function setGroupFieldOpen(fieldCode: string, isOpen: boolean): void;
}
}
namespace mobile {
namespace app {
function getFieldElements(fieldCode: string): HTMLElement[] | null;
function getHeaderSpaceElement(): HTMLElement | null;
function getId(): number | null;
function getLookupTargetAppId(fieldCode: string): string | null;
function getQuery(): string | null;
function getQueryCondition(): string | null;
function getRelatedRecordsTargetAppId(fieldCode: string): string | null;
namespace record {
function getId(): number | null;
function get(): any | null;
function getFieldElement(fieldCode: string): HTMLElement | null;
function set(record: any): void;
function getSpaceElement(id: string): HTMLElement | null;
function setFieldShown(fieldCode: string, isShown: boolean): void;
function setGroupFieldOpen(fieldCode: string, isOpen: boolean): void;
}
}
namespace portal {
function getContentSpaceElement(): HTMLElement | null;
}
namespace space {
namespace portal {
function getContentSpaceElement(): HTMLElement | null;
}
}
}
namespace plugin {
namespace app {
function getConfig(pluginId: string): any;
function setConfig(config: any, callback?: () => void): void;
function proxy(
pluginId: string,
url: string,
method: string,
headers: any,
data: any
): Promise<any>;
function proxy(
pluginId: string,
url: string,
method: string,
headers: any,
data: any,
callback: (resp: any) => void,
error: (err: any) => void
): void;
function setProxyConfig(
url: string,
method: string,
headers: any,
data: any,
callback?: () => void
): void;
function getProxyConfig(url: string, method: string): any;
namespace proxy {
function upload(
pluginId: any,
url: string,
method: string,
headers: any,
data: any
): Promise<any>;
function upload(
pluginId: any,
url: string,
method: string,
headers: any,
data: any,
callback: (resp: any) => void,
error: (err: any) => void
): void;
}
}
}
namespace portal {
function getContentSpaceElement(): HTMLElement | null;
}
namespace space {
namespace portal {
function getContentSpaceElement(): HTMLElement | null;
}
}
interface LoginUser {
id: string;
code: string;
name: string;
email: string;
url: string;
employeeNumber: string;
phone: string;
mobilePhone: string;
extensionNumber: string;
timezone: string;
isGuest: boolean;
language: string;
}
function getLoginUser(): LoginUser;
function getUiVersion(): 1 | 2;
const $PLUGIN_ID: string;
namespace fieldTypes {
interface SingleLineText {
type?: "SINGLE_LINE_TEXT";
value: string;
disabled?: boolean;
error?: string;
}
interface RichText {
type?: "RICH_TEXT";
value: string;
disabled?: boolean;
error?: string;
}
interface MultiLineText {
type?: "MULTI_LINE_TEXT";
value: string;
disabled?: boolean;
error?: string;
}
interface Number {
type?: "NUMBER";
value: string;
disabled?: boolean;
error?: string;
}
interface Calc {
type: "CALC";
value: string;
disabled?: boolean;
}
interface RadioButton {
type?: "RADIO_BUTTON";
value: string;
disabled?: boolean;
error?: string;
}
interface DropDown {
type?: "DROP_DOWN";
value: string;
disabled?: boolean;
error?: string;
}
interface Date {
type?: "DATE";
value: string;
disabled?: boolean;
error?: string;
}
interface Time {
type?: "TIME";
value: string;
disabled?: boolean;
error?: string;
}
interface DateTime {
type?: "DATETIME";
value: string;
disabled?: boolean;
error?: string;
}
interface Link {
type?: "LINK";
value: string;
disabled?: boolean;
error?: string;
}
interface CheckBox {
type?: "CHECK_BOX";
value: string[];
disabled?: boolean;
error?: string;
}
interface MultiSelect {
type?: "MULTI_SELECT";
value: string[];
disabled?: boolean;
error?: string;
}
interface UserSelect {
type?: "USER_SELECT";
value: Array<{ code: string; name: string }>;
disabled?: boolean;
error?: string;
}
interface OrganizationSelect {
type?: "ORGANIZATION_SELECT";
value: Array<{ code: string; name: string }>;
disabled?: boolean;
error?: string;
}
interface GroupSelect {
type?: "GROUP_SELECT";
value: Array<{ code: string; name: string }>;
disabled?: boolean;
error?: string;
}
interface File {
type: "FILE";
value: Array<{
contentType: string;
fileKey: string;
name: string;
size: string;
}>;
disabled?: boolean;
error?: string;
}
interface Id {
type: "__ID__";
value: string;
}
interface Revision {
type: "__REVISION__";
value: string;
}
/**
* field type of UserField is MODIFIER.
* So error property not exists.
*/
interface Modifier {
type: "MODIFIER";
value: { code: string; name: string };
}
/**
* field type of UserField is CREATOR.
* So error property not exists.
*/
interface Creator {
type: "CREATOR";
value: { code: string; name: string };
}
interface RecordNumber {
type: "RECORD_NUMBER";
value: string;
error?: string;
}
interface UpdatedTime {
type: "UPDATED_TIME";
value: string;
error?: string;
}
interface CreatedTime {
type: "CREATED_TIME";
value: string;
error?: string;
}
}
}

View File

@@ -0,0 +1,247 @@
/**
* アプリ情報
*/
export interface AppInfo {
appId: string;
code?: string;
name: string;
description?: string;
}
/**
* アクションのプロパティ定義
*/
export interface IActionProperty {
component: string;
props: {
//プロパティ名
name: string;
//プロパティ表示名
displayName: string;
placeholder: string;
//プロパティ設定値
modelValue: any;
};
}
/**
* アクションタイプ定義
*/
export interface IActionNode {
id: string;
//アクション名
name: string;
title: string;
subTitle: string;
inputPoint: string;
//出力ポイント(条件分岐以外未使用)
outputPoints: Array<string>;
//ルートアクションKintone event
isRoot: boolean;
//アクションのプロパティ定義
actionProps: Array<IActionProperty>;
//アクションのプロパティ設定値抽出
ActionValue: object
prevNodeId?: string;
nextNodeIds: Map<string, string>;
}
/**
* アクションフローの定義
*/
export interface IActionFlow {
id: string;
actionNodes: IActionNode[];
findNodeById(id: string): IActionNode | undefined;
toJSON(): any;
getRoot(): IActionNode | undefined;
}
export interface IActionResult{
canNext:boolean;
result?:any;
}
export interface IAction{
name:string;
actionProps: Array<IActionProperty>;
process(prop:IActionNode,event:any):IActionResult;
register():void;
}
/**
* アクションのプロパティ定義に基づいたクラス
*/
export class ActionProperty implements IActionProperty {
component: string;
props: {
// プロパティ名
name: string;
// プロパティ表示名
displayName: string;
placeholder: string;
// プロパティ設定値
modelValue: any;
};
static defaultProperty(): IActionProperty {
return new ActionProperty('InputText', 'displayName', '表示名', '表示を入力してください', '');
};
constructor(
component: string,
name: string,
displayName: string,
placeholder: string,
modelValue: any
) {
this.component = component;
this.props = {
name: name,
displayName: displayName,
placeholder: placeholder,
modelValue: modelValue
};
}
}
/**
* IActionNodeの実装、RootActionNode以外のアクション定義
*/
export class ActionNode implements IActionNode {
id: string;
name: string;
get title(): string {
const prop = this.actionProps.find((prop) => prop.props.name === "displayName");
return prop?.props.modelValue;
};
get subTitle(): string {
return this.name;
};
inputPoint: string;
//出力ポイント(条件分岐以外未使用)
outputPoints: Array<string>;
actionProps: Array<IActionProperty>;
get isRoot(): boolean {
return false;
};
get ActionValue(): object {
const propValue: any = {};
this.actionProps.forEach((value) => {
propValue[value.props.name] = value.props.modelValue
});
return propValue;
};
prevNodeId?: string;
nextNodeIds: Map<string, string>;
constructor(
{id,name,title,inputPoint,outputPoint,actionProps}:
{
id:string,
name: string,
title: string,
inputPoint: string,
outputPoint: Array<string>,
actionProps: Array<IActionProperty>}
) {
this.id=id;
this.name = name;
this.inputPoint = inputPoint;
this.outputPoints = outputPoint;
const defProp = ActionProperty.defaultProperty();
defProp.props.modelValue = title;
this.actionProps = actionProps;
this.nextNodeIds = new Map<string, string>();
}
}
/**
* ルートアクション定義
*/
export class RootAction implements IActionNode {
id: string;
name: string;
title: string;
subTitle: string;
inputPoint: string;
//出力ポイント(条件分岐以外未使用)
outputPoints: Array<string>;
isRoot: boolean;
actionProps: Array<IActionProperty>;
ActionValue: object;
prevNodeId?: string = undefined;
nextNodeIds: Map<string, string>;
constructor(
{id,name,title,subTitle}:{
id:string,
name: string,
title: string,
subTitle: string}
) {
this.id = id;
this.name = name;
this.title = title;
this.subTitle = subTitle;
this.inputPoint = '';
this.outputPoints = [];
this.isRoot = true;
this.actionProps = [];
this.ActionValue = {};
this.nextNodeIds = new Map<string, string>();
}
}
/**
* アクションフローの定義
*/
export class ActionFlow implements IActionFlow {
id: string;
actionNodes: Array<IActionNode>;
constructor(actionNodes: Array<IActionNode> | RootAction) {
if (actionNodes instanceof Array) {
this.actionNodes = actionNodes;
} else {
this.actionNodes = [actionNodes];
}
this.id = '';
}
/***
* IDでActionNodeを取得する
*/
findNodeById(id: string): IActionNode | undefined {
return this.actionNodes.find((node) => node.id === id);
}
toJSON() {
return {
id: this.id,
actionNodes: this.actionNodes.map(node => {
const { nextNodeIds, ...rest } = node;
return {
...rest,
nextNodeIds: Array.from(nextNodeIds.entries())
};
})
};
}
getRoot(): IActionNode | undefined {
return this.actionNodes.find(node => node.isRoot)
}
static fromJSON(json: string): ActionFlow {
const parsedObject = JSON.parse(json);
const actionNodes = parsedObject.actionNodes.map((node: any) => {
const nodeClass = !node.isRoot ? new ActionNode(node)
: new RootAction(node);
nodeClass.nextNodeIds = new Map(node.nextNodeIds);
nodeClass.prevNodeId = node.prevNodeId;
nodeClass.id = node.id;
return nodeClass;
});
const actionFlow = new ActionFlow(actionNodes);
actionFlow.id = parsedObject.id;
return actionFlow;
}
}

View File

@@ -0,0 +1,38 @@
import { actionAddins } from "../actions";
import '../actions/must-input';
import { ActionFlow,IActionFlow, IActionResult } from "./ActionTypes";
export class ActionProcess{
eventId:string;
flow:IActionFlow;
event:any;
constructor(eventId:string,flow:ActionFlow,event:any){
this.eventId=eventId;
this.flow=flow;
this.event=event;
}
exec(){
const root = this.flow.getRoot();
if(root===undefined || root.nextNodeIds.size===0){
return;
}
let id=root.nextNodeIds.get('');
if(id===undefined) return;
let nextAction = this.flow.findNodeById(id);
let result:IActionResult={
canNext:true
};
while(nextAction!==undefined && result.canNext){
const action = actionAddins[nextAction.name];
if(action!==undefined){
result = action.process(nextAction,this.event)
}
const nextInput = nextAction.outputPoints!==undefined?result.result||'':'';
id=nextAction.nextNodeIds.get(nextInput);
if(id===undefined) return;
nextAction = this.flow.findNodeById(id);
}
}
}

View File

@@ -0,0 +1,23 @@
import { ActionFlow } from "./ActionTypes";
export interface IFlowInfo {
flowid: string;
name:string;
content: string;
}
export class FlowInfo implements IFlowInfo{
flowid: string;
name:string;
content: string;
constructor({flowid,name,content}:{flowid: string,
name:string,
content: string,}) {
this.flowid=flowid;
this.name=name;
this.content=content;
}
getFlow():ActionFlow {
return ActionFlow.fromJSON(this.content)
}
}