export default function ConnectionForm()

in source/ui/src/views/ConnectionForm.tsx [35:411]


export default function ConnectionForm(): JSX.Element { // NOSONAR: typescript:S3776
  const { connectionName } = useParams<{ connectionName: string }>();
  const history = useHistory();
  const [loading, setLoading] = useState<boolean>(false);
  const [connection, setConnection] = useState<GetConnectionResponse>(INIT_CONNECTION);
  const [params, setParams] = useState<ConnectionDefinition>();
  const [errors, setErrors] = useState<KeyStringValue>({});
  const [showConfirmMessageModal, setShowConfirmMessageModal] = useState<boolean>(false);
  const [showMessagemMessageModal, setShowMessageMessageModal] = useState<boolean>(false);
  const [messageModalMessage, setMessageModalMessage] = useState<string | React.ReactNode>('');

  /**
   * Closes the connection form, and it goes to the main page.
   */
  const close = useCallback(() => {
    history.push('/');
  }, [history]);

  /**
   * Get a connection. This is for updating connection.
   * If there's no key value for optional values, it sets the default value.
   */
  const getConnection = useCallback(async () => {
    try {
      const encodedconnectionName = encodeURIComponent(connectionName);
      const response: GetConnectionResponse = await API.get(API_NAME, `/connections/${encodedconnectionName}`, {});

      if (response.protocol === MachineProtocol.OPCDA) {
        let listTags: string[] = [];
        let tags: string[] = [];

        if (response.opcDa!.listTags) {
          for (let tag of response.opcDa!.listTags) {
            listTags = listTags.concat(tag);
          }
        }

        if (response.opcDa!.tags) {
          for (let tag of response.opcDa!.tags) {
            tags = tags.concat(tag);
          }
        }

        response.listTags = listTags.join('\n');
        response.tags = tags.join('\n');
      } else if (response.protocol === MachineProtocol.OPCUA) {
        if (response.opcUa!.port === undefined) {
          response.opcUa!.port = '';
        }
      }

      setConnection(response);
    } catch (error) {
      const errorMessage = getErrorMessage(error);
      logger.error(errorMessage);

      setShowMessageMessageModal(true);
      setMessageModalMessage(
        <Alert variant="danger">
          {I18n.get('error.message.get.connection')}
          <br />
          {I18n.get('error')}: <code>{JSON.stringify(errorMessage)}</code>
        </Alert>
      );
    }
  }, [connectionName]);

  /**
   * React useEffect hook.
   * For the updating connection, get the connection first.
   */
  useEffect(() => {
    let isMounted = true;

    if (isMounted && connectionName) {
      getConnection();
    }

    return () => { isMounted = false; };
  }, [connectionName, getConnection]);

  /**
   * Handles the value change.
   * @param event The React change event
   */
  function handleValueChange(event: React.ChangeEvent<FormControlElement>): void {
    const { id } = event.target;
    let value: any = event.target.value;
    const copiedConnection = copyObject(connection);

    if (id.startsWith('sendDataTo')) {
      const { checked } = event.target as HTMLInputElement;
      value = checked;
    }

    // To prevent same ID from different protocols, OPC UA has prefix.
    let opcUaId: string | undefined;
    if (id.startsWith('opcUa')) {
      opcUaId = id.split('-').pop() as string;
    }

    if (opcUaId ? setValue(copiedConnection.opcUa, opcUaId, value) : setValue(copiedConnection, id, value)) {
      setConnection(copiedConnection);

      // Since `listTags` and `tags` need to be built to the array, it does not check the validation in real time.
      if (!['listTags', 'tags'].includes(id)) {
        const newErrors = validateConnectionDefinition(copiedConnection);

        if (id.toLowerCase().endsWith('machineip') || id.toLowerCase().endsWith('servername')) {
          const newId = opcUaId ? `opcUa_${opcUaId}` : `opcDa_${id}`;
          setErrors({
            ...errors,
            [newId]: newErrors[newId]
          });
        } else {
          setErrors({
            ...errors,
            [id]: newErrors[id]
          });
        }
      }
    }
  }

  /**
   * Sets the value of the object key.
   * @param obj The object to set the value
   * @param key The object key
   * @param value The new object value
   * @returns If the key exists, return `true`. Otherwise, return `false`.
   */
  function setValue(obj: any, key: string, value: any): boolean {
    if (typeof obj !== 'object') return false;
    if (Object.keys(obj).includes(key)) {
      obj[key] = value;
      return true;
    }

    for (let childKey of Object.keys(obj)) {
      if (setValue(obj[childKey], key, value)) return true;
    }

    return false;
  }

  /**
   * Handles the connection deployment and update.
   */
  async function handleConnection(event: any): Promise<void> {
    event.preventDefault();

    try {
      const buildParams: ConnectionDefinition = {
        control: connectionName ? ConnectionControl.UPDATE : ConnectionControl.DEPLOY,
        connectionName: connection.connectionName,
        area: connection.area,
        machineName: connection.machineName,
        process: connection.process,
        protocol: connection.protocol,
        sendDataToIoTSitewise: connection.sendDataToIoTSitewise,
        sendDataToIoTTopic: connection.sendDataToIoTTopic,
        sendDataToKinesisDataStreams: connection.sendDataToKinesisDataStreams,
        siteName: connection.siteName
      };

      if (buildParams.protocol === MachineProtocol.OPCDA) {
        buildParams.opcDa = connection.opcDa;
        buildParams.opcDa!.listTags = buildTags(connection.listTags);
        buildParams.opcDa!.tags = buildTags(connection.tags);
      } else if (buildParams.protocol === MachineProtocol.OPCUA) {
        buildParams.opcUa = connection.opcUa;
      }

      const newErrors = validateConnectionDefinition(buildParams);

      /**
       * For the OPC UA, the server name should be unique, so if the server name is valid,
       * it checks if the server name is unique.
       */
      if (buildParams.protocol === MachineProtocol.OPCUA && !newErrors.opcUaServerName) {
        const serverName = buildParams.opcUa!.serverName;
        const opcUaConnection: GetConnectionResponse = await API.get(API_NAME, `/sitewise/${encodeURIComponent(serverName)}`, {});

        if (Object.keys(opcUaConnection).length > 0) {
          if (buildParams.control === ConnectionControl.DEPLOY) {
            newErrors.opcUa_serverName = I18n.get('invalid.duplicated.server.name');
          } else {
            /**
             * If it's updating the connection, the server name can be updated as well,
             * so if the server name on the form is same to the connection's server name, it's not an error.
             */
            const existingConnection: GetConnectionResponse = await API.get(API_NAME, `/connections/${encodeURIComponent(buildParams.connectionName)}`, {});
            const existingOpcUa = existingConnection.opcUa;

            if (existingOpcUa!.serverName !== serverName) {
              newErrors.opcUa_serverName = I18n.get('invalid.duplicated.server.name');
            }
          }
        }
      }

      if (Object.keys(newErrors).length > 0) {
        setErrors(newErrors);
      } else {
        setErrors({});
        setParams(buildParams);
        setShowConfirmMessageModal(true);
      }
    } catch (error) {
      const errorMessage = getErrorMessage(error);
      logger.error(errorMessage);

      setShowConfirmMessageModal(false);
      setShowMessageMessageModal(true);
      setMessageModalMessage(
        <Alert variant="danger">
          {connectionName ? I18n.get('error.message.update.connection') : I18n.get('error.message.create.connection')}
          <br />
          {I18n.get('error')}: <code>{JSON.stringify(errorMessage)}</code>
          <br /><br />
          {I18n.get('error.message.implementation.guide')}
        </Alert>
      );
    }
  }

  /**
   * Calls an API.
   * @param buildParams The build connection definition request parameters
   */
  async function callApi(buildParams: ConnectionDefinition): Promise<void> {
    setLoading(true);

    try {
      const connectionDefinition = buildConnectionDefinition(buildParams);
      const response: CreateUpdateConnectionResponse = await API.post(API_NAME, '/connections', { body: connectionDefinition });
      const message = I18n.get(connectionName ? 'info.message.update.connection' : 'info.message.create.connection').replace('{}', response.connectionName);
      setShowMessageMessageModal(true);
      setMessageModalMessage(
        <>
          <span>{message}</span>
          <br />
          <span>{I18n.get('info.message.background.running')}</span>
        </>
      );
    } catch (error) {
      const errorMessage = getErrorMessage(error);
      logger.error(errorMessage);

      setShowConfirmMessageModal(false);
      setShowMessageMessageModal(true);
      setMessageModalMessage(
        <Alert variant="danger">
          {connectionName ? I18n.get('error.message.update.connection') : I18n.get('error.message.create.connection')}
          <br />
          {I18n.get('error')}: <code>{JSON.stringify(errorMessage)}</code>
          <br /><br />
          {I18n.get('error.message.implementation.guide')}
        </Alert>
      );
    }

    setLoading(false);
  }

  /**
   * Builds the OPC DA tags.
   * @param value The value from the textarea
   */
  function buildTags(value: string | undefined): string[] {
    const arr: string[] = [];

    if (value && value.trim() !== '') {
      const splitTags = value.split('\n');

      for (let tag of splitTags) {
        const trimTag = tag.trim();
        if (trimTag !== '') arr.push(trimTag);
      }
    }

    return arr;
  }

  return (
    <Container>
      <Breadcrumb>
        <LinkContainer to="/" exact>
          <Breadcrumb.Item>{I18n.get('manage.connections')}</Breadcrumb.Item>
        </LinkContainer>
        <Breadcrumb.Item active>{connectionName ? `${I18n.get('update.connection')}: ${connectionName}` : I18n.get('create.connection')}</Breadcrumb.Item>
      </Breadcrumb>
      <Form onSubmit={handleConnection}>
        <Form.Group>
          <Form.Label>{I18n.get('connection.name')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.connection.name')}</Form.Text>
          <Form.Control id="connectionName" type="text" required disabled={connectionName !== undefined}
            defaultValue={connection.connectionName} placeholder={I18n.get('placeholder.connection.name')}
            onChange={handleValueChange} isInvalid={!!errors.connectionName} />
          <Form.Control.Feedback type="invalid">{errors.connectionName}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group>
          <Form.Label>{I18n.get('site.name')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.site.name')}</Form.Text>
          <Form.Control id="siteName" type="text" required
            defaultValue={connection.siteName} placeholder={I18n.get('placeholder.site.name')}
            onChange={handleValueChange} isInvalid={!!errors.siteName} />
          <Form.Control.Feedback type="invalid">{errors.siteName}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group>
          <Form.Label>{I18n.get('area')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.area')}</Form.Text>
          <Form.Control id="area" type="text" required
            defaultValue={connection.area} placeholder={I18n.get('placeholder.area')}
            onChange={handleValueChange} isInvalid={!!errors.area} />
          <Form.Control.Feedback type="invalid">{errors.area}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group>
          <Form.Label>{I18n.get('process')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.process')}</Form.Text>
          <Form.Control id="process" type="text" required
            defaultValue={connection.process} placeholder={I18n.get('placeholder.process')}
            onChange={handleValueChange} isInvalid={!!errors.process} />
          <Form.Control.Feedback type="invalid">{errors.process}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group>
          <Form.Label>{I18n.get('machine.name')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.machine.name')}</Form.Text>
          <Form.Control id="machineName" type="text" required
            defaultValue={connection.machineName} placeholder={I18n.get('placeholder.machine.name')}
            onChange={handleValueChange} isInvalid={!!errors.machineName} />
          <Form.Control.Feedback type="invalid">{errors.machineName}</Form.Control.Feedback>
        </Form.Group>
        <Form.Group>
          <Form.Label>{I18n.get('send.data.to')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.send.data.to')}</Form.Text>
          <Form.Group>
            <Form.Check inline type="checkbox" id="sendDataToIoTSitewise"
              label={I18n.get('iot.sitewise')} checked={connection.sendDataToIoTSitewise}
              onChange={handleValueChange} isInvalid={!!errors.sendDataTo} />
            <EmptyCol />
            <Form.Check inline type="checkbox" id="sendDataToIoTTopic"
              label={I18n.get('iot.topic')} checked={connection.sendDataToIoTTopic}
              onChange={handleValueChange} isInvalid={!!errors.sendDataTo} />
            <EmptyCol />
            <Form.Check inline type="checkbox" id="sendDataToKinesisDataStreams"
              label={I18n.get('kinesis.data.streams')} checked={connection.sendDataToKinesisDataStreams}
              onChange={handleValueChange} isInvalid={!!errors.sendDataTo} />
            <Form.Control.Feedback type="invalid">{errors.connectionName}</Form.Control.Feedback>
          </Form.Group>
        </Form.Group>
        <Form.Group>
          <Form.Label>{I18n.get('protocol')} <span className="red-text">*</span></Form.Label>
          <Form.Text muted>{I18n.get('description.protocol')}</Form.Text>
          <Form.Control id="protocol" as="select" onChange={handleValueChange} value={connection.protocol} disabled={connectionName !== undefined}>
            <option value={MachineProtocol.OPCDA}>OPC DA</option>
            <option value={MachineProtocol.OPCUA}>OPC UA</option>
          </Form.Control>
        </Form.Group>
        {
          connection.protocol === MachineProtocol.OPCDA &&
          <OpcDaForm connection={{ ...connection }} onChange={handleValueChange} errors={errors} />
        }
        {
          connection.protocol === MachineProtocol.OPCUA &&
          <OpcUaForm connection={{ ...connection }} onChange={handleValueChange} errors={errors} />
        }
        <EmptyRow />
        <Row>
          <Col className="justify-content-center grid">
            <ButtonGroup>
              <Button id="connection-form-submit-button" size="sm" className="uppercase-text" type="submit" disabled={loading}>
                {loading ? <><Spinner as="span" animation="border" size="sm" role="status" aria-hidden="true" /><EmptyCol /></> : <></>}
                {connectionName ? I18n.get('update') : I18n.get('create')}
              </Button>
              <EmptyCol />
              <Button id="connection-form-cancel-button" size="sm" className="uppercase-text" onClick={() => close()} disabled={loading} variant="secondary">{I18n.get('cancel')}</Button>