kirin-ri / memo

0 stars 0 forks source link

2212 #21

Open kirin-ri opened 7 months ago

kirin-ri commented 7 months ago
from pyspark.sql import SparkSession
from pyspark.sql.functions import col
from pyspark.sql.types import DoubleType, DecimalType
def convert_double_to_decimal(df, precision=38, scale=10):
    # DataFrameのスキーマを調べて、データ型がDoubleTypeのカラムを見つける
    double_columns = [field.name for field in df.schema.fields if isinstance(field.dataType, DoubleType)]

    # 各Double型のカラムをDecimal型にキャストする
    for column in double_columns:
        df = df.withColumn(column, col(column).cast(DecimalType(precision, scale)))

    return df

# DataFrameを読み込む(例)
# df = spark.read.load("your_data_path")

# Double型のカラムをDecimal型に変換
df = convert_double_to_decimal(df)
kirin-ri commented 7 months ago
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-web-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: catalog-web-app
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  minReadySeconds: 5
  template:
    metadata:
      labels:
        app: catalog-web-app
    spec:
      nodeSelector:
        'beta.kubernetes.io/os': linux
      containers:
        - name: catalog-web-app
          image: idnaacr.azurecr.io/catalog-web-app:latest
          ports:
            - containerPort: 5000
          resources:
            requests:
              cpu: 50m
            limits:
              cpu: 500m
          env:
            - name: AAD_CLIENT_ID
              value: 'd69ba062-cb62-440c-bf7d-4cda629c4e97'
            - name: AAD_CLIENT_SECRET
              value: 'iUG8Q~xoZNeyaaW023kMVyV4wmdJOQHl1AcDzcmW'
            - name: AAD_TENANT_ID
              value: '17a6323e-2beb-40b3-817b-97ae9fad5804'
            - name: ACCESS_HISTORY_LIMIT_DAYS
              value: '14'
            - name: ARANGO_URL
              value: 'https://4.190.18.243:8529/'
            - name: ARANGO_DB
              value: 'INDUSTRIAL_DNA_DB'
            - name: ARANGO_USER
              value: 'ind-arangodb'
            - name: ARANGO_PWD
              value: 'uH7yYk6|'
            - name: BASE_PROJECT
              value: 'startpack'
            - name: CHAT2QUERY_API_KEY
              value: 'asx28iyo4BtqvAK1JG8kMr569zdDbKfse31M5HFx24lOOzBBB0inwk3KLbBCxKFEl'
            - name: CHAT2QUERY_TRIES
              value: '1'
            - name: CHAT2QUERY_URL
              value: 'https://data.tidbcloud.com/api/v1beta/apps/chat2query-uoeZOQoN/v1/chat2data'
            - name: DATABRICKS_TOKEN
              value: 'dapi3104aa04027fefcdbd531acf6cc01839'
            - name: DATABRICKS_URL
              value: 'https://adb-5718025460118174.14.azuredatabricks.net'
            - name: DPB_APP_URI
              value: 'http://10.128.1.4:8000'
            - name: DPB_RETRY_DELAY
              value: '10'
            - name: DPB_RETRY_TRIES
              value: '300'
            - name: DPB_TIMEOUT
              value: '30'
            - name: DPB_USER
              value: 'ind-user'
            - name: DPB_USER_DATABASE
              value: 'INDUSTRIAL_DNA_DB'
            - name: DPB_USER_SCHEMA
              value: 'INDUSTRIAL_DNA_SCHEMA'
            - name: DPL_HOST
              value: 'startpack.dev.ind.prd.is.a.i4square.info'
            - name: ENABLE_CREATING_METRICS
              value: 'true'
            - name: ENABLE_DPL_API
              value: 'true'
            - name: ENV
              value: 'dev'
            - name: FAVORITES_MAX_ITEMS
              value: '10'
            - name: FLASK_APP
              value: 'catalog-web-app'
            - name: GIT_BRANCH
              value: 'master'
            - name: GIT_BRANCH_CATALOG_AP
              value: 'master'
            - name: GIT_BRANCH_SCM_METRICS
              value: 'master'
            - name: GIT_EMAIL
              value: 'ind-dpb@example.com'
            - name: GIT_ORGANIZATION
              value: 'qmonus-test'
            - name: GIT_REPOSITORY
              value: 'scm-metrics'
            - name: GIT_TOKEN
              value: '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'
            - name: GIT_USER
              value: 'FumiyaOkoshi'
            - name: JENKINS_HOST
              value: '20.48.61.168:4000'
            - name: JENKINS_PWD
              value: 'V3f8QuT9'
            - name: JENKINS_RETRY_DELAY
              value: '10'
            - name: JENKINS_RETRY_TRIES
              value: '300'
            - name: JENKINS_USER
              value: 'AdminUser'
            - name: PROJECT
              value: 'startpack'
            - name: SNOW_ACCOUNT
              value: 'ld50475.ap-northeast-1.aws'
            - name: SNOW_DB
              value: 'INDUSTRIAL_DNA_DB'
            - name: SNOW_PWD
              value: 'Fjt807Gnjkly'
            - name: SNOW_ROLE
              value: 'SYSADMIN'
            - name: SNOW_SCHEMA
              value: 'INDUSTRIAL_DNA_SCHEMA'
            - name: SNOW_USER
              value: 'ACADMI4IND'
            - name: SNOW_WH
              value: 'TEST_WH'
            - name: TENANT
              value: 'ind'
            - name: TO_PATH
              value: 'tmp/i4-dpb'
      hostAliases:
        - hostnames:
            - startpack.dev.ind.prd.is.a.i4square.info # DNA DPL
          ip: 4.216.131.132
---
apiVersion: v1
kind: Service
metadata:
  name: catalog-web-app
spec:
  # loadBalancerIP: 20.48.118.80
  type: LoadBalancer
  ports:
    - name: backend-port
      port: 80
      targetPort: 5000
      protocol: TCP
  selector:
    app: catalog-web-app
  loadBalancerSourceRanges:
    # daas and git and webhook ip
    - 20.48.61.168/32
    # aks ip
    # - 40.81.216.40/32
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: catalog-web-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/whitelist-source-range: '20.48.61.168/32'
spec:
  tls:
    - hosts:
        - catalog.mock.i-dna.i4square.net
      secretName: kvc-ind-ssl-cert
  ingressClassName: nginx
  rules:
    - host: catalog.mock.i-dna.i4square.net
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: catalog-web-app
                port:
                  number: 80
kirin-ri commented 7 months ago
// ConfigContext.tsx
import React, { createContext, useState, useEffect, useContext } from 'react';
import { Config } from './types';

interface ConfigContextType {
  config: Config | null;
}

const ConfigContext = createContext<ConfigContextType | null>(null);

export const ConfigProvider: React.FC = ({ children }) => {
  const [config, setConfig] = useState<Config | null>(null);

  useEffect(() => {
    fetch('/api/config')
      .then(response => response.json())
      .then((data: Config) => {
        setConfig(data);
      })
      .catch(error => {
        console.error('Error fetching config:', error);
      });
  }, []);

  return (
    <ConfigContext.Provider value={{ config }}>
      {children}
    </ConfigContext.Provider>
  );
};

export const useConfig = () => {
  const context = useContext(ConfigContext);
  if (!context) {
    throw new Error('useConfig must be used within a ConfigProvider');
  }
  return context.config;
};
kirin-ri commented 7 months ago
// ConfigContext.tsx
import React, { createContext, useState, useEffect, useContext } from 'react';

// 定义配置类型接口
export interface Config {
    aadClientId: string;
}

// 定义上下文类型接口
interface ConfigContextType {
  config: Config | null;
}

// 创建上下文
const ConfigContext = createContext<ConfigContextType | null>(null);

// 提供者组件
export const ConfigProvider: React.FC = ({ children }) => {
  const [config, setConfig] = useState<Config | null>(null);

  useEffect(() => {
    fetch('/api/config')
      .then(response => response.json())
      .then((data: Config) => {
        setConfig(data);
      })
      .catch(error => {
        console.error('Error fetching config:', error);
      });
  }, []);

  return (
    <ConfigContext.Provider value={{ config }}>
      {children}
    </ConfigContext.Provider>
  );
};

// 自定义钩子
export const useConfig = () => {
  const context = useContext(ConfigContext);
  if (!context) {
    throw new Error('useConfig must be used within a ConfigProvider');
  }
  return context.config;
};
kirin-ri commented 7 months ago

プロパティ 'children' は型 '{}' に存在しません。

kirin-ri commented 7 months ago
  const systemDemo = process.env.REACT_APP_DEMO_PROCESS;
  const title = `${systemDemo} メトリクス一覧`

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  {title}
                </Link>
kirin-ri commented 7 months ago
'use strict';

const fs = require('fs');
const path = require('path');
const paths = require('./paths');

// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')];

const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
  throw new Error(
    'The NODE_ENV environment variable is required but was not specified.'
  );
}

// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
  `${paths.dotenv}.${NODE_ENV}.local`,
  // Don't include `.env.local` for `test` environment
  // since normally you expect tests to produce the same
  // results for everyone
  NODE_ENV !== 'test' && `${paths.dotenv}.local`,
  `${paths.dotenv}.${NODE_ENV}`,
  paths.dotenv,
].filter(Boolean);

// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.  Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
  if (fs.existsSync(dotenvFile)) {
    require('dotenv-expand')(
      require('dotenv').config({
        path: dotenvFile,
      })
    );
  }
});

// We support resolving modules according to `NODE_PATH`.
// This lets you use absolute paths in imports inside large monorepos:
// https://github.com/facebook/create-react-app/issues/253.
// It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '')
  .split(path.delimiter)
  .filter(folder => folder && !path.isAbsolute(folder))
  .map(folder => path.resolve(appDirectory, folder))
  .join(path.delimiter);

// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in webpack configuration.
const REACT_APP = /^REACT_APP_/i;

function getClientEnvironment(publicUrl) {
  const raw = Object.keys(process.env)
    .filter(key => REACT_APP.test(key))
    .reduce(
      (env, key) => {
        env[key] = process.env[key];
        return env;
      },
      {
        // Useful for determining whether we’re running in production mode.
        // Most importantly, it switches React into the correct mode.
        NODE_ENV: process.env.NODE_ENV || 'development',
        // Useful for resolving the correct path to static assets in `public`.
        // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
        // This should only be used as an escape hatch. Normally you would put
        // images into the `src` and `import` them in code to get their paths.
        PUBLIC_URL: publicUrl,
        // We support configuring the sockjs pathname during development.
        // These settings let a developer run multiple simultaneous projects.
        // They are used as the connection `hostname`, `pathname` and `port`
        // in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
        // and `sockPort` options in webpack-dev-server.
        WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
        WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
        WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
        // Whether or not react-refresh is enabled.
        // It is defined here so it is available in the webpackHotDevClient.
        FAST_REFRESH: process.env.FAST_REFRESH !== 'false',
      }
    );
  // Stringify all values so we can feed into webpack DefinePlugin
  const stringified = {
    'process.env': Object.keys(raw).reduce((env, key) => {
      env[key] = JSON.stringify(raw[key]);
      return env;
    }, {}),
  };

  return { raw, stringified };
}

module.exports = getClientEnvironment;
kirin-ri commented 7 months ago
API_META_INFO
28 Rows
API_PHYSICAL_NAME
VARCHAR(16777216)
API_LOGICAL_NAME
VARCHAR(16777216)
PROVIDE
BOOLEAN
API_LINKAGE_ITEMS
VARIANT
TAGS
VARIANT
PRESET
kirin-ri commented 7 months ago

データセット 良品ショット数 稼働時間 段替え時間 段替え回数(日次) 段替え回数(月次) 良品数(手直し含む) MT

(マシンタイム) MCT

(マシンサイクルタイム) CT

(サイクルタイム) 良品加工時間 稼働日数 空回し回数 ATT

:Average Time To Troubleshoot 設備停止時間 可動時間 廃品数 生産数 手直し不良数 工程内不良数 実績生産時間 基準時間 正味人工 配置人数 OPTリリーフ 正味作業工数 時間当たりの生産数 作業者数 良品数(手直し含まず) 出庫数 平均在庫数 故障回数 設備有効性 工程効率 良品率 実働時間 実時間 計画していた作業者数 排出係数 活動量

kirin-ri commented 7 months ago

INSERT INTO API_META_INFO (API_PHYSICAL_NAME, PROVIDE, TAGS, PRESET) VALUES ('良品ショット数', FALSE, PARSE_JSON('[]'), TRUE);

kirin-ri commented 7 months ago

Invalid expression [PARSE_JSON('[]')] in VALUES clause

kirin-ri commented 7 months ago

Error: invalid identifier 'API_LOGICAL_NAME' (line 4)

kirin-ri commented 7 months ago

INSERT INTO IND_ECMDEMO_DEV_DB.IND_ECMDEMO_DEV_SCHEMA.API_META_INFO(API_LINKAGE_ITEMS) select PARSE_JSON('[]') where API_LOGICAL_NAME = '良品ショット数'

kirin-ri commented 7 months ago

API_PHYSICAL_NAME API_LOGICAL_NAME PROVIDE API_LINKAGE_ITEMS TAGS PRESET quality_shot_number 良品ショット数 FALSE [] [] TRUE

kirin-ri commented 7 months ago

INSERT INTO API_META_INFO (API_PHYSICAL_NAME, API_LOGICAL_NAME, PROVIDE, API_LINKAGE_ITEMS, TAGS, PRESET) VALUES ('operation_time', '稼働時間', FALSE, PARSE_JSON('[]'), PARSE_JSON('[]'), TRUE);

kirin-ri commented 7 months ago
良品ショット数: quality_shot_count
稼働時間: operation_time
段替え時間: changeover_time
段替え回数(日次): daily_changeover_count
段替え回数(月次): monthly_changeover_count
良品数(手直し含む): quality_product_including_repairs
MT(マシンタイム): machine_time
MCT(マシンサイクルタイム): machine_cycle_time
CT(サイクルタイム): cycle_time
良品加工時間: quality_processing_time
稼働日数: operating_days
空回し回数: idle_run_count
ATT:Average Time To Troubleshoot: average_time_to_troubleshoot
設備停止時間: equipment_downtime
可動時間: operating_time
廃品数: waste_count
生産数: production_number
手直し不良数: rework_defect_count
工程内不良数: in_process_defect_count
実績生産時間: actual_production_time
基準時間: standard_time
正味人工: net_labor
配置人数: staff_count
OPTリリーフ: opt_relief
正味作業工数: net_working_hours
時間当たりの生産数: production_per_hour
作業者数: worker_count
良品数(手直し含まず): quality_product_excluding_repairs
出庫数: dispatch_count
平均在庫数: average_inventory
故障回数: breakdown_count
設備有効性: equipment_effectiveness
工程効率: process_efficiency
良品率: yield_rate
実働時間: actual_working_time
実時間: actual_time
計画していた作業者数: planned_worker_count
排出係数: emission_factor
活動量: activity_volume
kirin-ri commented 7 months ago
import { useEffect, useRef } from 'react';
import { HashRouter, Route, useLocation } from 'react-router-dom';
import { commonAjax } from '../components/commonAjax';
import AppHeader from './components/block/appHeader';
import AppMenu from './components/block/appMenu';
import routes from './routes/routes';

function App() {
  const location = useLocation();
  const lastAccessHash = useRef<String>('');

  useEffect(() => {
    if (location.hash !== lastAccessHash.current) {
      recordAccess(location as unknown as Location);
      lastAccessHash.current = location.hash;
    }
  }, [location.hash]);

  return <MainComponent />;
}

function recordAccess(location: Location) {
  // メトリクス詳細画面
  const matches = /#\/metrics-details\/(.+)/.exec(location.hash);
  if (matches) {
    const id = decodeURIComponent(matches[1]);
    commonAjax.axios().put('/api/recordAccess', {
      url: `/metrics-details/${id}`,
      disp: id,
    });
  }
}

function MainComponent() {
  return (
    <div className="wrapper">
      <AppHeader />
      <HashRouter>
        <AppMenu />
        <div className="wrapper">
          {routes.map((route, idx) => (
            <Route
              path={route.path}
              exact={route.exact}
              component={route.component}
              key={idx}
            />
          ))}
        </div>
      </HashRouter>
      <div
        id="loading-spinner"
        style={{
          display: 'none',
          position: 'fixed',
          top: '0',
          left: '0',
          width: '100vw',
          height: '100vh',
          zIndex: 2000,
          backgroundColor: '#00000080',
          justifyItems: 'center',
          alignItems: 'center',
        }}
      >
        <div className="loading-spinner"></div>
      </div>
    </div>
  );
}

export default App;
``
kirin-ri commented 7 months ago
  useEffect(() => {
    // ルートに応じてタイトルを設定するための基本的なロジック
    const baseTitle = process.env.REACT_APP_SITE_TITLE || 'Default App Title';
    document.title = baseTitle + ' - ' + location.pathname.replace(/^\/|\/$/g, '');
  }, [location]);
kirin-ri commented 7 months ago
  if (!settings) {
    settings = {
      panels: [
        { id: PANEL_ID_UPDATE, order: 1 },
        { id: PANEL_ID_METRICS, order: 2 },
        { id: PANEL_ID_FAV, order: 3 },
      ],
      metricsPanel: {
        metricsId: '基準在庫週数',
      },
    };
    saveSettings(settings);
  }
kirin-ri commented 7 months ago
// systemdemoの値によってmetricsIdを変更する
let metricsId;
if (systemdemo === 'ecm') {
  metricsId = '基准在库';
} else if (systemdemo === 'scm') {
  metricsId = '基準在庫週数';
} else {
  metricsId = 'demo';
}

// settingsが未定義の場合、初期設定を行う
if (!settings) {
  settings = {
    panels: [
      { id: PANEL_ID_UPDATE, order: 1 },
      { id: PANEL_ID_METRICS, order: 2 },
      { id: PANEL_ID_FAV, order: 3 },
    ],
    metricsPanel: {
      metricsId: metricsId,  // 条件に基づいたmetricsIdを使用
    },
  };
  saveSettings(settings);
}
kirin-ri commented 6 months ago
Traceback (most recent call last):
  File "/app/tools/metricsUtils.py", line 397, in cloneMetricsRepo
    git.Repo.clone_from(url, path, multi_options=["-b", branch])
  File "/usr/local/lib/python3.9/site-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/git/repo/base.py", line 1169, in _clone
    multi = shlex.split(" ".join(multi_options))
TypeError: sequence item 1: expected str instance, NoneType found
Traceback (most recent call last):
  File "/app/tools/metricsUtils.py", line 397, in cloneMetricsRepo
    git.Repo.clone_from(url, path, multi_options=["-b", branch])
  File "/usr/local/lib/python3.9/site-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/git/repo/base.py", line 1169, in _clone
    multi = shlex.split(" ".join(multi_options))
TypeError: sequence item 1: expected str instance, NoneType found
kirin-ri commented 6 months ago
                {metrics.metrics.map((val) => {
                  return (
                    <a
                      href={`#/metrics-details/${val.name}`}
                      className="link"
                    >
                      <p>
                        <i
                          className={`fa fa-circle ${
                            val.provided
                              ? 'metrics-provide-icon'
                              : 'metrics-no-provide-icon'
                          }`}
                          aria-hidden="true"
                        />
                        &nbsp;
                        {val.name}
                      </p>
                    </a>
                  );
                })}
              </>
            }
kirin-ri commented 6 months ago
  /* API情報取得のリクエスト */
  useEffect(() => {
    (async () => {
      await commonAjax
        .axios({})
        .get('/api/metrics')
        .then((res) => {
          let data: Metrics[] = res.data.metrics;
          // ダミーデータ挿入
          // data = data.concat(dummyData);
          // サイクルを切り取るロジック
          // data = data.map((d) => {
          //   d.name = d.name.split(':')[0];
          //   return d;
          // });
          data = Array.from(new Map(data.map((d) => [d.name, d])).values());
          const group = data.reduce((result: MetricsList[], current) => {
            const element = result.find((p) => {
              return p.subCategory === current.subCategory;
            });
            if (element) {
              element.metrics.push(current);
            } else {
              result.push({
                subCategory: current.subCategory,
                metrics: [current],
              });
            }
            return result;
          }, []);
          setMetricsList(group);
          setEnableCreatingMetrics(res.data.enableCreatingMetrics);
        });
    })();
  }, []);
kirin-ri commented 6 months ago
  useEffect(() => {
    (async () => {
      await commonAjax
        .axios({})
        .get('/api/metrics')
        .then((res) => {
          let data: Metrics[] = res.data.metrics;
          // ダミーデータ挿入
          // data = data.concat(dummyData);
          // サイクルを切り取るロジック
          // data = data.map((d) => {
          //   d.name = d.name.split(':')[0];
          //   return d;
          // });
          data = Array.from(new Map(data.map((d) => [d.name, d])).values());
          data.sort((a,b) => a.name.localeCompare(b.name,'ja'))
          const group = data.reduce((result: MetricsList[], current) => {
            const element = result.find((p) => {
              return p.subCategory === current.subCategory;
            });
            if (element) {
              element.metrics.push(current);
            } else {
              result.push({
                subCategory: current.subCategory,
                metrics: [current],
              });
            }
            return result;
          }, []);
          setMetricsList(group);
          setEnableCreatingMetrics(res.data.enableCreatingMetrics);
        });
    })();
  }, []);
  const metricsProcess = process.env.REACT_APP_METRICS_PROCESS;
  const title = `${metricsProcess} メトリクス一覧`

  return (
    <div className="content-wrapper metrics-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>
        </section>
      {/* Metrics List (Grid) */}
      {Spacer({ size: 50 })}
      {view(props as any)}
    </div>
  );
}

// API List
function GridMetricsList(props: MetricsListProps) {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
}

// Metrics Card
function MetricsCard(props: MetricsListProps, metrics: MetricsList) {
  return (
    <>
      <div>
        <div className="card card-tertiary card-collapse-metrics">
          <div className="card-header">
            <div
              className="name-font"
              style={{
                fontSize: `clamp(0.65rem, ${
                  30 / metrics.subCategory.length
                }vw, 1rem)`,
              }}
            >
              {metrics.subCategory}
            </div>
          </div>
          <div className="card-body" id={metrics.subCategory}>
            {
              <>
                {metrics.metrics.map((val) => {
                  return (
                    <a
                      href={`#/metrics-details/${val.name}`}
                      className="link"
                    >
                      <p>
                        <i
                          className={`fa fa-circle ${
                            val.provided
                              ? 'metrics-provide-icon'
                              : 'metrics-no-provide-icon'
                          }`}
                          aria-hidden="true"
                        />
                        &nbsp;
                        {val.name}
                      </p>
                    </a>
                  );
                })}
              </>
            }
            {props.enableCreatingMetrics && (
              <>
                {Spacer({ size: 20 })}
                <div className="centering-btn">
                  <button
                    className="btn btn-secondary"
                    data-toggle="modal"
                    data-target="#metrics-dtlModal"
                    value={metrics.subCategory}
                  >
                    メトリクスを追加する +
                  </button>
                </div>
              </>
            )}
          </div>
        </div>
        <NewMetricsDtlModal/>
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <>
        <section className="content">
          <div className="list-wrapper">
            {GridMetricsList(props)}
          </div>
        </section>
      </>
    );
  }
};
kirin-ri commented 6 months ago

当然可以。这里是如何用日语描述您的原始代码状态和修改后的状态:

現在の状態の説明:
現在のコードでは、APIから取得したデータは特定の順序でソートされていません。データはAPIからどのように返されるかに基づいています。このため、表示されるデータの順序はAPIの応答の内容とその順序に依存しており、明確なソート処理は行われていないため、ユーザーインターフェースに表示されるデータの順序は一貫性がない可能性があります。

改良後の状態の説明:
提案する改良では、データをAPIから取得した後、重複を削除し、各サブカテゴリーに基づいてデータをグループ化します。その後、各グループ内のmetricsの数に基づいてグループをソートします。このソートは、metricsの数が多いグループがリストの上部に来るように行われます。これにより、より多くのデータが含まれているカテゴリーをユーザーがすぐに認識できるようになり、ユーザーインターフェースの使いやすさが向上します。

この説明をもとに、上司への報告やプレゼンテーションを行うと、現在のコードの動作と提案された改良点が明確に伝わります。

User
并且说明,为了泛用性,不从api取得里进行修改
ChatGPT
追加説明:API側での修正を行わない理由
APIからデータを取得する際に、そのソート順序を変更しない選択をした理由は、システムの汎用性を高めるためです。API側でデータの順序を固定すると、特定のビューや要件に対して最適化されますが、それがすべての使用シナリオや将来の要件に適合するとは限りません。

フロントエンドでデータを操作してソートするアプローチを採用することで、同一のAPI応答を異なるビューやコンテキストに柔軟に対応させることが可能になります。例えば、あるビューではmetricsの数が多い順に表示する必要があり、別のビューではアルファベット順や作成日時順での表示が求められるかもしれません。

このように、フロントエンドでのデータ処理を選ぶことは、アプリケーションの再利用性と拡張性を保ちながら、異なるユーザー要件に柔軟に対応できる設計を可能にします。APIは可能な限りシンプルに保ち、データの提供に集中することで、システム全体の複雑さを避け、メンテナンスの容易さやパフォーマンスの最適化を図ることができます。
kirin-ri commented 6 months ago
現在のコードでは、APIから取得したカテゴリは特定の順序でソートされていません。データはAPIからどのように返されるかに基づいています。

API側でデータの順序を固定すると、今回の要件に対してだけ最適化されます。
カテゴリの位置を変えるため、各カテゴリ内のメトリクスの数に基づいてソートするように改修しました。メトリクス数が多い順カテゴリが上部に来るように行われます。メトリクス一覧画面の汎用性を高めるため、上記の対応でいかがでしょうか。