lodash#isNaN TypeScript Examples

The following examples show how to use lodash#isNaN. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: formatter.ts    From S2 with MIT License 6 votes vote down vote up
auto = (
  v: number,
  fixed = 2,
  formatter = FORMATTERS[getLang()] || FORMATTERS.zh_CN,
): string => {
  if (typeof v !== 'number' || isNaN(v)) {
    return '';
  }
  // let n = Math.abs(v); // abs什么鬼。
  let n = v;
  // 语义化
  const [texts, powers] = formatter;

  let loop = 0;
  let power;
  let running = true;

  while (running) {
    power = powers[loop] as number;

    if (n >= power && loop < texts.length) {
      n /= power;
    } else {
      running = false;
    }
    loop += 1;
  }

  // parseFloat 解决 toFixed 出现很多 0 结尾。
  // 举例:123.toFixed(2) = '123.00',需要返回 '123'
  n = parseFloat(n.toFixed(fixed));

  // 千分位
  const output = n >= 1000 ? n.toLocaleString('en') : `${n}`;

  // 加上最后的单位
  return loop === 0 ? output : `${output} ${texts[loop - 1]}`;
}
Example #2
Source File: machine-table.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
filterFunc = {
  include: (record: ORG_MACHINE.IMachine, val = '', key: string) => {
    return record[key].includes(val);
  },
  includeMult: (record: ORG_MACHINE.IMachine, val: string[] = [], key: string) => {
    if (isEmpty(val)) {
      return true;
    }
    let res = true;
    val.forEach((item) => {
      if (!record[key].includes(item)) res = false;
    });
    return res;
  },
  numberRange: (record: ORG_MACHINE.IMachine, val: string[] = [], key: string) => {
    const [more, less] = val[0] || ([] as any);
    const curVal = get(record, key);
    if (!isNaN(curVal)) {
      if (more && Number(curVal) <= Number(more)) {
        return false;
      }
      if (less && Number(curVal) >= Number(less)) {
        return false;
      }
    }
    return true;
  },
}
Example #3
Source File: in-params-drawer.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
formDataToYmlData = (data: IFormData[]): PIPELINE.IPipelineInParams[] => {
  return compact(
    map(data, (item) => {
      const { key, required, defaultValue, component, labelTip } = item;
      const type = get(find(typeMapping, { component }), 'type') || component;
      let _default = defaultValue as any;
      if (type === 'int') _default = isNaN(+_default) ? undefined : +_default;
      if (type === 'boolean') _default = isBoolean(_default) ? _default : _default === 'true';
      if (!key) return null;
      const dataItem = {
        name: key,
        required,
        default: _default || undefined,
        type,
        desc: labelTip,
      };
      const res = {};
      map(dataItem, (val, k) => {
        if (val !== undefined) {
          res[k] = val;
        }
      });
      return res as PIPELINE.IPipelineInParams;
    }),
  );
}
Example #4
Source File: in-params-drawer.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
ymlDataToFormData = (data: PIPELINE.IPipelineInParams[], val: Obj = {}): IFormData[] => {
  return map(data, (item) => {
    const { name, required = false, default: _default, type, desc, inConfig, value } = item;
    let _defaultVal = val[item.name] !== null && val[item.name] !== undefined ? val[item.name] : _default || undefined;
    const component = get(find(typeMapping, { type }), 'component') || type;
    let labelTip = desc;
    if (_defaultVal !== null && _defaultVal !== undefined && value === _defaultVal) {
      labelTip = `${inConfig ? i18n.t('dop:default value comes from the environment configuration') : ''}; ${desc}`;
    }
    if (type === 'boolean' && _defaultVal !== true) _defaultVal = false;
    if (type === 'int') _defaultVal = _defaultVal === null ? undefined : isNaN(+_defaultVal) ? undefined : +_defaultVal;
    return {
      key: name,
      label: name,
      required,
      defaultValue: _defaultVal,
      component: componentList.includes(component) ? component : 'input',
      labelTip,
    };
  });
}
Example #5
Source File: alert-worker.ts    From prism-frontend with MIT License 5 votes vote down vote up
async function processAlert(alert: Alert, alertRepository: Repository<Alert>) {
  const { baseUrl, serverLayerName, type } = alert.alertConfig;
  const {
    id,
    alertName,
    createdAt,
    email,
    lastTriggered,
    prismUrl,
    active,
  } = alert;
  const availableDates =
    type === 'wms'
      ? await getWMSCapabilities(`${baseUrl}/wms`)
      : await getWCSCoverage(`${baseUrl}`);
  const layerAvailableDates = availableDates[serverLayerName];
  const maxDate = new Date(Math.max(...(layerAvailableDates || [])));

  if (
    !active ||
    isNaN(maxDate.getTime()) ||
    (lastTriggered && lastTriggered >= maxDate) ||
    createdAt >= maxDate
  ) {
    return;
  }

  const alertMessage = await calculateBoundsForAlert(maxDate, alert);

  // Use the URL API to create the url and perform url encoding on all character
  const url = new URL(`/alerts/${id}`, ANALYSIS_API_URL);
  url.searchParams.append('deactivate', 'true');
  url.searchParams.append('email', email);

  if (alertMessage) {
    const emailMessage = `
        Your alert${alertName ? ` ${alertName}` : ''} has been triggered.

        Layer: ${serverLayerName}
        Date: ${maxDate}

        Go to ${prismUrl} for more information.
  
        Alert: ${alertMessage}`;

    const emailHtml = `${emailMessage.replace(
      /(\r\n|\r|\n)/g,
      '<br>',
    )} <br><br>To cancel this alert, click <a href='${url.href}'>here</a>.`;

    console.log(
      `Alert ${id} - '${alert.alertName}' was triggered on ${maxDate}.`,
    );
    // TODO - Send an email using WFP SMTP servers.
    await sendEmail({
      from: '[email protected]',
      to: email,
      subject: `PRISM Alert Triggered`,
      text: emailMessage,
      html: emailHtml,
    });

    console.log(alertMessage);
  }
  // Update lastTriggered (imnactive during testing)
  await alertRepository.update(alert.id, { lastTriggered: maxDate });
}
Example #6
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
getSiteRecords = async (
  dataAsJson: Coords[],
  siteIds: number[],
  regionRepository: Repository<Region>,
) => {
  // Group by site
  const recordsGroupedBySite = groupBy(dataAsJson, 'site');

  // Extract site entities and calculate position of site by averaging all each surveyPoints positions
  const sites = await Promise.all(
    siteIds.map((siteId) => {
      // Filter out NaN values
      const filteredSiteCoords = recordsGroupedBySite[siteId].filter(
        (record) => !isNaN(record.lat) && !isNaN(record.long),
      );

      const siteRecord = filteredSiteCoords.reduce(
        (previous, record) => {
          return {
            ...previous,
            lat: previous.lat + record.lat,
            long: previous.long + record.long,
          };
        },
        { site: siteId, colony: 0, lat: 0, long: 0 },
      );

      // Calculate site position
      const point: Point = createPoint(
        siteRecord.long / filteredSiteCoords.length,
        siteRecord.lat / filteredSiteCoords.length,
      );

      // Augment site information
      const [longitude, latitude] = point.coordinates;
      const timezones = getTimezones(latitude, longitude) as string[];

      return Promise.all([
        getRegion(longitude, latitude, regionRepository),
        getMMM(longitude, latitude),
      ]).then(([region, maxMonthlyMean]) => ({
        name: SITE_PREFIX + siteId,
        polygon: point,
        region,
        maxMonthlyMean,
        display: false,
        timezone: timezones[0],
        status: SiteStatus.Approved,
      }));
    }),
  );

  return { recordsGroupedBySite, sites };
}
Example #7
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
createSurveyPoints = async (
  siteEntities: Site[],
  dbIdToCSVId: Record<number, number>,
  recordsGroupedBySite: Dictionary<Coords[]>,
  rootPath: string,
  poiRepository: Repository<SiteSurveyPoint>,
) => {
  // Create site points of interest entities for each imported site
  // Final result needs to be flattened since the resulting array is grouped by site
  const surveyPoints = siteEntities
    .map((site) => {
      const currentSiteId = dbIdToCSVId[site.id];
      const siteFolder = FOLDER_PREFIX + currentSiteId;
      return recordsGroupedBySite[currentSiteId]
        .filter((record) => {
          const colonyId = record.colony.toString().padStart(3, '0');
          const colonyFolder = COLONY_FOLDER_PREFIX + colonyId;
          const colonyFolderPath = path.join(
            rootPath,
            siteFolder,
            colonyFolder,
          );

          return fs.existsSync(colonyFolderPath);
        })
        .map((record) => {
          const point: Point | undefined =
            !isNaN(record.long) && !isNaN(record.lat)
              ? createPoint(record.long, record.lat)
              : undefined;

          return {
            name: COLONY_PREFIX + record.colony,
            site,
            polygon: point,
          };
        });
    })
    .flat();

  logger.log('Saving site points of interest');
  const poiEntities = await Promise.all(
    surveyPoints.map((poi) =>
      poiRepository
        .save(poi)
        .catch(handleEntityDuplicate(poiRepository, poiQuery, poi.polygon)),
    ),
  );

  return poiEntities;
}
Example #8
Source File: numberUtils.ts    From aqualink-app with MIT License 5 votes vote down vote up
export function formatNumber(n?: number | null, decimal = 0) {
  return isNumber(n) && !isNaN(n) ? n.toFixed(decimal) : "- -";
}
Example #9
Source File: NumberInput.tsx    From react-native-jigsaw with MIT License 5 votes vote down vote up
NumberInput: FC<Props> = ({
  onChangeText,
  value,
  defaultValue,
  ...props
}) => {
  const [currentStringNumberValue, setCurrentStringNumberValue] = useState("0");

  const formatValueToStringNumber = (valueToFormat?: number | string) => {
    if (valueToFormat != null) {
      if (isString(valueToFormat) && valueToFormat !== "") {
        if (/^0[1-9]$/.test(valueToFormat)) {
          return valueToFormat.slice(1);
        } else if (/^[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)$/.test(valueToFormat)) {
          return valueToFormat;
        } else {
          return currentStringNumberValue;
        }
      } else if (isNumber(valueToFormat) && !isNaN(valueToFormat)) {
        return valueToFormat.toString();
      }
    }

    return "0";
  };

  // set currentStringNumberValue as defaultValue prop if there is a differnce on first render only
  useEffect(() => {
    const defaultStringNumberValue = formatValueToStringNumber(defaultValue);

    if (currentStringNumberValue !== defaultStringNumberValue) {
      setCurrentStringNumberValue(defaultStringNumberValue);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const handleChangeText = (newValue: string) => {
    const newStringNumberValue = formatValueToStringNumber(newValue);
    const number = parseFloat(newStringNumberValue);

    setCurrentStringNumberValue(newStringNumberValue);
    onChangeText?.(number);
  };

  // run handleChangeText with value prop only when value prop changes (and first render to reset currentStringNumberValue)
  useEffect(() => {
    const nextStringNumberValue = formatValueToStringNumber(value);

    if (currentStringNumberValue !== nextStringNumberValue) {
      handleChangeText(nextStringNumberValue);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value]);

  return (
    <TextInput
      keyboardType="numeric"
      value={currentStringNumberValue}
      onChangeText={handleChangeText}
      {...props}
    />
  );
}
Example #10
Source File: upload-sheet-data.ts    From aqualink-app with MIT License 4 votes vote down vote up
uploadTimeSeriesData = async (
  filePath: string,
  fileName: string,
  siteId: string,
  surveyPointId: string | undefined,
  sourceType: SourceType,
  repositories: Repositories,
  failOnWarning?: boolean,
  mimetype?: Mimetype,
) => {
  // // TODO
  // // - Add foreign key constraint to sources on site_id
  console.time(`Upload datafile ${fileName}`);
  const { site, surveyPoint } = surveyPointId
    ? await getSiteAndSurveyPoint(
        parseInt(siteId, 10),
        parseInt(surveyPointId, 10),
        repositories.siteRepository,
        repositories.surveyPointRepository,
      )
    : {
        site: await getSite(parseInt(siteId, 10), repositories.siteRepository),
        surveyPoint: undefined,
      };

  const existingSourceEntity = await repositories.sourcesRepository.findOne({
    relations: ['surveyPoint', 'site'],
    where: {
      site: { id: siteId },
      surveyPoint: surveyPointId || null,
      type: sourceType,
    },
  });

  const sourceEntity =
    existingSourceEntity ||
    (await repositories.sourcesRepository.save({
      type: sourceType,
      site,
      surveyPoint,
    }));

  if (
    sourceType === SourceType.SONDE ||
    sourceType === SourceType.METLOG ||
    sourceType === SourceType.HOBO
  ) {
    const workSheetsFromFile = xlsx.parse(filePath, { raw: true });
    const workSheetData = workSheetsFromFile[0]?.data;
    const { ignoredHeaders, importedHeaders } = validateHeaders(
      fileName,
      workSheetData,
      sourceType,
    );

    if (failOnWarning && ignoredHeaders.length > 0) {
      throw new BadRequestException(
        `${fileName}: The columns ${ignoredHeaders
          .map((header) => `"${header}"`)
          .join(
            ', ',
          )} are not configured for import yet and cannot be uploaded.`,
      );
    }

    const signature = await md5Fle(filePath);

    const uploadExists = await repositories.dataUploadsRepository.findOne({
      where: {
        signature,
        site,
        surveyPoint,
        sensorType: sourceType,
      },
    });

    if (uploadExists) {
      throw new ConflictException(
        `${fileName}: A file upload named '${uploadExists.file}' with the same data already exists`,
      );
    }
    console.time(`Get data from sheet ${fileName}`);
    const results = findSheetDataWithHeader(
      fileName,
      workSheetData,
      sourceType,
      mimetype,
    );
    console.timeEnd(`Get data from sheet ${fileName}`);

    console.time(`Remove duplicates and empty values ${fileName}`);
    const data = uniqBy(
      results
        .reduce((timeSeriesObjects: any[], object) => {
          const { timestamp } = object;
          return [
            ...timeSeriesObjects,
            ...Object.keys(object)
              .filter((k) => k !== 'timestamp')
              .map((key) => {
                return {
                  timestamp,
                  value: parseFloat(object[key]),
                  metric: key,
                  source: sourceEntity,
                };
              }),
          ];
        }, [])
        .filter((valueObject) => {
          if (!isNaN(parseFloat(valueObject.value))) {
            return true;
          }
          logger.log('Excluding incompatible value:');
          logger.log(valueObject);
          return false;
        }),
      ({ timestamp, metric, source }) =>
        `${timestamp}, ${metric}, ${source.id}`,
    );
    console.timeEnd(`Remove duplicates and empty values ${fileName}`);

    const minDate = get(
      minBy(data, (item) => new Date(get(item, 'timestamp')).getTime()),
      'timestamp',
    );
    const maxDate = get(
      maxBy(data, (item) => new Date(get(item, 'timestamp')).getTime()),
      'timestamp',
    );

    // Initialize google cloud service, to be used for media upload
    const googleCloudService = new GoogleCloudService();

    // Note this may fail. It would still return a location, but the file may not have been uploaded
    const fileLocation = googleCloudService.uploadFileAsync(
      filePath,
      sourceType,
      'data_uploads',
      'data_upload',
    );

    const dataUploadsFile = await repositories.dataUploadsRepository.save({
      file: fileName,
      signature,
      sensorType: sourceType,
      site,
      surveyPoint,
      minDate,
      maxDate,
      metrics: importedHeaders,
      fileLocation,
    });

    const dataAsTimeSeries = data.map((x: any) => {
      return {
        timestamp: x.timestamp,
        value: x.value,
        metric: x.metric,
        source: x.source,
        dataUpload: dataUploadsFile,
      };
    });

    // Data is too big to added with one bulk insert so we batch the upload.
    console.time(`Loading into DB ${fileName}`);
    const batchSize = 100;
    logger.log(`Saving time series data in batches of ${batchSize}`);
    const inserts = chunk(dataAsTimeSeries, batchSize).map(
      async (batch: any[]) => {
        try {
          await repositories.timeSeriesRepository
            .createQueryBuilder('time_series')
            .insert()
            .values(batch)
            // If there's a conflict, replace data with the new value.
            // onConflict is deprecated, but updating it is tricky.
            // See https://github.com/typeorm/typeorm/issues/8731?fbclid=IwAR2Obg9eObtGNRXaFrtKvkvvVSWfvjtHpFu-VEM47yg89SZcPpxEcZOmcLw
            .onConflict(
              'ON CONSTRAINT "no_duplicate_data" DO UPDATE SET "value" = excluded.value',
            )
            .execute();
        } catch {
          console.warn('The following batch failed to upload:');
          console.warn(batch);
        }
        return true;
      },
    );

    // Return insert promises and print progress updates
    const actionsLength = inserts.length;
    await Bluebird.Promise.each(inserts, (props, idx) => {
      logger.log(`Saved ${idx + 1} out of ${actionsLength} batches`);
    });
    console.timeEnd(`Loading into DB ${fileName}`);
    logger.log('loading complete');

    refreshMaterializedView(repositories);

    console.timeEnd(`Upload datafile ${fileName}`);
    return ignoredHeaders;
  }

  return [];
}
Example #11
Source File: index.tsx    From aqualink-app with MIT License 4 votes vote down vote up
MultipleSensorsCharts = ({
  site,
  pointId,
  surveysFiltered,
  disableGutters,
  displayOceanSenseCharts,
}: MultipleSensorsChartsProps) => {
  const classes = useStyles();
  const dispatch = useDispatch();
  const getQueryParam = useQueryParams();
  const [startDateParam, endDateParam, initialChart] = [
    "start",
    "end",
    "chart",
  ].map(getQueryParam);
  const initialStart =
    startDateParam && isISODate(startDateParam) ? startDateParam : undefined;
  const initialEnd =
    endDateParam && isISODate(endDateParam) ? endDateParam : undefined;
  const granularDailyData = useSelector(siteGranularDailyDataSelector);
  const timeSeriesData = useSelector(siteTimeSeriesDataSelector);
  const oceanSenseData = useSelector(siteOceanSenseDataSelector);
  const latestData = useSelector(latestDataSelector);
  const { bottomTemperature, topTemperature } = timeSeriesData || {};
  const { hobo: hoboBottomTemperature } = bottomTemperature || {};
  const timeSeriesDataRanges = useSelector(siteTimeSeriesDataRangeSelector);
  const { hobo: hoboBottomTemperatureRange } =
    timeSeriesDataRanges?.bottomTemperature || {};
  const rangesLoading = useSelector(siteTimeSeriesDataRangeLoadingSelector);
  const [pickerEndDate, setPickerEndDate] = useState<string>();
  const [pickerStartDate, setPickerStartDate] = useState<string>();
  const [endDate, setEndDate] = useState<string>();
  const [startDate, setStartDate] = useState<string>();
  const [pickerErrored, setPickerErrored] = useState(false);
  const [range, setRange] = useState<RangeValue>(
    initialStart || initialEnd ? "custom" : "one_month"
  );
  const history = useHistory();

  const today = localizedEndOfDay(undefined, site.timezone);

  const hasSpotterData = Boolean(
    bottomTemperature?.spotter?.data?.[1] || topTemperature?.spotter?.data?.[1]
  );

  const hasSondeData = Boolean(
    latestData?.some((data) => data.source === "sonde")
  );

  const hasMetlogData = Boolean(
    latestData?.some((data) => data.source === "metlog")
  );

  const chartStartDate = startDate || subtractFromDate(today, "week");
  const chartEndDate = moment
    .min(
      moment(),
      moment(endDate)
        .tz(site.timezone || "UTC")
        .endOf("day")
    )
    .toISOString();

  const hasOceanSenseId = Boolean(oceanSenseConfig?.[site.id]);

  const tempAnalysisDatasets = generateTempAnalysisDatasets(
    granularDailyData,
    timeSeriesData?.bottomTemperature?.spotter?.data,
    timeSeriesData?.topTemperature?.spotter?.data,
    hoboBottomTemperature?.data,
    site.historicalMonthlyMean,
    startDate,
    endDate,
    chartStartDate,
    chartEndDate,
    site.timezone,
    site.depth
  );

  const spotterMetricDataset = (metric: Metrics) => {
    const { unit, convert } = spotterConfig[metric] || {};

    return generateMetricDataset(
      "SENSOR",
      timeSeriesData?.[metric]?.spotter?.data?.map((item) => ({
        ...item,
        value: convert ? convert * item.value : item.value,
      })) || [],
      unit || "",
      SPOTTER_METRIC_DATA_COLOR,
      chartStartDate,
      chartEndDate,
      site.timezone
    );
  };

  const sondeDatasets = () =>
    hasSondeData
      ? sortBy(getPublicSondeMetrics(), (key) => getSondeConfig(key).order)
          .filter(
            (key) =>
              timeSeriesData?.[camelCase(key) as Metrics]?.sonde?.data?.length
          )
          .map((key) => {
            const { data, surveyPoint } =
              timeSeriesData?.[camelCase(key) as Metrics]?.sonde || {};
            const { title, units } = getSondeConfig(key);

            return {
              key,
              title,
              surveyPoint,
              dataset: generateMetricDataset(
                "SENSOR",
                data || [],
                units,
                SONDE_DATA_COLOR,
                chartStartDate,
                chartEndDate,
                site.timezone
              ),
            };
          })
      : [];

  const metlogDatasets = () =>
    hasMetlogData
      ? sortBy(getPublicMetlogMetrics(), (key) => getMetlogConfig(key).order)
          .filter(
            (key) =>
              timeSeriesData?.[camelCase(key) as Metrics]?.metlog?.data?.length
          )
          .map((key) => {
            const { data, surveyPoint } =
              timeSeriesData?.[camelCase(key) as Metrics]?.metlog || {};
            const { title, units, convert } = getMetlogConfig(key);

            return {
              key,
              title,
              surveyPoint,
              dataset: generateMetricDataset(
                "SENSOR",
                (data || []).map((item) => ({
                  ...item,
                  value: isNumber(convert) ? item.value * convert : item.value,
                })),
                units,
                METLOG_DATA_COLOR,
                chartStartDate,
                chartEndDate,
                site.timezone
              ),
            };
          })
      : [];

  // Scroll to the chart defined by the initialChart query param.
  useEffect(() => {
    if (initialChart) {
      const chartElement = document.getElementById(initialChart);
      chartElement?.scrollIntoView();
    }
  }, [initialChart]);

  // Set pickers initial values once the range request is completed
  useEffect(() => {
    if (!rangesLoading && !pickerStartDate && !pickerEndDate) {
      const { maxDate } = hoboBottomTemperatureRange?.data?.[0] || {};
      const localizedMaxDate = localizedEndOfDay(maxDate, site.timezone);
      const pastOneMonth = moment(
        subtractFromDate(localizedMaxDate || today, "month", 1)
      )
        .tz(site.timezone || "UTC")
        .startOf("day")
        .toISOString();
      setPickerStartDate(
        initialStart
          ? zonedTimeToUtc(initialStart, site.timezone || "UTC").toISOString()
          : utcToZonedTime(pastOneMonth, site.timezone || "UTC").toISOString()
      );
      setPickerEndDate(
        initialEnd
          ? zonedTimeToUtc(initialEnd, site.timezone || "UTC").toISOString()
          : utcToZonedTime(
              localizedMaxDate || today,
              site.timezone || "UTC"
            ).toISOString()
      );
    }
  }, [
    hoboBottomTemperatureRange,
    initialEnd,
    initialStart,
    pickerStartDate,
    pickerEndDate,
    rangesLoading,
    site.timezone,
    today,
  ]);

  // Get time series data
  useEffect(() => {
    if (
      pickerStartDate &&
      pickerEndDate &&
      isBefore(pickerStartDate, pickerEndDate)
    ) {
      const siteLocalStartDate = setTimeZone(
        new Date(pickerStartDate),
        site.timezone
      );

      const siteLocalEndDate = setTimeZone(
        new Date(pickerEndDate),
        site.timezone
      );

      dispatch(
        siteTimeSeriesDataRequest({
          siteId: `${site.id}`,
          pointId,
          start: siteLocalStartDate,
          end: siteLocalEndDate,
          metrics: hasSondeData || hasMetlogData ? undefined : DEFAULT_METRICS,
          hourly:
            moment(siteLocalEndDate).diff(moment(siteLocalStartDate), "days") >
            2,
        })
      );

      if (hasOceanSenseId) {
        dispatch(
          siteOceanSenseDataRequest({
            sensorID: oceanSenseConfig[site.id],
            startDate: siteLocalStartDate,
            endDate: siteLocalEndDate,
            latest: false,
          })
        );
      }
    }
  }, [
    dispatch,
    hasMetlogData,
    hasOceanSenseId,
    hasSondeData,
    pickerEndDate,
    pickerStartDate,
    pointId,
    site.id,
    site.timezone,
  ]);

  // Set chart start/end dates based on data received
  useEffect(() => {
    const pickerLocalEndDate = new Date(
      setTimeZone(
        new Date(moment(pickerEndDate).format("MM/DD/YYYY")),
        site?.timezone
      )
    ).toISOString();
    const pickerLocalStartDate = new Date(
      setTimeZone(
        new Date(moment(pickerStartDate).format("MM/DD/YYYY")),
        site?.timezone
      )
    ).toISOString();

    const [minDataDate, maxDataDate] = findDataLimits(
      site.historicalMonthlyMean,
      granularDailyData,
      timeSeriesData,
      pickerLocalStartDate,
      localizedEndOfDay(pickerLocalEndDate, site.timezone)
    );

    setStartDate(
      minDataDate
        ? moment
            .max(moment(minDataDate), moment(pickerLocalStartDate))
            .toISOString()
        : pickerLocalStartDate
    );

    setEndDate(
      maxDataDate
        ? moment
            .min(moment(maxDataDate), moment(pickerLocalEndDate).endOf("day"))
            .toISOString()
        : moment(pickerLocalEndDate).endOf("day").toISOString()
    );
  }, [granularDailyData, pickerEndDate, pickerStartDate, site, timeSeriesData]);

  useEffect(() => {
    if (pickerStartDate && pickerEndDate) {
      // eslint-disable-next-line fp/no-mutating-methods
      history.push({
        search: `?start=${pickerStartDate.split("T")[0]}&end=${
          pickerEndDate.split("T")[0]
        }`,
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [history, pickerEndDate, pickerStartDate, site.timezone]);

  // Set picker error
  useEffect(() => {
    if (pickerStartDate && pickerEndDate) {
      setPickerErrored(!isBefore(pickerStartDate, pickerEndDate));
    }
  }, [pickerEndDate, pickerStartDate]);

  const dataForCsv = [
    ...tempAnalysisDatasets.map((dataset) => ({
      name: `${dataset.metric || "unknownMetric"}_${
        dataset.source || "unknownSource"
      }`,
      values: dataset.data,
    })),
    ...Object.entries(spotterConfig).map(([key]) => {
      const dataset = spotterMetricDataset(key as Metrics);
      return {
        name: `${key}_spotter`,
        values: dataset.data,
      };
    }),
    ...Object.entries(constructOceanSenseDatasets(oceanSenseData)).map(
      ([key, item]) => {
        const dataset = generateMetricDataset(
          key,
          item.data,
          item.unit,
          OCEAN_SENSE_DATA_COLOR,
          chartStartDate,
          chartEndDate,
          site.timezone
        );
        return {
          name: `${camelCase(item.title.split(" ")[0])}`,
          values: dataset.data,
        };
      }
    ),
    ...sondeDatasets().map(({ title, dataset }) => ({
      name: `${title} ${dataset.label}`,
      values: dataset.data,
    })),
  ].filter((x) => x.values.length > 0);

  const onRangeChange = (value: RangeValue) => {
    const { minDate, maxDate } = hoboBottomTemperatureRange?.data?.[0] || {};
    const localizedMinDate = new Date(
      moment(minDate)
        .tz(site.timezone || "UTC")
        .format("MM/DD/YYYY")
    ).toISOString();
    const localizedMaxDate = new Date(
      moment(maxDate)
        .tz(site.timezone || "UTC")
        .format("MM/DD/YYYY")
    ).toISOString();
    setRange(value);
    switch (value) {
      case "one_month":
        setPickerEndDate(moment(localizedMaxDate).endOf("day").toISOString());
        setPickerStartDate(subtractFromDate(localizedMaxDate, "month", 1));
        break;
      case "one_year":
        setPickerEndDate(moment(localizedMaxDate).endOf("day").toISOString());
        setPickerStartDate(subtractFromDate(localizedMaxDate, "year"));
        break;
      case "max":
        setPickerEndDate(moment(localizedMaxDate).endOf("day").toISOString());
        setPickerStartDate(localizedMinDate);
        break;
      default:
        break;
    }
  };

  const onPickerDateChange = (type: "start" | "end") => (date: Date | null) => {
    const time = date?.getTime();
    if (date && time && !isNaN(time)) {
      const dateString = date.toISOString();
      setRange("custom");
      switch (type) {
        case "start":
          // Set picker start date only if input date is after zero time
          if (
            moment(dateString)
              .startOf("day")
              .isSameOrAfter(moment(0).startOf("day"))
          ) {
            setPickerStartDate(moment(dateString).startOf("day").toISOString());
          }
          break;
        case "end":
          // Set picker end date only if input date is before today
          if (
            moment(dateString)
              .endOf("day")
              .isSameOrBefore(moment().endOf("day"))
          ) {
            setPickerEndDate(moment(dateString).endOf("day").toISOString());
          }
          break;
        default:
          break;
      }
    }
  };

  return (
    <Container
      disableGutters={disableGutters}
      className={classes.chartWithRange}
    >
      <div className={classes.buttonWrapper}>
        <DownloadCSVButton
          data={dataForCsv}
          startDate={pickerStartDate}
          endDate={pickerEndDate}
          siteId={site.id}
          pointId={pointId}
          className={classes.button}
        />
      </div>
      <ChartWithCard
        id="temperature"
        range={range}
        onRangeChange={onRangeChange}
        disableMaxRange={!hoboBottomTemperatureRange?.data?.[0]}
        chartTitle="TEMPERATURE ANALYSIS"
        availableRanges={[
          {
            name: "Spotter",
            data: timeSeriesDataRanges?.bottomTemperature?.spotter?.data,
          },
          {
            name: "HOBO",
            data: timeSeriesDataRanges?.bottomTemperature?.hobo?.data,
          },
        ]}
        timeZone={site.timezone}
        chartWidth={findChartWidth(tempAnalysisDatasets)}
        site={site}
        datasets={tempAnalysisDatasets}
        pointId={pointId ? parseInt(pointId, 10) : undefined}
        pickerStartDate={pickerStartDate || subtractFromDate(today, "week")}
        pickerEndDate={pickerEndDate || today}
        chartStartDate={chartStartDate}
        chartEndDate={chartEndDate}
        onStartDateChange={onPickerDateChange("start")}
        onEndDateChange={onPickerDateChange("end")}
        isPickerErrored={pickerErrored}
        areSurveysFiltered={surveysFiltered}
      />
      {hasSpotterData &&
        Object.entries(spotterConfig).map(([key, { title }]) => (
          <Box mt={4} key={title}>
            <ChartWithCard
              datasets={[spotterMetricDataset(key as Metrics)]}
              id={key}
              range={range}
              onRangeChange={onRangeChange}
              disableMaxRange={!hoboBottomTemperatureRange?.data?.[0]}
              chartTitle={title}
              availableRanges={[
                {
                  name: "Spotter",
                  data: timeSeriesDataRanges?.[key as Metrics]?.spotter?.data,
                },
              ]}
              timeZone={site.timezone}
              showRangeButtons={false}
              chartWidth="large"
              site={site}
              pickerStartDate={
                pickerStartDate || subtractFromDate(today, "week")
              }
              pickerEndDate={pickerEndDate || today}
              chartStartDate={chartStartDate}
              chartEndDate={chartEndDate}
              onStartDateChange={onPickerDateChange("start")}
              onEndDateChange={onPickerDateChange("end")}
              isPickerErrored={pickerErrored}
              showDatePickers={false}
              hideYAxisUnits
              cardColumnJustification="flex-start"
            />
          </Box>
        ))}
      {displayOceanSenseCharts &&
        hasOceanSenseId &&
        Object.entries(constructOceanSenseDatasets(oceanSenseData)).map(
          ([key, item]) => (
            <Box mt={4} key={item.title}>
              <ChartWithCard
                datasets={[
                  generateMetricDataset(
                    key,
                    item.data,
                    item.unit,
                    OCEAN_SENSE_DATA_COLOR,
                    chartStartDate,
                    chartEndDate,
                    site.timezone
                  ),
                ]}
                id={item.id}
                range={range}
                onRangeChange={onRangeChange}
                disableMaxRange={!hoboBottomTemperatureRange?.data?.[0]}
                chartTitle={item.title}
                timeZone={site.timezone}
                showRangeButtons={false}
                chartWidth="large"
                site={site}
                pickerStartDate={
                  pickerStartDate || subtractFromDate(today, "week")
                }
                pickerEndDate={pickerEndDate || today}
                chartStartDate={chartStartDate}
                chartEndDate={chartEndDate}
                onStartDateChange={onPickerDateChange("start")}
                onEndDateChange={onPickerDateChange("end")}
                isPickerErrored={pickerErrored}
                showDatePickers={false}
                hideYAxisUnits
                cardColumnJustification="flex-start"
              />
            </Box>
          )
        )}
      {sondeDatasets().map(({ key, title, surveyPoint, dataset }) => (
        <Box mt={4} key={key}>
          <ChartWithCard
            datasets={[dataset]}
            id={key}
            range={range}
            onRangeChange={onRangeChange}
            disableMaxRange={!hoboBottomTemperatureRange?.data?.[0]}
            chartTitle={title}
            availableRanges={[
              {
                name: "Sonde",
                data: timeSeriesDataRanges?.[camelCase(key) as Metrics]?.sonde
                  ?.data,
              },
            ]}
            timeZone={site.timezone}
            showRangeButtons={false}
            chartWidth="large"
            site={site}
            pickerStartDate={pickerStartDate || subtractFromDate(today, "week")}
            pickerEndDate={pickerEndDate || today}
            chartStartDate={chartStartDate}
            chartEndDate={chartEndDate}
            onStartDateChange={onPickerDateChange("start")}
            onEndDateChange={onPickerDateChange("end")}
            isPickerErrored={pickerErrored}
            showDatePickers={false}
            surveyPoint={surveyPoint}
            hideYAxisUnits
            cardColumnJustification="flex-start"
          />
        </Box>
      ))}
      {metlogDatasets().map(({ key, title, surveyPoint, dataset }) => (
        <Box mt={4} key={key}>
          <ChartWithCard
            datasets={[dataset]}
            id={key}
            range={range}
            onRangeChange={onRangeChange}
            disableMaxRange={!hoboBottomTemperatureRange?.data?.[0]}
            chartTitle={title}
            availableRanges={[
              {
                name: "Meteorological",
                data: timeSeriesDataRanges?.[camelCase(key) as Metrics]?.metlog
                  ?.data,
              },
            ]}
            timeZone={site.timezone}
            showRangeButtons={false}
            chartWidth="large"
            site={site}
            pickerStartDate={pickerStartDate || subtractFromDate(today, "week")}
            pickerEndDate={pickerEndDate || today}
            chartStartDate={chartStartDate}
            chartEndDate={chartEndDate}
            onStartDateChange={onPickerDateChange("start")}
            onEndDateChange={onPickerDateChange("end")}
            isPickerErrored={pickerErrored}
            showDatePickers={false}
            surveyPoint={surveyPoint}
            hideYAxisUnits
            cardColumnJustification="flex-start"
          />
        </Box>
      ))}
    </Container>
  );
}
Example #12
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
CaseTable = ({
  query: queryProp,
  columns,
  onClickRow,
  scope,
  onChange,
  testPlanId,
  modalQuery = {},
  slot,
}: IProps) => {
  const isModal = scope === 'caseModal';
  const client = layoutStore.useStore((s) => s.client);
  const projectInfo = projectStore.useStore((s) => s.info);
  const { getCases: oldGetCases } = testCaseStore.effects;
  const { updateBreadcrumb } = testSetStore.effects;
  const [loading] = useLoading(testCaseStore, ['getCases']);
  const routeQuery = queryProp || routeInfoStore.useStore((s) => s.query);
  const [metaFields, pageCaseList, pageCaseTotal, modalCaseList, modalCaseTotal] = testCaseStore.useStore((s) => [
    s.metaFields,
    s.caseList,
    s.caseTotal,
    s.modalCaseList,
    s.modalCaseTotal,
  ]);
  const caseList = isModal ? modalCaseList : pageCaseList;
  const caseTotal = (isModal ? modalCaseTotal : pageCaseTotal) as number;

  const getCases = useCallback(
    (payload: any) => {
      oldGetCases({ selected: [], ...payload, scope, testPlanId });
    },
    [scope, oldGetCases, testPlanId],
  );

  // 使用弹框里可能传入的参数覆盖url上的参数
  const query = { ...routeQuery, ...modalQuery };
  const { dataSource, expandedRowKeys } = useMemo(
    () => getDataSource(caseList, scope, projectInfo.name),
    [caseList, projectInfo.name, scope],
  );

  useEffect(() => {
    if (query.caseId && onClickRow) {
      let target: any;
      forEach(dataSource, (item) => {
        target = target || find(item.children, { id: +query.caseId });
      });
      if (target) {
        updateBreadcrumb({
          pathName: target.parent.directory,
          testSetID: target.testSetID,
          testPlanID: testPlanId,
        });
        onClickRow(target, dataSource);
      }
    }
  }, [query.caseId, dataSource, onClickRow]);

  // 计算是否需要左右滑动
  const ref = useRef(null);
  const hasMount = !!ref.current;
  // 不必去除 react-hooks/exhaustive-deps, 无法识别 clientWidth
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const tableWidth = useMemo(
    () => get(document.querySelector('.case-table'), ['clientWidth'], 0),
    [client.width, hasMount],
  );
  const tempProps: any = {};
  const className = 'case-table';
  // if (mode !== 'modal') { // 非弹框
  const configWidth = reduce(
    columns,
    (temp, { width }: any) => temp + (isNaN(parseInt(width, 10)) ? 380 : parseInt(width, 10)),
    0,
  );
  // 操作列
  const operationWidth = (columns[columns.length - 1].width || 60) as number;
  if (tableWidth + operationWidth < configWidth) {
    tempProps.scroll = { x: configWidth };
  }
  // }

  const isScroll = !!tempProps.scroll;
  const { orderBy, orderRule } = query;
  const metaMap = useMemo(() => {
    const temp = {};
    forEach(metaFields, ({ enums }) => {
      forEach(enums, ({ fieldUniqueName, showName, value }) => {
        temp[`${fieldUniqueName}-${value}`] = showName;
      });
    });
    return temp;
  }, [metaFields]);

  const newColumns = useMemo(() => {
    // 初始化表格columns
    const tempColumns = cloneDeep(columns);
    // ID列
    const idColumn = find(tempColumns, ({ key }: any) => key === 'id');
    if (idColumn) {
      Object.assign(idColumn, {
        dataIndex: 'id',
        className: 'case-table-id',
        render: (id: string, record: any) => {
          if (id) {
            return <span className="text-normal">{`#${record.testCaseID}`}</span>;
          }
          return {
            children: <Ellipsis className="text-desc" title={record.directory} />,
            props: {
              colSpan: 5,
            },
          };
        },
      });
    }
    // 标题列(只针对当前页操作)
    const nameColumn = find(tempColumns, ({ key }: any) => key === 'name');
    if (nameColumn) {
      Object.assign(nameColumn, {
        // title: <ChooseTitle mode={mode} />,
        title: <span>{i18n.t('dop:Use case title')}</span>,
        render: (name: string, record: any) => {
          const obj = {
            children: <Ellipsis className="font-bold" title={name} />,
            props: {},
          };
          if (!record.id) {
            obj.props.colSpan = 0;
          }
          return obj;
        },
      });
    }
    // 全选列
    const checkColumn = find(tempColumns, ({ key }: any) => key === 'checkbox');
    if (checkColumn) {
      Object.assign(checkColumn, {
        title: <AllCheckBox mode={scope} />,
        className: 'case-table-checkbox',
        render: (_text: any, record: any) => {
          if (record.id) {
            return <CaseCheckBox mode={scope} id={record.id} />;
          }
          return <CustomIcon type="wjj1" className="text-warning" />;
        },
      });
    }
    // 正在排序的列
    if (orderBy) {
      const sortColumn = find(tempColumns, ({ key }: any) => key === orderBy);
      if (!sortColumn || !biggerSorterMap[orderRule]) return;
      Object.assign(sortColumn, {
        defaultSortOrder: biggerSorterMap[orderRule],
      });
    }
    // 测试元数据相关列
    forEach(tempColumns, (single: any) => {
      const { dataIndex, key } = single;
      const fieldUniqueName = metaColumnsMap[dataIndex] || metaColumnsMap[key];
      if (fieldUniqueName && !single.render) {
        // eslint-disable-next-line no-param-reassign
        single.render = (value: any) => metaMap[`${fieldUniqueName}-${value}`] || value;
      }
    });
    // 操作列
    const operationColumn = filter(tempColumns, ({ fixed }: any) => fixed);
    if (!isEmpty(operationColumn)) {
      operationColumn.width = 120;
      forEach(operationColumn, (single: any) => {
        Object.assign(single, {
          fixed: isScroll ? single.fixed : undefined,
        });
      });
    }

    return tempColumns;
  }, [orderBy, orderRule, columns, scope, metaMap, isScroll]);

  const handleChange = (pagination: any, _filters: any, sorter: any) => {
    let newSorter = {};
    if (!sorter?.order) {
      delete query.orderBy;
      delete query.orderRule;
      newSorter = { orderBy: undefined, orderRule: undefined };
    } else {
      newSorter = { orderBy: sorter.columnKey, orderRule: sorterMap[sorter.order] };
    }
    const newQuery = { ...query, testPlanId, pageNo: pagination.current, pageSize: pagination.pageSize, ...newSorter };
    // 不在弹框里时,不改变url参数
    if (scope !== 'caseModal') {
      updateSearch(newQuery);
    }
    getCases(newQuery);
    if (onChange) {
      onChange(newQuery);
    }
  };

  const handleClickRow = (record: TEST_CASE.CaseTableRecord) => {
    if (record.children) {
      return;
    }
    updateBreadcrumb({
      pathName: record.parent.directory,
      testSetID: record.testSetID,
      testPlanID: testPlanId,
    });
    onClickRow && onClickRow(record, dataSource);
  };

  return (
    <ErdaTable
      ref={ref}
      loading={loading}
      className={className}
      indentSize={0}
      expandedRowKeys={expandedRowKeys}
      dataSource={dataSource}
      columns={newColumns}
      rowKey={(record: any) => `${record.testSetID}-${record.id || ''}`}
      onChange={handleChange}
      expandIcon={() => null}
      onRow={(record: TEST_CASE.CaseTableRecord) => ({
        onClick: () => handleClickRow(record),
      })}
      slot={slot}
      {...tempProps}
      pagination={{
        total: caseTotal,
        current: parseInt(query.pageNo, 10) || defaultPageNo,
        pageSize: parseInt(query.pageSize, 10) || defaultPageSize,
        showSizeChanger: true,
      }}
    />
  );
}