Open kirin-ri opened 2 months ago
if collection == "metrics_node" and PROJECT != "startpack":
# 假设 jsonLoad 是一个包含字典的列表
for item in jsonLoad:
for key, value in item.items():
if value == "INDUSTRIAL":
item[key] = "a"
{
"_key": "PARTSSTOCK_OUTOFSTOCK_MONTHLY",
"metrics_id": "PARTSSTOCK_OUTOFSTOCK_MONTHLY",
"disp_name": "部品在庫欠品:月次",
"provide": false,
"in_info": [
{
"db": "INDUSTRIAL_DNA_DB",
"schema": "DATASET_SCHEMA",
"table": "priprt_inventory",
"column": [
{
"logicalName": "工場_生産拠点",
"physicalName": "SUPPLIER_NAME",
"type": "文字列"
},
{
"logicalName": "一次部品_部品番号",
"physicalName": "PARTS_NO",
"type": "文字列"
},
{
"logicalName": "在庫確認日",
"physicalName": "STOCK_DATE",
"type": "日付"
},
{
"logicalName": "在庫数",
"physicalName": "STOCK_QTY",
"type": "数値"
}
]
},
{
"db": "INDUSTRIAL_DNA_DB",
"schema": "DATASET_SCHEMA",
"table": "components_master",
"column": [
{
"logicalName": "構成品目_子品目",
"physicalName": "COMPONENT",
"type": "文字列"
},
{
"logicalName": "品目コード_親品目",
"physicalName": "ITEM_CODE",
"type": "文字列"
},
{
"logicalName": "員数",
"physicalName": "PARTS_NUMBER",
"type": "数値"
}
]
},
{
"db": "INDUSTRIAL_DNA_DB",
"schema": "DATASET_SCHEMA",
"table": "fg_prd_m_plan",
"column": [
{
"logicalName": "工場_生産拠点",
"physicalName": "FACTORY_NAME",
"type": "文字列"
},
{
"logicalName": "完成品_生産機種型番",
"physicalName": "FINAL_PRODUCT",
"type": "文字列"
},
{
"logicalName": "生産日",
"physicalName": "PRODUCTION_DATE",
"type": "日付"
},
{
"logicalName": "生産数量",
"physicalName": "PRODUCTION_QTY",
"type": "数値"
}
]
},
if collection == "metrics_node" and PROJECT != "startpack":
# 遍历 JSON 数据并替换 'INDUSTRIAL' 为 'a'
def replace_industrial(data):
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, str) and 'INDUSTRIAL' in value:
data[key] = value.replace('INDUSTRIAL', 'a')
else:
replace_industrial(value)
elif isinstance(data, list):
for index, item in enumerate(data):
if isinstance(item, str) and 'INDUSTRIAL' in item:
data[index] = item.replace('INDUSTRIAL', 'a')
else:
replace_industrial(item)
# 调用递归函数替换所有 'INDUSTRIAL' 字符串
replace_industrial(jsonLoad)
コンフルエンスを説明いたします。 改修箇所の整理部分について、前回の中間レビュー後の変更点は赤字です。 データセット関連のコンフルを修正しようと思ってましたが、参照DBの情報が記載されていないため、更新はしていません。
サンプルデータ挿入の処理で接続先情報を更新しました。 また、中間レビュー時に挿入データの削除機能が対応していないため、追記しました。。 削除時の対象をPUBLICからDATASET_SCHEMAに変更しました。
メトリクス関連について、IN、OUT以外に中間レビュー時、計算データの削除の対応が漏れていたので、追記しました。 各メトリクスの入力出力参照DBを更新しました。 おなじく記載ないところは更新していません。 同じ修正を実施したので、各コンフルエンスの展開は割愛させていただきます。
データ分析管理画面は判定条件を更新しました。コンフルに処理詳細がDB記載がないため、更新はないです。
個社コピー・削除はsnowflakeの作成SCHEMAの変更とDPB/DPL部分をコメントアウトしました。
それ以外、動作確認中に2点対応しています。 まず個社コピー後、初回CI実行時にscm-metricsとの差分を見て動いているため、CR90などコピー時に削除されたメトリクスファイルもCIの対象内になっているため、エラーになりました。 初回の判定条件で削除ファイルを無視するため、shellを書き換えています。 diff-fitlerを追加しています。 改修内容は前スプリントのCICDテンプレート化に含まれています。 動作検証も個社コピー実施して確認しました。
個社コピーでArangoコピー処理は個社ごとに対応していませんでした。 Arangoコピー処理で個社名を判別し、DB名書き換える処理を追加しました。 既存環境は手動でArango情報を修正し、個社コピー機能で動作確認をしました。 dbとschema名が個社ごとに対応していることを確認しました。
改修点としては以上ですが、なにか質問ありますか?
動作確認は個社ごとからCATALOGAPの各機能をテストしました。
エビデンスとしては以上です。なにか気になるところございますでしょうか?
受け入れ条件に記載していた、データ追加、サンプルデータ挿入とメトリクス即時実行を実施します。 使用する環境は個社コピーによって作成先されたchangedbです。
// fillter tag
function FilterTags({
tags,
setTypes,
}: {
tags: Tag[];
setTypes: React.Dispatch<React.SetStateAction<Tag[]>>;
}) {
const handleClick = (item: Tag) => {
// eslint-disable-next-line no-param-reassign
item.selected = !item.selected;
const t = tags.slice(0, tags.length);
setTypes(t);
};
return (
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
<div className="inline-form-label">ジョブ種別</div>
<div className="inline-form-group">
{tags.map((item: Tag) => (
<button
type="button"
className={`btn btn-tag${item.selected ? ' active' : ''}`}
key={item.name}
onClick={() => handleClick(item)}
>
{item.name}
</button>
))}
</div>
</div>
);
}
import parse from 'html-react-parser'; import React, { useEffect, useLayoutEffect, useState } from 'react'; import ReactPaginate from 'react-paginate'; import { useHistory } from 'react-router-dom'; import Swal from 'sweetalert2'; import withReactContent from 'sweetalert2-react-content'; import { commonAjax } from '../../../components/commonAjax'; import { currentJST } from '../util/dateTimeUtil'; import { DialogModalOK, showDialog } from './dialogModal';
// eslint-disable-next-line @typescript-eslint/no-var-requires const moment = require('moment'); require('moment-duration-format');
const MySwal = withReactContent(Swal);
interface Props { match: { params: { id?: string; }; }; }
type JobEntry = { id: string; type: string; name: string; execDate: string; endDate: string | null; status: Status; warning: boolean; };
type JobDetailsTypes = { id: string; type: string; name: string; currPhase: number; totalPhase: number; phaseDesc: string; execDate: string; endDate: string | null; status: Status; warning: boolean; errorInfo: { message: string; description: string; userAction: string; dumpMsg: string | null; dumpData: string | null; } | null; externalQuery: { apiTablePhy?: string; putApiDataTran?: string; metrics?: string; putMetricsTran?: string; } | null; };
type Tag = { name: string; selected: boolean; };
type Status = 'running' | 'finished' | 'error';
function ProgressIndicator({ info }: { info?: JobDetailsTypes }) { const progList: JSX.Element[] = []; if (info) { for (let i = 0; i <= info.totalPhase + 1; i += 1) { let type; let stat; const content = null; if (i === 0) { type = 'progress-start'; } else if (i <= info.totalPhase) { type = 'progress-phase'; } else { type = 'progress-finish'; }
if (i < info.currPhase) {
stat = 'progress-done';
} else if (i === info.currPhase) {
if (info.status === 'running') {
stat = 'progress-doing';
} else if (info.status === 'finished') {
stat = 'progress-done';
} else {
stat = 'progress-error';
}
} else if (i === info.totalPhase + 1) {
if (info.status === 'finished') {
stat = info.warning ? 'progress-warning' : 'progress-done';
} else {
stat = 'progress-todo';
}
} else {
stat = 'progress-todo';
}
progList.push(
<div className={`${type} ${stat}`} key={i}>
{content}
</div>,
);
}
}
return (
); }
const getJobStatus = (status: Status, warning: boolean) => { if (status === 'running') { return ( <> 実行中 {warning && ( )} </> ); } if (status === 'finished') { if (warning) { return 警告終了; } return 正常終了; } if (status === 'error') { return 異常終了; } if (status === 'canceled') { return 処理中断; } // eslint-disable-next-line react/jsx-no-useless-fragment return <>{status}</>; };
function ErrorDetailModal({ info }: { info?: JobDetailsTypes }) { const dumpMessage = (info?.errorInfo?.dumpMsg || '') .split(/(\r?\n)/) .map((line, idx) => ( // eslint-disable-next-line react/no-array-index-key
Job ID: {info?.id}
{dumpMessage}
{info?.errorInfo?.dumpData}
ID | 種別 | 処理名 | 状態 | 実行日時 | 終了日時 |
---|
`import parse from 'html-react-parser'; import React, { useEffect, useLayoutEffect, useState } from 'react'; import ReactPaginate from 'react-paginate'; import { useHistory } from 'react-router-dom'; import Swal from 'sweetalert2'; import withReactContent from 'sweetalert2-react-content'; import { commonAjax } from '../../../components/commonAjax'; import { currentJST } from '../util/dateTimeUtil'; import { DialogModalOK, showDialog } from './dialogModal';
// eslint-disable-next-line @typescript-eslint/no-var-requires const moment = require('moment'); require('moment-duration-format');
const MySwal = withReactContent(Swal);
interface Props { match: { params: { id?: string; }; }; }
type JobEntry = { id: string; type: string; name: string; execDate: string; endDate: string | null; status: Status; warning: boolean; };
type JobDetailsTypes = { id: string; type: string; name: string; currPhase: number; totalPhase: number; phaseDesc: string; execDate: string; endDate: string | null; status: Status; warning: boolean; errorInfo: { message: string; description: string; userAction: string; dumpMsg: string | null; dumpData: string | null; } | null; externalQuery: { apiTablePhy?: string; putApiDataTran?: string; metrics?: string; putMetricsTran?: string; } | null; };
type Tag = { name: string; selected: boolean; };
type Status = 'running' | 'finished' | 'error';
function ProgressIndicator({ info }: { info?: JobDetailsTypes }) { const progList: JSX.Element[] = []; if (info) { for (let i = 0; i <= info.totalPhase + 1; i += 1) { let type; let stat; const content = null; if (i === 0) { type = 'progress-start'; } else if (i <= info.totalPhase) { type = 'progress-phase'; } else { type = 'progress-finish'; }
if (i < info.currPhase) {
stat = 'progress-done';
} else if (i === info.currPhase) {
if (info.status === 'running') {
stat = 'progress-doing';
} else if (info.status === 'finished') {
stat = 'progress-done';
} else {
stat = 'progress-error';
}
} else if (i === info.totalPhase + 1) {
if (info.status === 'finished') {
stat = info.warning ? 'progress-warning' : 'progress-done';
} else {
stat = 'progress-todo';
}
} else {
stat = 'progress-todo';
}
progList.push(
<div className={`${type} ${stat}`} key={i}>
{content}
</div>,
);
}
}
return (
); }
const getJobStatus = (status: Status, warning: boolean) => { if (status === 'running') { return ( <> 実行中 {warning && ( )} </> ); } if (status === 'finished') { if (warning) { return 警告終了; } return 正常終了; } if (status === 'error') { return 異常終了; } if (status === 'canceled') { return 処理中断; } // eslint-disable-next-line react/jsx-no-useless-fragment return <>{status}</>; };
function ErrorDetailModal({ info }: { info?: JobDetailsTypes }) { const dumpMessage = (info?.errorInfo?.dumpMsg || '') .split(/(\r?\n)/) .map((line, idx) => ( // eslint-disable-next-line react/no-array-index-key
Job ID: {info?.id}
{dumpMessage}
{info?.errorInfo?.dumpData}
ID | 種別 | 処理名 | 状態 | 実行日時 | 終了日時 |
---|
`import parse from 'html-react-parser'; import React, { useEffect, useLayoutEffect, useState } from 'react'; import ReactPaginate from 'react-paginate'; import { useHistory } from 'react-router-dom'; import Swal from 'sweetalert2'; import withReactContent from 'sweetalert2-react-content'; import { commonAjax } from '../../../components/commonAjax'; import { currentJST } from '../util/dateTimeUtil'; import { DialogModalOK, showDialog } from './dialogModal';
// eslint-disable-next-line @typescript-eslint/no-var-requires const moment = require('moment'); require('moment-duration-format');
const MySwal = withReactContent(Swal);
interface Props { match: { params: { id?: string; }; }; }
type JobEntry = { id: string; type: string; name: string; execDate: string; endDate: string | null; status: Status; warning: boolean; };
type JobDetailsTypes = { id: string; type: string; name: string; currPhase: number; totalPhase: number; phaseDesc: string; execDate: string; endDate: string | null; status: Status; warning: boolean; errorInfo: { message: string; description: string; userAction: string; dumpMsg: string | null; dumpData: string | null; } | null; externalQuery: { apiTablePhy?: string; putApiDataTran?: string; metrics?: string; putMetricsTran?: string; } | null; };
type Tag = { name: string; selected: boolean; };
type Status = 'running' | 'finished' | 'error';
function ProgressIndicator({ info }: { info?: JobDetailsTypes }) { const progList: JSX.Element[] = []; if (info) { for (let i = 0; i <= info.totalPhase + 1; i += 1) { let type; let stat; const content = null; if (i === 0) { type = 'progress-start'; } else if (i <= info.totalPhase) { type = 'progress-phase'; } else { type = 'progress-finish'; }
if (i < info.currPhase) {
stat = 'progress-done';
} else if (i === info.currPhase) {
if (info.status === 'running') {
stat = 'progress-doing';
} else if (info.status === 'finished') {
stat = 'progress-done';
} else {
stat = 'progress-error';
}
} else if (i === info.totalPhase + 1) {
if (info.status === 'finished') {
stat = info.warning ? 'progress-warning' : 'progress-done';
} else {
stat = 'progress-todo';
}
} else {
stat = 'progress-todo';
}
progList.push(
<div className={`${type} ${stat}`} key={i}>
{content}
</div>,
);
}
}
return (
); }
const getJobStatus = (status: Status, warning: boolean) => { if (status === 'running') { return ( <> 実行中 {warning && ( )} </> ); } if (status === 'finished') { if (warning) { return 警告終了; } return 正常終了; } if (status === 'error') { return 異常終了; } if (status === 'canceled') { return 処理中断; } // eslint-disable-next-line react/jsx-no-useless-fragment return <>{status}</>; };
function ErrorDetailModal({ info }: { info?: JobDetailsTypes }) { const dumpMessage = (info?.errorInfo?.dumpMsg || '') .split(/(\r?\n)/) .map((line, idx) => ( // eslint-disable-next-line react/no-array-index-key
Job ID: {info?.id}
{dumpMessage}
{info?.errorInfo?.dumpData}
ID | 種別 | 処理名 | 状態 | 実行日時 | 終了日時 |
---|
import parse from 'html-react-parser';
import React, { useEffect, useLayoutEffect, useState } from 'react';
import ReactPaginate from 'react-paginate';
import { useHistory } from 'react-router-dom';
import Swal from 'sweetalert2';
import withReactContent from 'sweetalert2-react-content';
import { commonAjax } from '../../../components/commonAjax';
import { currentJST } from '../util/dateTimeUtil';
import { DialogModalOK, showDialog } from './dialogModal';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const moment = require('moment');
require('moment-duration-format');
const MySwal = withReactContent(Swal);
interface Props {
match: {
params: {
id?: string;
};
};
}
type JobEntry = {
id: string;
type: string;
name: string;
execDate: string;
endDate: string | null;
status: Status;
warning: boolean;
};
type JobDetailsTypes = {
id: string;
type: string;
name: string;
currPhase: number;
totalPhase: number;
phaseDesc: string;
execDate: string;
endDate: string | null;
status: Status;
warning: boolean;
errorInfo: {
message: string;
description: string;
userAction: string;
dumpMsg: string | null;
dumpData: string | null;
} | null;
externalQuery: {
apiTablePhy?: string;
putApiDataTran?: string;
metrics?: string;
putMetricsTran?: string;
} | null;
};
type Tag = {
name: string;
selected: boolean;
};
type Status = 'running' | 'finished' | 'error';
function ProgressIndicator({ info }: { info?: JobDetailsTypes }) {
const progList: JSX.Element[] = [];
if (info) {
for (let i = 0; i <= info.totalPhase + 1; i += 1) {
let type;
let stat;
const content = null;
if (i === 0) {
type = 'progress-start';
} else if (i <= info.totalPhase) {
type = 'progress-phase';
} else {
type = 'progress-finish';
}
if (i < info.currPhase) {
stat = 'progress-done';
} else if (i === info.currPhase) {
if (info.status === 'running') {
stat = 'progress-doing';
} else if (info.status === 'finished') {
stat = 'progress-done';
} else {
stat = 'progress-error';
}
} else if (i === info.totalPhase + 1) {
if (info.status === 'finished') {
stat = info.warning ? 'progress-warning' : 'progress-done';
} else {
stat = 'progress-todo';
}
} else {
stat = 'progress-todo';
}
progList.push(
<div className={`${type} ${stat}`} key={i}>
{content}
</div>,
);
}
}
return (
<div className="progress-indicator">
<div className="progress-arrow">{progList}</div>
<div className="phase-description">{info?.phaseDesc}</div>
</div>
);
}
const getJobStatus = (status: Status, warning: boolean) => {
if (status === 'running') {
return (
<>
<span className="status status-running">実行中</span>
{warning && (
<i className="fa fa-exclamation-triangle status status-warning" />
)}
</>
);
}
if (status === 'finished') {
if (warning) {
return <span className="status status-warning">警告終了</span>;
}
return <span className="status status-finished">正常終了</span>;
}
if (status === 'error') {
return <span className="status status-error">異常終了</span>;
}
if (status === 'canceled') {
return <span className="status status-warning">処理中断</span>;
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{status}</>;
};
function ErrorDetailModal({ info }: { info?: JobDetailsTypes }) {
const dumpMessage = (info?.errorInfo?.dumpMsg || '')
.split(/(\r?\n)/)
.map((line, idx) => (
// eslint-disable-next-line react/no-array-index-key
<React.Fragment key={idx}>
{line.match(/\r?\n/) ? <br /> : parse(line)}
</React.Fragment>
));
return (
<DialogModalOK id="errorDetailModal" title="エラー詳細情報" closeBtn>
<div className="inline-form error-title">
<div className="inline-form-label">メッセージ</div>
<div className="inline-form-value">{info?.errorInfo?.message}</div>
</div>
<div className="inline-form error-desc">
<div className="inline-form-label">詳細</div>
<div className="inline-form-value">
{info?.errorInfo?.description && parse(info.errorInfo.description)}
</div>
</div>
<div className="inline-form error-user-action">
<div className="inline-form-label">アクション</div>
<div className="inline-form-value">
{info?.errorInfo?.userAction && parse(info.errorInfo.userAction)}
</div>
</div>
<div className="inline-form error-dump">
<div className="inline-form-label">内部メッセージ</div>
<div className="inline-form-value">
<p>Job ID: {info?.id}</p>
<p>{dumpMessage}</p>
<p>{info?.errorInfo?.dumpData}</p>
</div>
</div>
</DialogModalOK>
);
}
function JobDetails({ id, load }: { id?: string; load: VoidFunction }) {
const [currId, setCurrId] = useState<string | undefined>(id);
const [info, setInfo] = useState<JobDetailsTypes>();
const [beginUpdate, setBeginUpdate] = useState<boolean>(false);
const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
const fetchLatest = () => {
commonAjax
.axios()
.get('/api/jobs/latest')
.then((res) => {
const { job } = res.data;
setInfo(job);
setCurrId(job.id);
});
};
const update = () => {
commonAjax
.axios()
.get(`/api/jobs/${currId}`)
.then((res) => {
const { job } = res.data;
if (job) {
if (job.status === 'running') {
setBeginUpdate(true);
} else if (timer) {
clearTimeout(timer);
setTimer(null);
}
}
load();
setInfo(job);
});
};
const handleErrorBtnClicked = () => {
showDialog('errorDetailModal');
};
let elapsedTime = '';
let status = null;
if (info) {
// Calculate elapsed time since the job started
const since = moment(info.execDate);
const until = moment(info.endDate ?? currentJST());
elapsedTime = moment
.duration(until.diff(since))
.format('hh:mm:ss', { trim: false });
status = getJobStatus(info.status, info.warning);
}
// ジョブ中断
const handleCancelClick = () => {
commonAjax
.axios()
.post(`/api/jobs/cancel`, {
jobId: currId,
})
.then(() => {
update();
})
.catch((err) => {
MySwal.fire({
icon: 'error',
title: `ERROR: ${err}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
});
};
// サンプルデータ挿入データ削除
const handleDeleteApiDataClick = () => {
const targetData = {
apiTablePhy: info?.externalQuery?.apiTablePhy,
putApiDataTran: info?.externalQuery?.putApiDataTran,
};
commonAjax
.axios({ swalFire: false, loading: true })
.delete(`/api/api/deleteData`, {
data: targetData,
})
.then((res) => {
if (!res.data.dataExistFlg) {
MySwal.fire({
icon: 'error',
title: `ERROR: ${res.data.message}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
} else {
MySwal.fire({
icon: 'success',
title: `Message: ${res.data.message}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
}
})
.catch((err) => {
MySwal.fire({
icon: 'error',
title: `ERROR: ${err}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
});
};
// メトリクス実行データ削除
const handleDeleteMetricsDataClick = () => {
const targetData = {
metrics: info?.externalQuery?.metrics,
putMetricsTran: info?.externalQuery?.putMetricsTran,
};
commonAjax
.axios({ swalFire: false, loading: true })
.delete(`/api/api/deleteMetricsData`, {
data: targetData,
})
.then((res) => {
if (!res.data.dataExistFlg) {
MySwal.fire({
icon: 'error',
title: `ERROR: ${res.data.message}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
} else {
MySwal.fire({
icon: 'success',
title: `Message: ${res.data.message}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
}
})
.catch((err) => {
MySwal.fire({
icon: 'error',
title: `ERROR: ${err}`,
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
});
});
};
const cancelAndUpdateButtonHidden =
info?.status === 'running' && info?.type === 'メトリクス即時実行';
const deleteApiDataButtonHidden = info?.type === 'サンプルデータ挿入';
const deleteMetricsDataButtonHidden = info?.type === 'メトリクス即時実行';
useEffect(() => {
if (!currId) {
fetchLatest();
} else if (currId !== id) {
setCurrId(id);
}
}, [id]);
useEffect(() => {
let ignore = false;
if (timer) {
clearTimeout(timer);
setTimer(null);
}
if (currId) {
setTimeout(() => !ignore && update());
}
return () => {
ignore = true;
};
}, [currId]);
useLayoutEffect(() => {
if (beginUpdate) {
const newTimer = setTimeout(update, 3000);
setTimer(newTimer);
setBeginUpdate(false);
}
}, [beginUpdate]);
return (
<div className="job-details">
<div className="content-header-right btn-update">
{
// eslint-disable-next-line no-nested-ternary
cancelAndUpdateButtonHidden ? (
<button
type="button"
className="btn btn-danger mr-3"
onClick={handleCancelClick}
>
中断
</button>
) : deleteApiDataButtonHidden ? (
<button
type="button"
className="btn btn-secondary"
onClick={handleDeleteApiDataClick}
>
挿入データ削除
</button>
) : (
deleteMetricsDataButtonHidden && (
<button
type="button"
className="btn btn-secondary"
onClick={handleDeleteMetricsDataClick}
>
実行データ削除
</button>
)
)
}
</div>
<ProgressIndicator info={info} />
<div className="inline-form-job-detail detail-id">
<div className="inline-form-label detail-label">ジョブID</div>
<div className="inline-form-value">{info?.id}</div>
</div>
<div className="inline-form-job-detail detail-type">
<div className="inline-form-label detail-label">種別</div>
<div className="inline-form-value">{info?.type}</div>
</div>
<div className="inline-form-job-detail detail-name">
<div className="inline-form-label detail-label">処理名</div>
<div className="inline-form-value">{info?.name}</div>
</div>
<div className="inline-form-job-detail detail-exec-date">
<div className="inline-form-label detail-label">実行日時</div>
<div className="inline-form-value">{info?.execDate}</div>
</div>
<div className="inline-form-job-detail detail-end-date">
<div className="inline-form-label detail-label">終了日時</div>
<div className="inline-form-value">{info?.endDate}</div>
</div>
<div className="inline-form-job-detail detail-elapsed-time">
<div className="inline-form-label detail-label">所要時間</div>
<div className="inline-form-value">{elapsedTime}</div>
</div>
<div className="inline-form-job-detail detail-status">
<div className="inline-form-label detail-label">状態</div>
<div className="inline-form-value">
{status}
{info?.errorInfo && (
<button
type="button"
className="btn btn-small btn-primary"
onClick={handleErrorBtnClicked}
>
エラー詳細
</button>
)}
</div>
</div>
<ErrorDetailModal info={info} />
</div>
);
}
// fillter tag
function FilterTags({
tags,
setTypes,
}: {
tags: Tag[];
setTypes: React.Dispatch<React.SetStateAction<Tag[]>>;
}) {
const handleClick = (item: Tag) => {
// eslint-disable-next-line no-param-reassign
item.selected = !item.selected;
const t = tags.slice(0, tags.length);
setTypes(t);
};
return (
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
<div className="inline-form-label">ジョブ種別</div>
<div className="inline-form-group">
{tags.map((item: Tag) => (
<button
type="button"
className={`btn btn-tag${item.selected ? ' active' : ''}`}
key={item.name}
onClick={() => handleClick(item)}
>
{item.name}
</button>
))}
</div>
</div>
);
}
function JobListEntry({
entry,
onClick,
}: {
entry: JobEntry;
onClick: (id: string) => void;
}) {
const handleClick = () => {
onClick(entry.id);
};
const status = getJobStatus(entry.status, entry.warning);
return (
<tr className="job" onClick={handleClick}>
<td>{entry.id}</td>
<td>{entry.type}</td>
<td className="w-50">{entry.name}</td>
<td>{status}</td>
<td>{entry.execDate}</td>
<td>{entry.endDate}</td>
</tr>
);
}
function JobList({
onJobSelected,
load,
list,
types,
setTypes,
}: {
onJobSelected: (id: string) => void;
load: VoidFunction;
list: JobEntry[] | undefined;
types: Tag[];
setTypes: React.Dispatch<React.SetStateAction<Tag[]>>;
}) {
const perPage = 10;
// const [types, setTypes] = useState<Tag[]>([]);
// const [list, setList] = useState<JobEntry[]>();
const [offset, setOffset] = useState(0);
useEffect(() => {
load();
}, [types]);
const handlePageChange = (data: { selected: number }) => {
const page = data.selected;
setOffset(page * perPage);
};
if (list) {
return (
<div className="card card-primary jobs">
<div className="card-body">
{list.length > 0 && (
<>
<FilterTags tags={types} setTypes={setTypes} />
<table className="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>種別</th>
<th>処理名</th>
<th>状態</th>
<th>実行日時</th>
<th>終了日時</th>
</tr>
</thead>
<tbody>
{list
.filter(
(e: JobEntry, idx: number) =>
idx >= offset && idx < offset + perPage,
)
.map((e: JobEntry) => (
<JobListEntry
entry={e}
onClick={onJobSelected}
key={e.id}
/>
))}
</tbody>
</table>
<div className="d-flex justify-content-center">
<ReactPaginate
pageCount={Math.ceil(list.length / perPage)}
marginPagesDisplayed={3}
pageRangeDisplayed={3}
onPageChange={handlePageChange}
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
activeClassName="active"
previousLabel="<"
nextLabel=">"
previousClassName="page-item"
nextClassName="page-item"
previousLinkClassName="page-link"
nextLinkClassName="page-link"
disabledClassName="disabled"
breakLabel="..."
breakClassName="page-item"
breakLinkClassName="page-link"
/>
</div>
</>
)}
{list.length === 0 && (
<div className="no-jobs">ジョブはありません</div>
)}
</div>
</div>
);
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
function JobStatus(props: Props) {
const { match } = props;
const history = useHistory();
const [id, setId] = useState<string | undefined>(match.params.id);
const [list, setList] = useState<JobEntry[]>();
const [types, setTypes] = useState<Tag[]>([]);
useEffect(() => {
setId(match.params.id);
}, [match.params.id, history]);
const handleJobSelected = (targetId: string) => {
setId(targetId);
history.push(`/jobs/${targetId}`);
};
// ジョブ終了後に一覧更新の必要があるため、メインコンポーネントに移動
const load = () => {
const req = {
types: types.filter((t) => t.selected).map((t) => t.name),
};
commonAjax
.axios()
.post('/api/jobs', req)
.then((res) => {
const { data } = res;
setList(
data.list.sort(
(
a: { status: string; id: string },
b: { status: string; id: string },
) =>
// eslint-disable-next-line no-nested-ternary
a.status === 'running' && b.status !== 'running'
? -1
: a.status !== 'running' && b.status === 'running'
? 1
: parseInt(b.id, 10) - parseInt(a.id, 10),
),
);
if (!types.length) {
setTypes(
data.types.map((t: string) => ({ name: t, selected: true })),
);
}
});
};
return (
<div className="content-wrapper job-status">
<section className="page-cover">
<div className="page-cover-title-frame">
<h1>ジョブステータス確認</h1>
</div>
</section>
<section className="content">
<div className="card card-primary status">
<div className="card-body">
<JobDetails id={id} load={load} />
</div>
</div>
<JobList
onJobSelected={handleJobSelected}
load={load}
list={list}
types={types}
setTypes={setTypes}
/>
</section>
</div>
);
}
// const JOB_TYPES = [
// 'API追加',
// 'API更新',
// 'メトリクスデプロイ',
// '新規メトリクス追加',
// 'サンプルデータ挿入',
// 'メトリクス改修',
// ]; // , "個社環境コピー", "個社環境削除", "デモ環境初期化"
// const SAMPLE_JOBS: { [key: string]: JobDetails } = {
// '123': {
// id: '123',
// type: 'サンプルデータ挿入',
// name: '完成品販売計画_月次 サンプルデータ挿入処理',
// currPhase: 2,
// totalPhase: 3,
// phaseDesc: 'データ挿入中',
// execDate: '2023-07-31 13:55:00',
// endDate: null,
// status: 'running' as Status,
// warning: false,
// errorInfo: null,
// },
// '122': {
// id: '122',
// type: 'サンプルデータ挿入',
// name: '完成品販売計画_月次 サンプルデータ挿入処理',
// currPhase: 2,
// totalPhase: 3,
// phaseDesc: 'データ挿入中',
// execDate: '2023-07-25 05:55:00',
// endDate: null,
// status: 'running' as Status,
// warning: true,
// errorInfo: {
// message: 'データバリデーションエラー',
// description:
// '必要な情報が欠落しているか、データが規定の形式に適合していません。',
// userAction: '入力内容を確認して再度お試しください。',
// dumpMsg: null,
// dumpData: null,
// },
// },
// '121': {
// id: '121',
// type: 'サンプルデータ挿入',
// name: '完成品販売計画_月次 サンプルデータ挿入処理',
// currPhase: 3,
// totalPhase: 3,
// phaseDesc: 'リモートリポジトリをclone中',
// execDate: '2023-07-24 13:43:55',
// endDate: '2023-07-24 13:56:08',
// status: 'finished' as Status,
// warning: true,
// errorInfo: {
// message: 'データバリデーションエラー',
// description:
// '必要な情報が欠落しているか、データが規定の形式に適合していません。',
// userAction: '入力内容を確認して再度お試しください。',
// dumpMsg: null,
// dumpData: null,
// },
// },
// '120': {
// id: '120',
// type: 'API作成',
// name: '"TEST API" 作成処理',
// currPhase: 2,
// totalPhase: 10,
// phaseDesc: 'リモートリポジトリをclone中',
// execDate: '2023-07-24 13:43:55',
// endDate: '2023-07-24 13:56:08',
// status: 'error' as Status,
// warning: false,
// errorInfo: {
// message: 'リモートリポジトリのcloneに失敗',
// description:
// 'Githubの認証に失敗したため、リモートリポジトリがcloneできませんでした。',
// userAction:
// '本画面のスクリーンショットを取得し、サポートへお問い合わせください。',
// dumpMsg: 'git.exc.GitCommandError',
// dumpData:
// "git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)\n" +
// "cmdline: git clone -v -b feature/ind_tmp https://*****:*****@github.com/i4platform/i4-dpb_script.git tmp/i4-dpb\nstderr: 'Cloning into 'tmp/i4-dpb'...\n" +
// 'remote: Invalid username or password.\n' +
// "fatal: Authentication failed for 'https://github.com/i4platform/i4-dpb_script.git/'",
// },
// },
// '119': {
// id: '119',
// type: 'サンプルデータ挿入',
// name: '完成品販売計画_月次 サンプルデータ挿入処理',
// currPhase: 3,
// totalPhase: 3,
// phaseDesc: '処理終了',
// execDate: '2023-07-25 05:55:00',
// endDate: '2023-07-25 06:15:18',
// status: 'finished' as Status,
// warning: false,
// errorInfo: null,
// },
// };
// const TESTDATA_LIST = [
// {
// id: '123',
// type: 'サンプルデータ挿入',
// name: '完成品販売計画_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 14:43:55',
// endDate: null,
// status: 'running' as Status,
// warning: false,
// },
// {
// id: '122',
// type: 'サンプルデータ挿入',
// name: '完成品販売計画_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 11:23:55',
// endDate: null,
// status: 'running' as Status,
// warning: true,
// },
// {
// id: '121',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: true,
// },
// {
// id: '120',
// type: 'API更新',
// name: '完成品販売計画_月次 API更新処理',
// execDate: '2023-07-24 13:23:55',
// endDate: '2023-07-24 13:43:55',
// status: 'error' as Status,
// warning: false,
// },
// {
// id: '119',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: false,
// },
// {
// id: '118',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: false,
// },
// {
// id: '117',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: false,
// },
// {
// id: '116',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: false,
// },
// {
// id: '115',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: false,
// },
// {
// id: '114',
// type: 'サンプルデータ挿入',
// name: '完成品販売_月次 サンプルデータ挿入処理',
// execDate: '2023-07-24 10:23:55',
// endDate: '2023-07-24 10:33:55',
// status: 'finished' as Status,
// warning: false,
// },
// ];
export default JobStatus;
function FilterTags({
tags,
setTypes,
}: {
tags: Tag[];
setTypes: React.Dispatch<React.SetStateAction<Tag[]>>;
}) {
const handleClick = (item: Tag) => {
// 切换Tag的选择状态
item.selected = !item.selected;
const updatedTags = tags.map((tag) =>
tag.name === item.name ? { ...tag, selected: item.selected } : tag
);
setTypes(updatedTags);
};
return (
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
<div className="inline-form-label">ジョブ種別</div>
<div className="inline-form-group">
{tags.map((item: Tag) => (
<button
type="button"
className={`btn btn-tag${item.selected ? ' active' : ''}`}
key={item.name}
onClick={() => handleClick(item)}
>
{item.name}
</button>
))}
</div>
</div>
);
}
useEffect(() => {
const initialTags = types.map((t) => ({ name: t.name, selected: false })); // 默认设置未选择状态
setTypes(initialTags);
}, []);
const load = () => {
const selectedTags = types.filter((tag) => tag.selected).map((tag) => tag.name);
const req = {
types: selectedTags.length ? selectedTags : [], // 如果没有选中Tag,则显示全部
};
commonAjax
.axios()
.post('/api/jobs', req)
.then((res) => {
const { data } = res;
setList(
data.list.sort(
(
a: { status: string; id: string },
b: { status: string; id: string },
) =>
a.status === 'running' && b.status !== 'running'
? -1
: a.status !== 'running' && b.status === 'running'
? 1
: parseInt(b.id, 10) - parseInt(a.id, 10),
),
);
});
};
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 状态控制哪一个モーダル打开
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
useEffect(() => {
return () => {
// 清理掉多余的モーダル状态
setIsModalOpen(null);
};
}, []);
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => {
setIsModalOpen('editApiList');
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => {
setIsModalOpen('apiDtl');
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
onClick={() => {
setIsModalOpen('selectDeployApi');
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen === 'editApiList' && (
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal
id="api-dtl-modal"
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
key={item.tag_name}
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)}
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{isModalOpen === `${api.physical_name}-dtl-modal` && (
<>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
)}
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
ERROR in src/app2/components/pages/apiList.tsx:137:11
TS2322: Type '{ id: string; changeStatus: boolean; setChangeStatus: Dispatch<SetStateAction<boolean>>; }' is not assignable to type 'IntrinsicAttributes & { val: Api; }'.
Property 'id' does not exist on type 'IntrinsicAttributes & { val: Api; }'.
135 | {isModalOpen === 'apiDtl' && (
136 | <NewAPIDtlModal
> 137 | id="api-dtl-modal"
| ^^
138 | changeStatus={changeStatus}
139 | setChangeStatus={setChangeStatus}
140 | />
ERROR in src/app2/components/pages/apiList.tsx:334:32
TS2304: Cannot find name 'setIsModalOpen'.
332 | <button
333 | className="btn btn-secondary btn-secondary"
> 334 | onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)}
| ^^^^^^^^^^^^^^
335 | >
336 | サンプルデータ挿入 / データセット連携項目追加
337 | </button>
ERROR in src/app2/components/pages/apiList.tsx:339:14
TS2304: Cannot find name 'isModalOpen'.
337 | </button>
338 | </div>
> 339 | {isModalOpen === `${api.physical_name}-dtl-modal` && (
| ^^^^^^^^^^^
340 | <>
341 | <NewAPIDtlModal val={api} />
342 | <NewSampleDataModal
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 修正1: 增加 isModalOpen 和 setIsModalOpen 状态
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
useEffect(() => {
return () => {
// 清理掉多余的モーダル状态
setIsModalOpen(null);
};
}, []);
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => {
setIsModalOpen('editApiList'); // 修正2: 使用 setIsModalOpen 来控制モーダル的打开
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => {
setIsModalOpen('apiDtl'); // 修正2: 控制 API详情モーダル的显示
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
onClick={() => {
setIsModalOpen('selectDeployApi'); // 修正2: 控制一括有効化モーダル
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen === 'editApiList' && ( // 修正3: 根据 isModalOpen 状态控制モーダル显示
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
key={item.tag_name}
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)} // 修正4: 使用状态控制每个API的モーダル
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{isModalOpen === `${api.physical_name}-dtl-modal` && ( // 修正5: 控制显示 API详情モーダル
<>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
)}
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
ERROR in src/app2/components/pages/apiList.tsx:139:11
TS2322: Type '{ changeStatus: boolean; setChangeStatus: Dispatch<SetStateAction<boolean>>; }' is not assignable to type 'IntrinsicAttributes & { val: Api; }'.
Property 'changeStatus' does not exist on type 'IntrinsicAttributes & { val: Api; }'.
137 | {isModalOpen === 'apiDtl' && (
138 | <NewAPIDtlModal
> 139 | changeStatus={changeStatus}
| ^^^^^^^^^^^^
140 | setChangeStatus={setChangeStatus}
141 | />
142 | )}
ERROR in src/app2/components/pages/apiList.tsx:337:32
TS2304: Cannot find name 'setIsModalOpen'.
335 | <button
336 | className="btn btn-secondary btn-secondary"
> 337 | onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)} // 修正4: 使用状态控制每个API的モーダル
| ^^^^^^^^^^^^^^
338 | >
339 | サンプルデータ挿入 / データセット連携項目追加
340 | </button>
ERROR in src/app2/components/pages/apiList.tsx:342:14
TS2304: Cannot find name 'isModalOpen'.
340 | </button>
341 | </div>
> 342 | {isModalOpen === `${api.physical_name}-dtl-modal` && ( // 修正5: 控制显示 API详情モーダル
| ^^^^^^^^^^^
343 | <>
344 | <NewAPIDtlModal val={api} />
345 | <NewSampleDataModal
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 定义 isModalOpen 和 setIsModalOpen
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
useEffect(() => {
return () => {
// 清理掉多余的モーダル状态
setIsModalOpen(null);
};
}, []);
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => {
setIsModalOpen('editApiList');
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => {
setIsModalOpen('apiDtl');
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
onClick={() => {
setIsModalOpen('selectDeployApi');
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen === 'editApiList' && (
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal val={api} /> {/* 移除 changeStatus 和 setChangeStatus */}
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
key={item.tag_name}
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)}
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{isModalOpen === `${api.physical_name}-dtl-modal` && (
<>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
)}
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
Failed to compile.
SyntaxError: /home/uenv/q_li/Desktop/catalog-web-app/client/src/app2/components/pages/apiList.tsx: Unexpected token, expected "," (138:37)
136 |
137 | {isModalOpen === 'apiDtl' && (
> 138 | <NewAPIDtlModal val={api} /> {/* 移除 changeStatus 和 setChangeStatus */}
| ^
139 | )}
140 |
141 | {isModalOpen === 'selectDeployApi' && (
ERROR in ./src/app2/components/pages/apiList.tsx
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /home/uenv/q_li/Desktop/catalog-web-app/client/src/app2/components/pages/apiList.tsx: Unexpected token, expected "," (138:37)
136 |
137 | {isModalOpen === 'apiDtl' && (
> 138 | <NewAPIDtlModal val={api} /> {/* 移除 changeStatus 和 setChangeStatus */}
| ^
139 | )}
140 |
141 | {isModalOpen === 'selectDeployApi' && (
at instantiate (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:60:32)
at constructor (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:355:12)
at TypeScriptParserMixin.raise (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:3204:19)
at TypeScriptParserMixin.unexpected (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:3234:16)
at TypeScriptParserMixin.expect (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:3571:28)
at TypeScriptParserMixin.parseParenAndDistinguishExpression (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:11450:14)
at TypeScriptParserMixin.parseExprAtom (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:11115:23)
at TypeScriptParserMixin.parseExprAtom (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:6918:20)
at TypeScriptParserMixin.parseExprSubscripts (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10841:23)
at TypeScriptParserMixin.parseUpdate (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10824:21)
at TypeScriptParserMixin.parseMaybeUnary (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10800:23)
at TypeScriptParserMixin.parseMaybeUnary (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9684:18)
at TypeScriptParserMixin.parseMaybeUnaryOrPrivate (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10638:61)
at TypeScriptParserMixin.parseExprOpBaseRightExpr (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10731:34)
at TypeScriptParserMixin.parseExprOpRightExpr (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10726:21)
at TypeScriptParserMixin.parseExprOp (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10689:27)
at TypeScriptParserMixin.parseExprOp (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9190:18)
at TypeScriptParserMixin.parseExprOp (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10697:21)
at TypeScriptParserMixin.parseExprOp (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9190:18)
at TypeScriptParserMixin.parseExprOps (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10647:17)
at TypeScriptParserMixin.parseMaybeConditional (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10620:23)
at TypeScriptParserMixin.parseMaybeAssign (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10581:21)
at TypeScriptParserMixin.parseMaybeAssign (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9631:20)
at TypeScriptParserMixin.parseExpressionBase (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10535:23)
at /home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10531:39
at TypeScriptParserMixin.allowInAnd (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:12231:12)
at TypeScriptParserMixin.parseExpression (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10531:17)
at TypeScriptParserMixin.jsxParseExpressionContainer (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:6778:31)
at TypeScriptParserMixin.jsxParseElementAt (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:6857:36)
at TypeScriptParserMixin.jsxParseElement (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:6901:17)
at TypeScriptParserMixin.parseExprAtom (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:6913:19)
at TypeScriptParserMixin.parseExprSubscripts (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10841:23)
at TypeScriptParserMixin.parseUpdate (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10824:21)
at TypeScriptParserMixin.parseMaybeUnary (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10800:23)
at TypeScriptParserMixin.parseMaybeUnary (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9684:18)
at TypeScriptParserMixin.parseMaybeUnaryOrPrivate (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10638:61)
at TypeScriptParserMixin.parseExprOps (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10643:23)
at TypeScriptParserMixin.parseMaybeConditional (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10620:23)
at TypeScriptParserMixin.parseMaybeAssign (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10581:21)
at /home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9620:39
at TypeScriptParserMixin.tryParse (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:3578:20)
at TypeScriptParserMixin.parseMaybeAssign (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:9620:18)
at /home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10551:39
at TypeScriptParserMixin.allowInAnd (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:12231:12)
at TypeScriptParserMixin.parseMaybeAssignAllowIn (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10551:17)
at TypeScriptParserMixin.parseParenAndDistinguishExpression (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:11464:28)
at TypeScriptParserMixin.parseExprAtom (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:11115:23)
at TypeScriptParserMixin.parseExprAtom (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:6918:20)
at TypeScriptParserMixin.parseExprSubscripts (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10841:23)
at TypeScriptParserMixin.parseUpdate (/home/uenv/q_li/Desktop/catalog-web-app/client/node_modules/@babel/parser/lib/index.js:10824:21)
webpack compiled with 1 error
ERROR in src/app2/components/pages/apiList.tsx:138:30
TS2304: Cannot find name 'api'.
136 |
137 | {isModalOpen === 'apiDtl' && (
> 138 | <NewAPIDtlModal val={api} /> {/* 移除 changeStatus 和 setChangeStatus */}
| ^^^
139 | )}
140 |
141 | {isModalOpen === 'selectDeployApi' && (
ERROR in src/app2/components/pages/apiList.tsx:138:38
TS1005: ')' expected.
136 |
137 | {isModalOpen === 'apiDtl' && (
> 138 | <NewAPIDtlModal val={api} /> {/* 移除 changeStatus 和 setChangeStatus */}
| ^
139 | )}
140 |
141 | {isModalOpen === 'selectDeployApi' && (
ERROR in src/app2/components/pages/apiList.tsx:139:8
TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
137 | {isModalOpen === 'apiDtl' && (
138 | <NewAPIDtlModal val={api} /> {/* 移除 changeStatus 和 setChangeStatus */}
> 139 | )}
| ^
140 |
141 | {isModalOpen === 'selectDeployApi' && (
142 | <SelectDeployApi
ERROR in src/app2/components/pages/apiList.tsx:334:32
TS2304: Cannot find name 'setIsModalOpen'.
332 | <button
333 | className="btn btn-secondary btn-secondary"
> 334 | onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)}
| ^^^^^^^^^^^^^^
335 | >
336 | サンプルデータ挿入 / データセット連携項目追加
337 | </button>
ERROR in src/app2/components/pages/apiList.tsx:339:14
TS2304: Cannot find name 'isModalOpen'.
337 | </button>
338 | </div>
> 339 | {isModalOpen === `${api.physical_name}-dtl-modal` && (
| ^^^^^^^^^^^
340 | <>
341 | <NewAPIDtlModal val={api} />
342 | <NewSampleDataModal
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 定义 isModalOpen 和 setIsModalOpen
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.forEach((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.forEach((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
useEffect(() => {
return () => {
setIsModalOpen(null);
};
}, []);
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">現在、設定されているデータセット一覧です</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button type="button" className="btn btn-secondary" onClick={() => setIsModalOpen('editApiList')}>
編集
</button>
<button type="button" className="btn btn-primary" onClick={() => setIsModalOpen('apiDtl')}>
追加
</button>
<button type="button" className="btn-long-text btn-primary" onClick={() => setIsModalOpen('selectDeployApi')}>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List */}
{viewList(apiList, tagList, changeStatus, setChangeStatus, setErrorMsg, errorMsg, setDeployJobId)}
</section>
{isModalOpen === 'editApiList' && <EditApiList val={apiList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} />}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal val={apiList[0]} /> {/* 确保此处传递有效的 api 对象 */}
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi val={apiList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} />
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// filter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select;
setTags([...tags]);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
function viewList(item: Defs.Api[], tagList: Defs.Tag[], changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, errorMsg: any, setDeployJobId: any) {
const filteredList = item.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) => tagList.some((tag) => tag.select && tag.tag_name === apiTag));
});
const length = Math.ceil(filteredList.length / 3);
const listItem = length !== 0 ? transpose(new Array(length).fill(0).map((_, i) => filteredList.slice(i * 3, (i + 1) * 3))) : [];
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) => (
<ApiCard key={val.physical_name} api={val} tagList={tagList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} setErrorMsg={setErrorMsg} errorMsg={errorMsg} setDeployJobId={setDeployJobId} />
))}
</div>
))}
</div>
);
}
function ApiCard(api: Defs.Api, tagList: Defs.Tag[], changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, errorMsg: any, setDeployJobId: any) {
let selected = false;
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.forEach((item: Defs.Tag) => {
api.tags.forEach((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
selected = true;
}
}
});
});
return (
<div className={`card ${selected ? 'card-primary' : 'card-not-applicable not-applicable'} card-collapse-sample`}>
<div className="card-header collapsed" data-toggle="collapse" data-target={`#card-body-${api.physical_name}`}>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${29 / `${api.physical_name} / ${api.logical_name}`.length}vw, 1rem)`,
}}
>
<i className={`fa fa-circle provide-icon-size ${selected ? (api.provide ? 'provide-icon-color' : 'no-provide-icon-color') : undefined}`} aria-hidden="true" />
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => setIsModalOpen(`${api.physical_name}-dtl-modal`)}
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{isModalOpen === `${api.physical_name}-dtl-modal` && (
<>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
)}
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 定义 isModalOpen 和 setIsModalOpen
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.forEach((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.forEach((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
// 当组件卸载或更新时,清空 modal 状态
useEffect(() => {
return () => {
setIsModalOpen(null);
};
}, []);
// 控制 modal 打开和关闭的函数
const handleModalOpen = (modalType: string) => {
if (isModalOpen !== modalType) {
setIsModalOpen(modalType);
} else {
setIsModalOpen(null); // 如果同样的 modal 已经打开,则关闭
}
};
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">現在、設定されているデータセット一覧です</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button type="button" className="btn btn-secondary" onClick={() => handleModalOpen('editApiList')}>
編集
</button>
<button type="button" className="btn btn-primary" onClick={() => handleModalOpen('apiDtl')}>
追加
</button>
<button type="button" className="btn-long-text btn-primary" onClick={() => handleModalOpen('selectDeployApi')}>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List */}
{viewList(apiList, tagList, changeStatus, setChangeStatus, setErrorMsg, errorMsg, setDeployJobId)}
</section>
{/* 根据 isModalOpen 的状态来渲染不同的 modal */}
{isModalOpen === 'editApiList' && <EditApiList val={apiList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} />}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal val={apiList[0]} /> {/* 确保此处传递有效的 api 对象 */}
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi val={apiList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} />
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// 其他辅助函数保持不变
// filter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select;
setTags([...tags]);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
function viewList(item: Defs.Api[], tagList: Defs.Tag[], changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, errorMsg: any, setDeployJobId: any) {
const filteredList = item.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) => tagList.some((tag) => tag.select && tag.tag_name === apiTag));
});
const length = Math.ceil(filteredList.length / 3);
const listItem = length !== 0 ? transpose(new Array(length).fill(0).map((_, i) => filteredList.slice(i * 3, (i + 1) * 3))) : [];
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) => (
<ApiCard key={val.physical_name} api={val} tagList={tagList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} setErrorMsg={setErrorMsg} errorMsg={errorMsg} setDeployJobId={setDeployJobId} />
))}
</div>
))}
</div>
);
}
function ApiCard(api: Defs.Api, tagList: Defs.Tag[], changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, errorMsg: any, setDeployJobId: any) {
let selected = false;
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.forEach((item: Defs.Tag) => {
api.tags.forEach((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
selected = true;
}
}
});
});
return (
<div className={`card ${selected ? 'card-primary' : 'card-not-applicable not-applicable'} card-collapse-sample`}>
<div className="card-header collapsed" data-toggle="collapse" data-target={`#card-body-${api.physical_name}`}>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${29 / `${api.physical_name} / ${api.logical_name}`.length}vw, 1rem)`,
}}
>
<i className={`fa fa-circle provide-icon-size ${selected ? (api.provide ? 'provide-icon-color' : 'no-provide-icon-color') : undefined}`} aria-hidden="true" />
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`#card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => handleModalOpen(`${api.physical_name}-dtl-modal`)}
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{isModalOpen === `${api.physical_name}-dtl-modal` && (
<>
<NewAPIDtlModal val={api} />
<NewSampleDataModal id={api.physical_name} logicalName={api.logical_name} />
<ApiUpdateResultModal val={api} />
</>
)}
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(api, changeStatus, setChangeStatus, setErrorMsg, setDeployJobId);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) => a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(api: Defs.Api, changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, setDeployJobId: any) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK id="api-deploy-job-start-notification" title="処理を開始しました">
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link to={`/jobs/${jobId}`} className="btn btn-secondary" target="_blank" onClick={() => hideDialog('api-deploy-job-start-notification')}>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 定义 isModalOpen 和 setIsModalOpen
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.forEach((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.forEach((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
// 当组件卸载或更新时,清空 modal 状态
useEffect(() => {
return () => {
setIsModalOpen(null);
};
}, []);
// 控制 modal 打开和关闭的函数
const handleModalOpen = (modalType: string) => {
if (isModalOpen !== modalType) {
setIsModalOpen(modalType);
} else {
setIsModalOpen(null); // 如果同样的 modal 已经打开,则关闭
}
};
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">現在、設定されているデータセット一覧です</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button type="button" className="btn btn-secondary" onClick={() => handleModalOpen('editApiList')}>
編集
</button>
<button type="button" className="btn btn-primary" onClick={() => handleModalOpen('apiDtl')}>
追加
</button>
<button type="button" className="btn-long-text btn-primary" onClick={() => handleModalOpen('selectDeployApi')}>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List */}
{viewList(apiList, tagList, changeStatus, setChangeStatus, setErrorMsg, errorMsg, setDeployJobId)}
</section>
{/* 根据 isModalOpen 的状态来渲染不同的 modal */}
{isModalOpen === 'editApiList' && <EditApiList val={apiList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} />}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal val={apiList[0]} /> {/* 确保此处传递有效的 api 对象 */}
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi val={apiList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} />
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// filter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select;
setTags([...tags]);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
function viewList(item: Defs.Api[], tagList: Defs.Tag[], changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, errorMsg: any, setDeployJobId: any) {
const filteredList = item.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) => tagList.some((tag) => tag.select && tag.tag_name === apiTag));
});
const length = Math.ceil(filteredList.length / 3);
const listItem = length !== 0 ? transpose(new Array(length).fill(0).map((_, i) => filteredList.slice(i * 3, (i + 1) * 3))) : [];
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) => (
<ApiCard key={val.physical_name} api={val} tagList={tagList} changeStatus={changeStatus} setChangeStatus={setChangeStatus} setErrorMsg={setErrorMsg} errorMsg={errorMsg} setDeployJobId={setDeployJobId} />
))}
</div>
))}
</div>
);
}
function ApiCard(api: Defs.Api, tagList: Defs.Tag[], changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, errorMsg: any, setDeployJobId: any) {
let selected = false;
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.forEach((item: Defs.Tag) => {
api.tags.forEach((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
selected = true;
}
}
});
});
return (
<div className={`card ${selected ? 'card-primary' : 'card-not-applicable not-applicable'} card-collapse-sample`}>
<div className="card-header collapsed" data-toggle="collapse" data-target={`#card-body-${api.physical_name}`}>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${29 / `${api.physical_name} / ${api.logical_name}`.length}vw, 1rem)`,
}}
>
<i className={`fa fa-circle provide-icon-size ${selected ? (api.provide ? 'provide-icon-color' : 'no-provide-icon-color') : undefined}`} aria-hidden="true" />
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => handleModalOpen(`${api.physical_name}-dtl-modal`)}
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{isModalOpen === `${api.physical_name}-dtl-modal` && (
<>
<NewAPIDtlModal val={api} />
<NewSampleDataModal id={api.physical_name} logicalName={api.logical_name} />
<ApiUpdateResultModal val={api} />
</>
)}
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(api, changeStatus, setChangeStatus, setErrorMsg, setDeployJobId);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) => a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(api: Defs.Api, changeStatus: boolean, setChangeStatus: any, setErrorMsg: any, setDeployJobId: any) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK id="api-deploy-job-start-notification" title="処理を開始しました">
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link to={`/jobs/${jobId}`} className="btn btn-secondary" target="_blank" onClick={() => hideDialog('api-deploy-job-start-notification')}>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 用于控制不同模态框
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
// 控制模态框的打开和关闭
const handleModalOpen = (modalType: string) => {
setIsModalOpen(modalType); // 打开对应的模态框
};
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => handleModalOpen('editApiList')}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => handleModalOpen('apiDtl')}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
onClick={() => handleModalOpen('selectDeployApi')}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{/* 根据 isModalOpen 渲染不同模态框 */}
{isModalOpen === 'editApiList' && (
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal
val={apiList[0]} // 确保传递有效的api对象
/>
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select;
setTags([...tags]);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// 其他功能和组件保持不变...
export default NewApiList;
console.log(
Opening modal: ${modalType});
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 用于控制不同模态框
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
// 控制模态框的打开和关闭
const handleModalOpen = (modalType: string) => {
console.log(`Opening modal: ${modalType}`); // 调试日志,确认点击动作
setIsModalOpen(modalType); // 打开对应的模态框
};
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => handleModalOpen('editApiList')}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => handleModalOpen('apiDtl')}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
onClick={() => handleModalOpen('selectDeployApi')}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{/* 根据 isModalOpen 渲染不同模态框 */}
{isModalOpen === 'editApiList' && (
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal
val={apiList[0]} // 确保传递有效的api对象
/>
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<string | null>(null); // 控制模态框
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
// 控制模态框的打开和关闭
const handleModalOpen = (modalType: string) => {
console.log(`Opening modal: ${modalType}`);
setIsModalOpen(modalType); // 打开对应的模态框
};
// 关闭模态框的函数
const handleModalClose = () => {
setIsModalOpen(null); // 关闭所有模态框
};
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => handleModalOpen('editApiList')}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => handleModalOpen('apiDtl')}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
onClick={() => handleModalOpen('selectDeployApi')}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{/* 根据 isModalOpen 渲染不同模态框 */}
{isModalOpen === 'editApiList' && (
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
onClose={handleModalClose} // 传递关闭模态框函数
/>
)}
{isModalOpen === 'apiDtl' && (
<NewAPIDtlModal
val={apiList[0]} // 确保传递有效的api对象
onClose={handleModalClose} // 传递关闭模态框函数
/>
)}
{isModalOpen === 'selectDeployApi' && (
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
onClose={handleModalClose} // 传递关闭模态框函数
/>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select;
setTags([...tags]);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
function transpose(a: any[]) {
return a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
}
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
const [isEditApiListModalOpen, setIsEditApiListModalOpen] = useState(false);
const [isApiDtlModalOpen, setIsApiDtlModalOpen] = useState(false);
const [isSelectDeployApiModalOpen, setIsSelectDeployApiModalOpen] = useState(false);
const [isSampleDataModalOpen, setIsSampleDataModalOpen] = useState(false);
<button
className="btn btn-secondary"
onClick={() => {
setIsSampleDataModalOpen(true);
}}
>
サンプルデータ挿入 / データセット連携項目追加
</button>
{isSampleDataModalOpen && (
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
onClose={() => setIsSampleDataModalOpen(false)} // 添加关闭模态的回调
/>
)}
{isEditApiListModalOpen && (
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
onClose={() => setIsEditApiListModalOpen(false)} // 添加关闭模态的回调
/>
)}
{isApiDtlModalOpen && (
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
onClose={() => setIsApiDtlModalOpen(false)} // 添加关闭模态的回调
/>
)}
<button
className="btn btn-secondary"
onClick={() => setIsEditApiListModalOpen(true)}
>
編集
</button>
<button
className="btn btn-primary"
onClick={() => setIsApiDtlModalOpen(true)}
>
追加
</button>
<button
className="btn-long-text btn-primary"
onClick={() => setIsSelectDeployApiModalOpen(true)}
>
一括有効化
</button>
function NewSampleDataModal({ id, logicalName, onClose }) {
return (
<div className="modal">
<div className="modal-content">
<h2>{logicalName}</h2>
<button onClick={onClose}>閉じる</button> {/* 添加关闭按钮 */}
</div>
</div>
);
}
const [isSampleDataModalOpen, setIsSampleDataModalOpen] = useState(false);
<button
className="btn btn-secondary"
onClick={() => setIsSampleDataModalOpen(true)} // 打开模态窗口
>
サンプルデータ挿入 / データセット連携項目追加
</button>
{isSampleDataModalOpen && (
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
onClose={() => setIsSampleDataModalOpen(false)} // 关闭模态窗口
/>
)}
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
function NewApiList() {
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
const filteredList = apiList.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
// 添加日志查看过滤后的 API 列表
useEffect(() => {
console.log("Filtered API list after tag selection:", filteredList);
}, [tagList, apiList]);
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
onClick={() => setIsModalOpen(true)}
>
追加
</button>
</div>
)}
</section>
<section className="content">
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{filteredList.length === 0 ? (
<p>没有匹配的API,请选择其他标签。</p>
) : (
viewList(
filteredList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<NewSampleDataModal
id={'sample-data'}
logicalName={'Sample Data'}
onClose={() => setIsModalOpen(false)}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
export default NewApiList;
import $ from 'jquery'; // 使用 jQuery 以便于折叠操作
useEffect(() => { // 每次 tagList 发生变化时折叠所有展开的部件 $('.collapse').collapse('hide'); }, [tagList]);
const [expandedCards, setExpandedCards] = useState<{ [key: string]: boolean }>({});
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
function NewApiList() {
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
// 新增:用于控制每个API卡片的折叠状态
const [expandedCards, setExpandedCards] = useState<{ [key: string]: boolean }>({});
// API 情報取得のリクエスト
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
// 监听标签变化时重置所有卡片的展开状态为收起
useEffect(() => {
setExpandedCards({});
}, [tagList]);
const toggleCard = (apiName: string) => {
setExpandedCards((prev) => ({
...prev,
[apiName]: !prev[apiName], // 切换展开状态
}));
};
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
<section className="content-header">
{/* 省略其他代码 */}
</section>
<section className="content">
{/* 过滤标签 */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API 列表 (网格) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
expandedCards, // 传递展开状态
toggleCard // 传递展开切换函数
)}
</section>
{/* 其他代码保持不变 */}
</div>
);
}
// 更新 viewList 函数,接收 expandedCards 和 toggleCard 参数
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
expandedCards: { [key: string]: boolean }, // 传递的展开状态
toggleCard: (apiName: string) => void // 传递的切换函数
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag)
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3))
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
expandedCards[val.physical_name], // 使用展开状态
toggleCard // 传递切换函数
),
)}
</div>
))}
</div>
);
}
// 更新 ApiCard 函数,接收 expanded 和 toggleCard 参数
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
expanded: boolean, // 接收的展开状态
toggleCard: (apiName: string) => void // 接收的切换函数
) {
const selected = tagList.some((tag) =>
api.tags.includes(tag.tag_name) && tag.select
);
return (
<div
className={`card ${selected ? 'card-primary' : 'card-not-applicable'}`}
>
<div
className="card-header"
onClick={() => toggleCard(api.physical_name)} // 点击切换展开状态
>
{api.physical_name} / {api.logical_name}
</div>
<div className={`card-body ${expanded ? 'show' : 'collapse'}`}>
{/* 卡片内容 */}
</div>
</div>
);
}
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// Filter the API list based on selected tags
const filteredList = item.filter((api) => {
// If no tags are selected, show all
const isAnyTagSelected = tagList.some((tag) => tag.select);
if (!isAnyTagSelected) return true;
// Show only the APIs that match at least one selected tag
return api.tags.some((apiTag) =>
tagList.some((tag) => tag.select && tag.tag_name === apiTag),
);
});
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length !== 0) {
listItem = transpose(
new Array(length)
.fill(0)
.map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
{listItem.map((column: Defs.Api[], columnIndex: number) => (
<div className="apiList" key={columnIndex}>
{column.map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)}
</div>
))}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// タグが1つでも選択されているか確認
const isAnyTagSelected = tagList.some((tag) => tag.select);
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag === item.tag_name) {
if (!isAnyTagSelected || item.select) {
// タグが未選択の時、全て選択と同じ扱いにする
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { commonAjax } from '../../../components/commonAjax';
import { Spacer } from '../spacer';
import * as Defs from './apiDefs';
import ApiUpdateResultModal from './apiUpdateResultModal';
import CreateNewApi from './createNewApi';
import { DialogModalOK, hideDialog, showDialog } from './dialogModal';
import EditApiList from './editApiList';
import NewAPIDtlModal from './newAPIDtlModal';
import NewSampleDataModal from './newSampleDataModal';
import SelectDeployApi from './selectDeployApi';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: true });
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// fillter tag
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => {
if (item.select) {
return (
<div
className="btn btn-tag active"
key={item.tag_name}
onClick={() => {
item.select = false;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
}
return (
<div
className="btn btn-tag"
onClick={() => {
item.select = true;
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
);
})}
</div>
);
}
function viewList(
item: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// if(item.length){
const length = Math.ceil(item.length / 3);
let listItem: any = [];
if (length != 0) {
listItem = transpose(
new Array(length).fill(0).map((_, i) => item.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
<div className="apiList">
{0 >= 0 && listItem.length > 0
? listItem[0].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{1 >= 0 && listItem.length > 1
? listItem[1].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{2 >= 0 && listItem.length > 2
? listItem[2].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
</div>
);
// }
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
tagList.map((item: Defs.Tag) => {
api.tags.map((apiTag: string) => {
if (apiTag == item.tag_name) {
if (item.select) {
selected = true;
}
}
});
});
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: undefined
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = [];
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>,
);
});
return res;
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`}
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
// transpose columns and rows
const transpose = (a: any[]) =>
a[0].map((_: any, c: string | number) => a.map((r) => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(
api: Defs.Api,
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
setDeployJobId: any,
) {
if (api) {
if (!api.provide) {
(async () => {
const errorMsgList: { [key: string]: string } = {
[api.physical_name]: '',
};
commonAjax
.axios({ swalFire: false, loading: true })
.put('/api/api/deploy', { id: [api.physical_name] })
.then((res) => {
if (res.data.errorMsg != null) {
const { errorMsg } = res.data;
console.log({ errorMsg });
errorMsgList[api.physical_name] = errorMsg;
setErrorMsg(errorMsgList);
} else {
setChangeStatus(!changeStatus);
setErrorMsg(errorMsgList);
setDeployJobId(res.data.jobId);
}
});
})();
}
}
}
function ApiDeployJobStartNotification({ jobId }: { jobId?: string }) {
return (
<DialogModalOK
id="api-deploy-job-start-notification"
title="処理を開始しました"
>
<p>
データセット有効化処理を開始しました。
<br />
完了には時間がかかりますのでお待ちください。
<br />
処理の状況は下記画面で確認できます。
</p>
<Link
to={`/jobs/${jobId}`}
className="btn btn-secondary"
target="_blank"
onClick={() => hideDialog('api-deploy-job-start-notification')}
>
ジョブステータス確認
</Link>
</DialogModalOK>
);
}
export default NewApiList;
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false }); // 初期状态设为 false
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// filter tags 逻辑修改
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select; // 切换选择状态
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
// 过滤显示逻辑修改
function viewList(
apiList: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let filteredList = apiList;
// 检查是否有选中的标签
const selectedTags = tagList.filter((tag) => tag.select).map((tag) => tag.tag_name);
// 如果有选中的标签,按标签过滤API列表
if (selectedTags.length > 0) {
filteredList = apiList.filter((api) =>
api.tags.some((tag) => selectedTags.includes(tag)),
);
}
// 保留原有的 list 渲染逻辑
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length != 0) {
listItem = transpose(
new Array(length).fill(0).map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
<div className="apiList">
{0 >= 0 && listItem.length > 0
? listItem[0].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{1 >= 0 && listItem.length > 1
? listItem[1].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{2 >= 0 && listItem.length > 2
? listItem[2].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
</div>
);
}
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// 确保每个 API 项目都检查它的 tags,并根据所选 tags 决定是否显示
tagList.forEach((item: Defs.Tag) => {
api.tags.forEach((apiTag: string) => {
if (apiTag === item.tag_name && item.select) {
selected = true;
}
});
});
// 确保未被选中时,仍可以打开 modal
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: ''
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
return val.column.map((item: Defs.Column) => (
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>
));
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`} // 确保 modal 的 target id 绑定正确
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{/* Modal 部分 */}
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false }); // 初期设为 false
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// filter tags 逻辑修改
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select; // 切换选择状态
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
// 过滤显示逻辑修改
function viewList(
apiList: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
// 先不做过滤,直接显示所有 API
let filteredList = apiList;
// 检查是否有选中的标签
const selectedTags = tagList.filter((tag) => tag.select).map((tag) => tag.tag_name);
// 如果有选中的标签,按标签过滤API列表
if (selectedTags.length > 0) {
filteredList = apiList.filter((api) =>
api.tags.some((tag) => selectedTags.includes(tag)),
);
}
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length != 0) {
listItem = transpose(
new Array(length).fill(0).map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
<div className="apiList">
{0 >= 0 && listItem.length > 0
? listItem[0].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{1 >= 0 && listItem.length > 1
? listItem[1].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{2 >= 0 && listItem.length > 2
? listItem[2].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// 确保每个 API 项目都检查它的 tags,并根据所选 tags 决定是否显示
tagList.forEach((item: Defs.Tag) => {
api.tags.forEach((apiTag: string) => {
if (apiTag === item.tag_name && item.select) {
selected = true;
}
});
});
// 确保未被选中时,仍可以打开 modal
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: ''
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
return val.column.map((item: Defs.Column) => (
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>
));
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`} // 确保 modal 的 target id 绑定正确
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
{/* Modal 部分 */}
<NewAPIDtlModal val={api} />
<NewSampleDataModal
id={api.physical_name}
logicalName={api.logical_name}
/>
<ApiUpdateResultModal val={api} />
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
const [enableDplApi, setEnableDplApi] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<{}>({});
const [deployJobId, setDeployJobId] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({ swalFire: false, loading: true })
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = [];
let tmpTags: string[] = [];
data.map((item: any) => {
tmpTags = tmpTags.concat(item.tags);
});
tmpTags = Array.from(new Set(tmpTags));
tmpTags.map((t: string) => {
resTags.push({ tag_name: t, select: false }); // 初期设为 false
});
setApiList(data);
setTags(resTags);
setEnableDplApi(res.data.enableDplApi);
});
})();
}, [changeStatus]);
useLayoutEffect(() => {
if (deployJobId) {
// ジョブ開始ダイアログ表示
showDialog('api-deploy-job-start-notification');
}
}, [deployJobId]);
const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
const title = `${metricsProcess} データセット一覧`;
return (
<div className="content-wrapper api-list">
<section className="page-cover">
<h1>{title}</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>{title}</h1>
<div className="content-header-desc">
現在、設定されているデータセット一覧です
</div>
</div>
{enableDplApi && (
<div className="content-header-right">
<button
type="button"
className="btn btn-secondary"
data-toggle="modal"
data-target="#editApiListModal"
onClick={() => {
setIsModalOpen(true);
}}
>
編集
</button>
<button
type="button"
className="btn btn-primary"
data-toggle="modal"
data-target="#api-dtl-modal"
onClick={() => {
setIsModalOpen(true);
}}
>
追加
</button>
<button
type="button"
className="btn-long-text btn-primary"
data-toggle="modal"
data-target="#selectDeployApiModal"
onClick={() => {
setIsModalOpen(true);
}}
>
一括有効化
</button>
</div>
)}
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({ size: 30 })}
{/* API List (Grid) */}
{viewList(
apiList,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
)}
</section>
{isModalOpen && (
<>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<SelectDeployApi
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</>
)}
<ApiDeployJobStartNotification jobId={deployJobId} />
</div>
);
}
// filter tags 逻辑修改
function FilterTags(tags: Defs.Tag[], setTags: any) {
return (
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item: Defs.Tag) => (
<div
className={`btn btn-tag ${item.select ? 'active' : ''}`}
key={item.tag_name}
onClick={() => {
item.select = !item.select; // 切换选择状态
const t = tags.slice(0, tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
))}
</div>
);
}
// 过滤显示逻辑修改
function viewList(
apiList: Defs.Api[],
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let filteredList = apiList;
// 检查是否有选中的标签
const selectedTags = tagList.filter((tag) => tag.select).map((tag) => tag.tag_name);
// 如果有选中的标签,按标签过滤API列表
if (selectedTags.length > 0) {
filteredList = apiList.filter((api) =>
api.tags.some((tag) => selectedTags.includes(tag)),
);
}
// 渲染所有 Modal 保证初始化
const modals = apiList.map((val) => (
<>
<NewAPIDtlModal val={val} />
<NewSampleDataModal
id={val.physical_name}
logicalName={val.logical_name}
/>
<ApiUpdateResultModal val={val} />
</>
));
const length = Math.ceil(filteredList.length / 3);
let listItem: any = [];
if (length != 0) {
listItem = transpose(
new Array(length).fill(0).map((_, i) => filteredList.slice(i * 3, (i + 1) * 3)),
);
}
return (
<div className="list-wrapper">
<div className="apiList">
{0 >= 0 && listItem.length > 0
? listItem[0].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{1 >= 0 && listItem.length > 1
? listItem[1].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
<div className="apiList">
{2 >= 0 && listItem.length > 2
? listItem[2].map((val: Defs.Api) =>
ApiCard(
val,
tagList,
changeStatus,
setChangeStatus,
setErrorMsg,
errorMsg,
setDeployJobId,
),
)
: null}
</div>
{/* 渲染所有 Modal */}
{modals}
</div>
);
}
// API Card
function ApiCard(
api: Defs.Api,
tagList: Defs.Tag[],
changeStatus: boolean,
setChangeStatus: any,
setErrorMsg: any,
errorMsg: any,
setDeployJobId: any,
) {
let selected = false;
// 确保每个 API 项目都检查它的 tags,并根据所选 tags 决定是否显示
tagList.forEach((item: Defs.Tag) => {
api.tags.forEach((apiTag: string) => {
if (apiTag === item.tag_name && item.select) {
selected = true;
}
});
});
// 确保未被选中时,仍可以打开 modal
if (!errorMsg) {
$(`#${api.physical_name}`).collapse('hide');
}
return (
<div
className={`card ${
selected ? 'card-primary' : 'card-not-applicable not-applicable'
} card-collapse-sample`}
>
<div
className="card-header collapsed"
data-toggle="collapse"
data-target={`#card-body-${api.physical_name}`}
>
<div
className="api-name-font"
style={{
fontSize: `clamp(0.6rem, ${
29 / `${api.physical_name} / ${api.logical_name}`.length
}vw, 1rem)`,
}}
>
<i
className={`fa fa-circle provide-icon-size ${
selected
? api.provide
? 'provide-icon-color'
: 'no-provide-icon-color'
: ''
}`}
aria-hidden="true"
/>
{Spacer({ size: 10, horizontal: true })}
{`${api.physical_name} / ${api.logical_name}`}
</div>
</div>
<div className="card-body collapse" id={`card-body-${api.physical_name}`}>
<div className="api-list-title">
<p>データセット 連携項目 一覧</p>
{Spacer({ size: 20 })}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
return val.column.map((item: Defs.Column) => (
<div className="list-contents-grid" key={item.physicalName}>
<p>{item.logicalName}</p>
</div>
));
})}
{Spacer({ size: 20 })}
<div className="text-danger error-msg">
{errorMsg[api.physical_name]}
</div>
<br />
{api.provide ? (
<>
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={`#${api.physical_name}-dtl-modal`} // 确保 modal 的 target id 绑定正确
data-backdrop="true"
>
サンプルデータ挿入 / データセット連携項目追加
</button>
</div>
</>
) : (
<div className="btn-center">
<button
className="btn btn-secondary btn-secondary"
onClick={() => {
putDeploy(
api,
changeStatus,
setChangeStatus,
setErrorMsg,
setDeployJobId,
);
}}
>
有効化
</button>
</div>
)}
</div>
</div>
);
}