GladysProject/Gladys

View on GitHub
front/src/routes/integration/all/mqtt/device-page/setup/index.js

Summary

Maintainability
F
3 days
Test Coverage
import { Component } from 'preact';
import { connect } from 'unistore/preact';
import actions from '../actions';
import FeatureTab from './FeatureTab';
import MqttPage from '../../MqttPage';
import uuid from 'uuid';
import get from 'get-value';
import update from 'immutability-helper';
import { RequestStatus } from '../../../../../../utils/consts';
import { slugify } from '../../../../../../../../server/utils/slugify';
import withIntlAsProp from '../../../../../../utils/withIntlAsProp';
import { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } from '../../../../../../../../server/utils/constants';

class MqttDeviceSetupPage extends Component {
  selectFeature(selectedFeatureOption) {
    if (selectedFeatureOption && selectedFeatureOption.value) {
      this.setState({
        selectedFeature: selectedFeatureOption.value,
        selectedFeatureOption
      });
    } else {
      this.setState({
        selectedFeature: null,
        selectedFeatureOption: null
      });
    }
  }

  addFeature() {
    const featureData = this.state.selectedFeature.split('|');

    let defaultValues = {};

    if (featureData[1] === DEVICE_FEATURE_TYPES.SWITCH.BINARY) {
      defaultValues.min = 0;
      defaultValues.max = 1;
    }

    if (featureData[1] === DEVICE_FEATURE_TYPES.TEXT.TEXT) {
      defaultValues.min = 0;
      defaultValues.max = 0;
      defaultValues.keep_history = false;
    }

    const device = update(this.state.device, {
      features: {
        $push: [
          {
            id: uuid.v4(),
            category: featureData[0],
            external_id: 'mqtt:',
            type: featureData[1],
            read_only: true,
            has_feedback: false,
            keep_history: true,
            ...defaultValues
          }
        ]
      }
    });

    this.setState({
      device
    });
  }

  deleteFeature(featureIndex) {
    const device = update(this.state.device, {
      features: {
        $splice: [[featureIndex, 1]]
      }
    });

    this.setState({
      device
    });
  }

  updateDeviceProperty(deviceIndex, property, value) {
    let device;
    if (property === 'external_id' && !value.startsWith('mqtt:')) {
      if (value.length < 5) {
        value = 'mqtt:';
      } else {
        value = `mqtt:${value}`;
      }
    }

    device = update(this.state.device, {
      [property]: {
        $set: value
      }
    });

    if (property === 'external_id') {
      device = update(device, {
        selector: {
          $set: value
        }
      });
    }

    this.setState({
      device
    });
  }

  updateFeatureProperty(e, property, featureIndex) {
    let value = e.target.value;
    let device;
    if (property === 'external_id' && !value.startsWith('mqtt:')) {
      if (value.length < 5) {
        value = 'mqtt:';
      } else {
        value = `mqtt:${value}`;
      }
    }

    device = update(this.state.device, {
      features: {
        [featureIndex]: {
          [property]: {
            $set: value
          }
        }
      }
    });

    if (property === 'external_id') {
      device = update(device, {
        features: {
          [featureIndex]: {
            selector: {
              $set: value
            }
          }
        }
      });
    }

    this.setState({
      device
    });
  }

  async saveDevice() {
    this.setState({
      loading: true
    });
    try {
      // If we are creating a device, we check that the device doesn't already exist
      if (!this.state.device.id) {
        try {
          await this.props.httpClient.get(`/api/v1/device/${slugify(this.state.device.selector)}`);
          // if we are here, it means the device already exist
          this.setState({
            saveStatus: RequestStatus.ConflictError,
            loading: false
          });
          return;
        } catch (e) {
          // If we are here, it's ok, it means the device does not exist yet
        }
      }
      const device = await this.props.httpClient.post('/api/v1/device', this.state.device);
      this.setState({
        saveStatus: RequestStatus.Success,
        loading: false,
        device
      });
    } catch (e) {
      const status = get(e, 'response.status');
      if (status === 409) {
        this.setState({
          saveStatus: RequestStatus.ConflictError,
          loading: false
        });
      } else {
        this.setState({
          saveStatus: RequestStatus.Error,
          loading: false
        });
      }
    }
  }

  getDeviceFeaturesOptions = () => {
    const deviceFeaturesOptions = [];
    Object.keys(DEVICE_FEATURE_CATEGORIES).forEach(category => {
      const categoryValue = DEVICE_FEATURE_CATEGORIES[category];
      if (get(this.props.intl.dictionary, `deviceFeatureCategory.${categoryValue}`)) {
        const categoryFeatureTypeOptions = [];

        const types = Object.keys(get(this.props.intl.dictionary, `deviceFeatureCategory.${categoryValue}`));

        types.forEach(type => {
          const typeValue = type;
          if (
            get(this.props.intl.dictionary, `deviceFeatureCategory.${categoryValue}.${typeValue}`) &&
            typeValue !== 'shortCategoryName'
          ) {
            categoryFeatureTypeOptions.push({
              value: `${categoryValue}|${typeValue}`,
              label: get(this.props.intl.dictionary, `deviceFeatureCategory.${categoryValue}.${typeValue}`)
            });
          }
        });
        deviceFeaturesOptions.push({
          label: get(this.props.intl.dictionary, `deviceFeatureCategory.${categoryValue}.shortCategoryName`),
          options: categoryFeatureTypeOptions
        });
      }
    });
    this.setState({ deviceFeaturesOptions });
  };

  constructor(props) {
    super(props);

    this.state = {
      loading: true
    };

    this.selectFeature = this.selectFeature.bind(this);
    this.addFeature = this.addFeature.bind(this);
    this.deleteFeature = this.deleteFeature.bind(this);
    this.updateDeviceProperty = this.updateDeviceProperty.bind(this);
    this.updateFeatureProperty = this.updateFeatureProperty.bind(this);
    this.saveDevice = this.saveDevice.bind(this);
  }

  async componentWillMount() {
    this.props.getHouses();
    this.getDeviceFeaturesOptions();
    await this.props.getIntegrationByName('mqtt');

    let { deviceSelector } = this.props;
    let device;

    if (!deviceSelector) {
      device = {
        name: '',
        should_poll: false,
        external_id: 'mqtt:',
        service_id: this.props.currentIntegration.id,
        features: []
      };
    } else {
      const loadedDevice = await this.props.httpClient.get(`/api/v1/device/${deviceSelector}`);

      if (
        loadedDevice &&
        this.props.currentIntegration &&
        loadedDevice.service_id === this.props.currentIntegration.id
      ) {
        device = loadedDevice;
      }
    }

    this.setState({
      device,
      loading: false
    });
  }

  render(props, state) {
    return (
      <MqttPage user={props.user}>
        <FeatureTab
          {...props}
          {...state}
          selectFeature={this.selectFeature}
          addFeature={this.addFeature}
          deleteFeature={this.deleteFeature}
          updateDeviceProperty={this.updateDeviceProperty}
          updateFeatureProperty={this.updateFeatureProperty}
          saveDevice={this.saveDevice}
        />
      </MqttPage>
    );
  }
}

export default withIntlAsProp(
  connect('session,user,httpClient,houses,currentIntegration', actions)(MqttDeviceSetupPage)
);