Microsoft Lists: リストアイテム検索

Microsoft Lists: リストアイテム検索

Microsoft Lists: Search List Items

この工程は、Microsoft Lists の指定リスト内のリストアイテムを検索します。

Basic Configs
工程名
メモ
Auto Step icon
Configs for this Auto Step
conf_OAuth2
C1: OAuth2 設定 *
conf_SiteUrl
C2: SharePoint サイトの URL *
conf_ListTitle
C3: リストの名前 *
conf_Filter
C4: フィルタ#{EL}
conf_Ids
C5: リストアイテム ID を保存するデータ項目
conf_ColumnName1
C-K1: 列 1 の Lists フィールド名
conf_ColumnValues1
C-V1: 列 1 の値を保存するデータ項目
conf_ColumnName2
C-K2: 列 2 の Lists フィールド名
conf_ColumnValues2
C-V2: 列 2 の値を保存するデータ項目

Notes

  • SharePoint サイトの URL は、リスト URL の /Lists より前の部分です
    • マイリスト内のリストの場合、https://{サブドメイン}-my.sharepoint.com/personal/{ユーザ識別子}_{サブドメイン}_onmicrosoft_com のような形式です
    • SharePoint サイト内のリストの場合、https://{サブドメイン}.sharepoint.com/sites/{サイト識別子} のような形式です
  • フィルタの書式については、Microsoft Graph ドキュメントを参照してください
  • フィールド名を取得する方法はいくつかあります
    • リストのスキーマ付き CSV をエクスポートすると、各列のスキーマ内の Name プロパティの値がフィールド名です
    • WebUI でリストを表示し、列の値で並び替えを行うと、URL に sortField={フィールド名} の形式でフィールド名が含まれます
  • このモデリング要素がサポートする Microsoft Lists の列タイプは、1 行テキスト、数、Yse/No、日付と時刻、選択肢(複数選択を許可していない場合のみ)、通貨です

Capture

See Also

Script (click to open)
  • 次のスクリプトが記述されている XML ファイルをダウンロードできます
    • microsoft-lists-listitem-search.xml (C) Questetra, Inc. (MIT License)
    • Professional のワークフロー基盤では、ファイル内容を改変しオリジナルのアドオン自動工程として活用できます

// OAuth2 config sample at [OAuth 2.0 Setting]
// - Authorization Endpoint URL: https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/authorize
// - Token Endpoint URL: https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
// - Scope: https://graph.microsoft.com/Sites.ReadWrite.All offline_access
// - Consumer Key: (Get by Microsoft Azure Active Directory)
// - Consumer Secret: (Get by Microsoft Azure Active Directory)

const GRAPH_URI = 'https://graph.microsoft.com/v1.0/';

const MAX_PAGE_SIZE = 5000; // 一度のリクエストで取得できるリストアイテムの最大数

const main = () => {
    //// == 工程コンフィグの参照 / Config Retrieving ==
    const oauth2 = configs.getObject('conf_OAuth2');
    const siteUrl = retrieveSiteUrl();
    const listTitle = configs.get('conf_ListTitle');
    const filter = configs.get('conf_Filter');
    const {idDef, columnNames, columnDefs, singleLineFlag} = retrieveColumnNamesAndDataDefs();

    //// == 演算 / Calculating ==
    const siteId = getSiteIdByUrl(oauth2, siteUrl);
    if (columnNames.length > 0) {
        checkColumns(oauth2, siteId, listTitle, columnNames);
    }
    const listItems = getListItems(oauth2, siteId, listTitle, filter, columnNames, singleLineFlag);

    //// == ワークフローデータへの代入 / Data Updating ==
    saveData(listItems, idDef, columnNames, columnDefs);
};

/**
 * 工程コンフィグからサイトの URL を取得する
 * @returns {String} サイトの URL
 */
const retrieveSiteUrl = () => {
    let siteUrl = configs.get('conf_SiteUrl');
    // 末尾にスラッシュがある場合、削除
    if (siteUrl.endsWith('/')) {
        siteUrl = siteUrl.slice(0, -1);
    }
    return siteUrl;
};

/**
 * 工程コンフィグから列の名前、保存先データ項目を取得する
 * 以下の場合はエラー
 * - 名前、保存先データ項目の片方だけが指定されている列がある
 * - 保存先データ項目がひとつも指定されていない
 * @returns {Object} result
 * @returns {DataDefinitionView} result.idDef  ID 保存先データ項目
 * @returns {Array<String>} result.columnNames  列の名前
 * @returns {Array<DataDefinitionView>} result.columnDefs  列の値の保存先データ項目
 * @returns {boolean} result.singleLineFlag  保存先データ項目のうちひとつでも単一行のものがあれば true
 */
const retrieveColumnNamesAndDataDefs = () => {
    const columnNames = [];
    const columnDefs = [];
    let singleLineFlag = false;
    for (let i = 0; i < 2; i++) {
        const columnName = configs.get(`conf_ColumnName${i + 1}`);
        const columnDef = configs.getObject(`conf_ColumnValues${i + 1}`);
        if (columnName === '' && columnDef === null) {
            continue;
        } else if (columnName !== '' && columnDef !== null) {
            columnNames.push(columnName);
            columnDefs.push(columnDef);
            if (columnDef.matchDataType('STRING_TEXTFIELD')) {
                singleLineFlag = true;
            }
        } else {
            throw new Error(`Name and data item for Column ${i + 1} must be specified at the same time.`);
        }
    }
    const idDef = configs.getObject('conf_Ids');
    if (idDef === null && columnDefs.length === 0) {
        throw new Error('No data item to save the result.');
    }
    if (idDef !== null && idDef.matchDataType('STRING_TEXTFIELD')) {
        singleLineFlag = true;
    }
    return {idDef, columnNames, columnDefs, singleLineFlag};
};

/**
 * サイトのメタデータを取得し、siteId を返す
 * APIの仕様: https://docs.microsoft.com/ja-jp/onedrive/developer/rest-api/api/shares_get?view=odsp-graph-online
 * @param {AuthSettingWrapper} oauth2  OAuth2 設定情報
 * @param {String} siteUrl  SharePoint サイトの URL
 * @returns {String} siteId
 */
const getSiteIdByUrl = (oauth2, siteUrl) => {
    // encoding sharing URL
    const encodedSharingUrl = encodeSharingUrl(siteUrl);

    // preparing for API Request
    const response = httpClient.begin()
        .authSetting(oauth2)
        .queryParam('select', 'id')
        .get(`${GRAPH_URI}shares/${encodedSharingUrl}/site`);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw new Error(`Failed to get site info. status: ${status}`);
    }
    return JSON.parse(responseStr).id;
};

/**
 * 共有URLを unpadded base64url 形式にエンコードする
 * @param {String} sharingUrl  共有URL
 * @returns {String} encodedSharingUrl  エンコードされた共有URL
 */
const encodeSharingUrl = (sharingUrl) => {
    let encodedSharingUrl = base64.encodeToUrlSafeString(sharingUrl);
    while (encodedSharingUrl.slice(-1) === '=') {
        encodedSharingUrl = encodedSharingUrl.slice(0, -1);
    }
    return `u!${encodedSharingUrl}`;
};

/**
 * リストの列のメタデータを取得し、指定した名前の列の存在と型を確認する
 * @param {AuthSettingWrapper} oauth2  OAuth2 設定情報
 * @param {String} siteId  SharePoint サイトの ID
 * @param {String} listTitle  リストの名前
 * @param {Array<String>} columnNames  列の名前
 */
const checkColumns = (oauth2, siteId, listTitle, columnNames) => {
    const url = `${GRAPH_URI}sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listTitle)}/columns`;
    const response = httpClient.begin()
        .authSetting(oauth2)
        .queryParam('$select', 'name,text') // 複数行のテキスト型をエラーにするため、text プロパティを取得
        // $filter, $search, $top パラメータは効かない
        .get(url);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw new Error(`Failed to get columns. status: ${status}`);
    }
    const columns = JSON.parse(responseStr).value;
    columnNames.forEach(columnName => {
        findAndCheckColumn(columns, columnName);
    });
};

/**
 * 列の存在と型を確認する
 * 列が存在しない場合、テキスト型で複数行を許可している場合はエラーにする
 * @param {Array<Object>} columns  列のメタデータ一覧
 * @param {String} name  列の名前
 */
const findAndCheckColumn = (columns, name) => {
    const column = columns.find((column) => column.name === name);
    if (column === undefined) {
        throw new Error(`Column '${name}' not found.`);
    }
    if (column.text !== undefined && column.text.allowMultipleLines) {
        throw new Error(`'${name}' is a text column which can contain a multi-line text.`);
    }
};

/**
 * リストアイテムを検索し、JSONオブジェクトを返す
 * @param {AuthSettingWrapper} oauth2  OAuth2 設定情報
 * @param {String} siteId  SharePoint サイトの ID
 * @param {String} listTitle  リストの名前
 * @param {String} filter  フィルタ
 * @param {Array<String>} columnNames  列の名前
 * @param {boolean} singleLineFlag  保存先データ項目のうちひとつでも単一行のものがあれば true
 * @returns {Array<Object>} 検索結果
 */
const getListItems = (oauth2, siteId, listTitle, filter, columnNames, singleLineFlag) => {
    const url = `${GRAPH_URI}sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listTitle)}/items`;
    let request = httpClient.begin()
        .authSetting(oauth2)
        // インデックスを作成していないフィールドでもフィルタできるように、Prefer ヘッダを指定
        // ただし、リストアイテムの総数が多すぎる(5000 件以上)とエラーになる
        .header('Prefer', 'HonorNonIndexedQueriesWarningMayFailRandomly')
        .queryParam('$select', 'id')
        .queryParam('$top', `${MAX_PAGE_SIZE}`)
        // $count パラメータは非対応
        // $orderby パラメータはあえて指定しない。デフォルトの ID 順のまま取得する
        .queryParam('$filter', filter);
    if (columnNames.length > 0) {
        request = request.queryParam('$expand', `fields($select=${columnNames.join(',')})`);
    }
    const response = request.get(url);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw new Error(`Failed to get list items. status: ${status}`);
    }
    const listItems = JSON.parse(responseStr).value;
    if (listItems.length === 0) {
        throw new Error('No list items found.');
    }
    // 本来は一度のリクエストで検索結果を取得しきれなかった場合にエラーにしたいが、$count パラメータが使用不可なので、
    // 取得された件数が上限値と同じ場合にエラーにする
    if (listItems.length === MAX_PAGE_SIZE) {
        throw new Error(`The number of results reached ${MAX_PAGE_SIZE}.`);
    }
    if (singleLineFlag && listItems.length > 1) {
        throw new Error('Multiple list items were found while the data item to save the result is single-line.');
    }
    return listItems;
};

/**
 * データ項目への保存
 * @param {Array<Object>} listItems リストアイテム
 * @param {DataDefinitionView} idDef ID 保存先データ項目
 * @param {Array<String>} columnNames 列の名前
 * @param {Array<DataDefinitionView>} columnDefs 列の値の保存先データ項目
 */
const saveData = (listItems, idDef, columnNames, columnDefs) => {
    setData(idDef, listItems.map((item) => item.id).join('\n'));
    columnNames.forEach((columnName, index) => {
        setData(columnDefs[index], stringifyColumnValues(listItems, columnName));
    });
};

/**
 * 列の値を文字列化する
 * @param listItems
 * @param columnName
 */
const stringifyColumnValues = (listItems, columnName) => {
    const columnValues = listItems.map((item) => item.fields[columnName])
        .map(columnValue => {
            if (columnValue === undefined || columnValue === null) {
                return '';
            }
            const supportedPrimitiveTypes = ['string', 'number', 'boolean'];
            if (supportedPrimitiveTypes.includes(typeof columnValue)) {
                return columnValue.toString();
            }
            // 配列や JSON オブジェクトの場合、エラー
            throw new Error(`Failed to stringify the value of Column '${columnName}'. It is not a string, number, nor boolean.`);
        });
    return columnValues.join('\n');
};

/**
 * データ項目への保存
 * @param {DataDefinitionView} def 保存先データ項目
 * @param {Object} data 保存するデータ
 */
const setData = (def, data) => {
    if (def !== null) {
        engine.setData(def, data);
    }
};

    
上部へスクロール

Questetra Supportをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む