Source: plugins/Operation/index.jsx

/*
 * Copyright 2024, GeoSolutions Sas.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React, { useEffect } from 'react';
import { createPlugin } from '@mapstore/framework/utils/PluginsUtils';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import Button from '@js/components/Button/Button';
import { selectOperation, reloadOperation } from './actions/operation';
import { getResourceData } from '@js/selectors/resource';
import operation from './reducers/operation';
import epics from './epics/operation';
import OperationUpload from './containers/OperationUpload';
import Message from '@mapstore/framework/components/I18N/Message';

/**
* @module Operation
*/

/**
 * render a upload interface for the configured operation
 * @name Operation.
 * @prop {string} id unique identifier of the operation
 * @prop {string} labelId label id for the menu button on the action navbar that opens the upload interface
 * @prop {boolean} blocking if true the ui will prevent interaction with the resource until the operation is completed
 * @prop {string} iconName a font awesome icon name for the central section
 * @prop {string} titleMsgId title for the central section
 * @prop {string} descriptionMsgId description for the central section
 * @prop {string} action the operation action used in the upload process
 * @prop {boolean} pageReload if true the page is reloaded after clicking on reload
 * @prop {object} api an object with the api configuration for upload and execution request
 * @prop {object} api.upload configuration for the upload process
 * @prop {string} api.upload.url upload endpoint
 * @prop {string} api.upload.method request method (default post)
 * @prop {number} api.upload.maxParallelUploads number of maximum parallel uploads
 * @prop {boolean} api.upload.enableRemoteUploads enable the remote upload button
 * @prop {array} api.upload.supportedFiles list of supported type of upload
 * @prop {object} api.upload.body body request configuration
 * @prop {object} api.upload.body.file body request configuration for file uploads
 * @prop {object} api.upload.body.remote body request configuration for remote uploads
 * @prop {object} api.executionRequest configuration for the execution requests
 * @prop {string} api.executionRequest.url execution requests endpoint
 * @prop {object} api.executionRequest.params query parameters for the request
 * @example
 * {
 *  "name": "Operation",
 *  "cfg": {
 *      "containerPosition": "header",
 *      "id": "operation-id",
 *      "labelId": "gnviewer.operationLabel",
 *      "blocking": true,
 *      "iconName": "file",
 *      "titleMsgId": "gnviewer.operationTitle",
 *      "descriptionMsgId": "gnviewer.operationDescription",
 *      "action": "upload",
 *      "api": {
 *          "upload": {
 *              "url": "{context.getEndpointUrl('uploads', '/upload')}",
 *              "maxParallelUploads": 1,
 *              "enableRemoteUploads": false,
 *              "supportedFiles": "{context.getSupportedFilesByResourceType('dataset', { actions: ['upload'] })}",
 *              "body": {
 *                  "file": {
 *                      "base_file": "{context.getUploadMainFile}",
 *                      "resource_pk": "{context.get(state('gnResourceData'), 'pk')}",
 *                      "action": "upload"
 *                  }
 *              }
 *          },
 *          "executionRequest": {
 *              "url": "{context.getEndpointUrl('executionrequest')}",
 *              "params": {
 *                  "filter{action}": "upload",
 *                  "sort[]": "-created",
 *                  "filter{geonode_resource}": "{context.get(state('gnResourceData'), 'pk')}"
 *              }
 *          }
 *      }
 *  },
 *  "override": {
 *      "ActionNavbar": {
 *          "name": "OperationId"
 *      }
 *  }
 * }
 */
function Operation({
    id,
    selected,
    resource,
    api,
    onReload,
    onSelect,
    blocking,
    iconName,
    titleMsgId,
    descriptionMsgId,
    action,
    pageReload
}) {

    // open the import ui if a blocking execution is still running
    const executions = resource?.executions;
    useEffect(() => {
        if (executions && action && blocking) {
            const runningExecution = executions
                .find((execution) => execution.status === 'running'
                && execution?.input_params?.action === action);
            if (runningExecution) {
                onSelect(id);
            }
        }
    }, [id, blocking, action, executions]);

    if (selected !== id) {
        return null;
    }
    return (
        <OperationUpload
            api={api}
            onSelect={onSelect}
            blocking={blocking}
            onReload={onReload}
            iconName={iconName}
            titleMsgId={titleMsgId}
            descriptionMsgId={descriptionMsgId}
            pageReload={pageReload}
        />
    );
}

const OperationPlugin = connect(
    createSelector([
        state => state?.operation?.selected,
        getResourceData
    ], (selected, resource) => ({
        selected,
        resource
    })),
    {
        onSelect: selectOperation,
        onReload: reloadOperation
    }
)(Operation);

function OperationButton({
    id,
    labelId,
    size,
    variant,
    onClick
}) {
    return (
        <Button size={size} variant={variant} onClick={() => onClick(id)}>
            <Message msgId={labelId} />
        </Button>
    );
}

const ConnectedOperationButton = connect(
    createSelector([], () => ({})),
    {
        onClick: selectOperation
    }
)(OperationButton);

export default createPlugin('Operation', {
    component: OperationPlugin,
    containers: {
        ActionNavbar: {
            Component: ConnectedOperationButton
        }
    },
    epics,
    reducers: {
        operation
    }
});