次の方法で共有


リモート監視ソリューション アクセラレータの Web UI にカスタム サービスを追加する

この記事では、リモート監視ソリューション アクセラレータの Web UI に新しいサービスを追加する方法について説明します。 この記事では、次の内容について説明します。

  • ローカルの開発環境を準備する方法。
  • Web UI に新しいサービスを追加する方法。

この記事で使用するサービスの例では、「Add a custom grid to the Remote Monitoring solution accelerator web UI」(リモート監視ソリューション アクセラレータ Web UI にカスタム グリッドを追加する) のハウツー記事で追加方法が説明されているグリッドのデータを提供します。

通常、React アプリケーションでは、サービスはバックエンド サービスと対話します。 リモート監視ソリューション アクセラレータの例として、IoT ハブ マネージャーや構成マイクロサービスと対話するサービスなどがあります。

前提条件

このハウツー ガイドの手順を最後まで行うには、ローカル開発マシンに次のソフトウェアがインストールされている必要があります。

開始する前に

続行する前に、「Add a custom page to the Remote Monitoring solution accelerator web UI」(リモート監視ソリューション アクセラレータ Web UI にカスタム ページを追加する) のハウツー記事に記載されている手順を完了してください。

サービスの追加

Web UI にサービスを追加するには、サービスが定義されているソース ファイルを追加し、既存のファイルをいくつか変更して、Web UI で新しいサービスが認識されるようにする必要があります。

サービスが定義されている新しいファイルを追加する

すぐに始められるように、src/walkthrough/services フォルダーには単純なサービスを定義するファイルが用意されています。

exampleService.js


import { Observable } from 'rxjs';
import { toExampleItemModel, toExampleItemsModel } from './models';

/** Normally, you'll need to define the endpoint URL.
 * See app.config.js to add a new service URL.
 *
 * For this example, we'll just hardcode sample data to be returned instead
 * of making an actual service call. See the other service files for examples.
 */
//const ENDPOINT = Config.serviceUrls.example;

/** Contains methods for calling the example service */
export class ExampleService {

  /** Returns an example item */
  static getExampleItem(id) {
    return Observable.of(
      { ID: id, Description: "This is an example item." },
    )
      .map(toExampleItemModel);
  }

  /** Returns a list of example items */
  static getExampleItems() {
    return Observable.of(
      {
        items: [
          { ID: "123", Description: "This is item 123." },
          { ID: "188", Description: "This is item ONE-DOUBLE-EIGHT." },
          { ID: "210", Description: "This is item TWO-TEN." },
          { ID: "277", Description: "This is item 277." },
          { ID: "413", Description: "This is item FOUR-THIRTEEN." },
          { ID: "789", Description: "This is item 789." },
        ]
      }
    ).map(toExampleItemsModel);
  }

  /** Mimics a server call by adding a delay */
  static updateExampleItems() {
    return this.getExampleItems().delay(2000);
  }
}

サービスの実装方法の詳細については、「The introduction to Reactive Programming you've been missing」(不足しているリアクティブ プログラミングの紹介) を参照してください。

model/exampleModels.js

import { camelCaseReshape, getItems } from 'utilities';

/**
 * Reshape the server side model to match what the UI wants.
 *
 * Left side is the name on the client side.
 * Right side is the name as it comes from the server (dot notation is supported).
 */
export const toExampleItemModel = (data = {}) => camelCaseReshape(data, {
  'id': 'id',
  'description': 'descr'
});

export const toExampleItemsModel = (response = {}) => getItems(response)
  .map(toExampleItemModel);

exampleService.jssrc/services フォルダーにコピーし、exampleModels.jssrc/services/models フォルダーにコピーします。

src/services フォルダーの index.js ファイルを更新して、新しいサービスをエクスポートします。

export * from './exampleService';

src/services/models フォルダーの index.js ファイルを更新して、新しいモデルをエクスポートします。

export * from './exampleModels';

ストアからサービスへの呼び出しを設定する

すぐに始められるように、src/walkthrough/store/reducers フォルダーにはサンプル レジューサーが用意されています。

exampleReducer.js

import 'rxjs';
import { Observable } from 'rxjs';
import moment from 'moment';
import { schema, normalize } from 'normalizr';
import update from 'immutability-helper';
import { createSelector } from 'reselect';
import { ExampleService } from 'walkthrough/services';
import {
  createReducerScenario,
  createEpicScenario,
  errorPendingInitialState,
  pendingReducer,
  errorReducer,
  setPending,
  toActionCreator,
  getPending,
  getError
} from 'store/utilities';

// ========================= Epics - START
const handleError = fromAction => error =>
  Observable.of(redux.actions.registerError(fromAction.type, { error, fromAction }));

export const epics = createEpicScenario({
  /** Loads the example items */
  fetchExamples: {
    type: 'EXAMPLES_FETCH',
    epic: fromAction =>
      ExampleService.getExampleItems()
        .map(toActionCreator(redux.actions.updateExamples, fromAction))
        .catch(handleError(fromAction))
  }
});
// ========================= Epics - END

// ========================= Schemas - START
const itemSchema = new schema.Entity('examples');
const itemListSchema = new schema.Array(itemSchema);
// ========================= Schemas - END

// ========================= Reducers - START
const initialState = { ...errorPendingInitialState, entities: {}, items: [], lastUpdated: '' };

const updateExamplesReducer = (state, { payload, fromAction }) => {
  const { entities: { examples }, result } = normalize(payload, itemListSchema);
  return update(state, {
    entities: { $set: examples },
    items: { $set: result },
    lastUpdated: { $set: moment() },
    ...setPending(fromAction.type, false)
  });
};

/* Action types that cause a pending flag */
const fetchableTypes = [
  epics.actionTypes.fetchExamples
];

export const redux = createReducerScenario({
  updateExamples: { type: 'EXAMPLES_UPDATE', reducer: updateExamplesReducer },
  registerError: { type: 'EXAMPLE_REDUCER_ERROR', reducer: errorReducer },
  isFetching: { multiType: fetchableTypes, reducer: pendingReducer }
});

export const reducer = { examples: redux.getReducer(initialState) };
// ========================= Reducers - END

// ========================= Selectors - START
export const getExamplesReducer = state => state.examples;
export const getEntities = state => getExamplesReducer(state).entities || {};
export const getItems = state => getExamplesReducer(state).items || [];
export const getExamplesLastUpdated = state => getExamplesReducer(state).lastUpdated;
export const getExamplesError = state =>
  getError(getExamplesReducer(state), epics.actionTypes.fetchExamples);
export const getExamplesPendingStatus = state =>
  getPending(getExamplesReducer(state), epics.actionTypes.fetchExamples);
export const getExamples = createSelector(
  getEntities, getItems,
  (entities, items) => items.map(id => entities[id])
);
// ========================= Selectors - END

exampleReducer.jssrc/store/reducers フォルダーにコピーします。

レジューサーとエピックの詳細については、redux-observable に関するページを参照してください。

ミドルウェアの構成

ミドルウェアを構成するには、src/store フォルダーの rootReducer.js ファイルにレジューサーを追加します。

import { reducer as exampleReducer } from './reducers/exampleReducer';

const rootReducer = combineReducers({
  ...appReducer,
  ...devicesReducer,
  ...rulesReducer,
  ...simulationReducer,
  ...exampleReducer
});

エピックを src/store フォルダーの rootEpics.js ファイルに追加します。

import { epics as exampleEpics } from './reducers/exampleReducer';

// Extract the epic function from each property object
const epics = [
  ...appEpics.getEpics(),
  ...devicesEpics.getEpics(),
  ...rulesEpics.getEpics(),
  ...simulationEpics.getEpics(),
  ...exampleEpics.getEpics()
];

次の手順

この記事では、リモート監視ソリューション アクセラレータの Web UI のサービスの追加またはカスタマイズに使用できるリソースについて説明しました。

サービスを定義した後の次の手順は、サービスから返されるデータを表示するリモート監視ソリューション アクセラレータ Web UI にカスタム グリッドを追加することです。

リモート監視ソリューション アクセラレータの概念の詳細については、「 リモート監視アーキテクチャ」を参照してください。