getUserMessages()

in x-pack/platform/plugins/shared/lens/public/visualizations/xy/visualization.tsx [830:1066]


  getUserMessages(state, { frame }) {
    const { datasourceLayers, dataViews, activeData } = frame;
    const annotationLayers = getAnnotationsLayers(state.layers);
    const errors: UserMessage[] = [];

    const hasDateHistogram = isTimeChart(getDataLayers(state.layers), frame);

    annotationLayers.forEach((layer) => {
      layer.annotations.forEach((annotation) => {
        if (!hasDateHistogram) {
          errors.push({
            uniqueId: ANNOTATION_MISSING_DATE_HISTOGRAM,
            severity: 'error',
            fixableInEditor: true,
            displayLocations: [{ id: 'dimensionButton', dimensionId: annotation.id }],
            shortMessage: i18n.translate(
              'xpack.lens.xyChart.addAnnotationsLayerLabelDisabledHelp',
              {
                defaultMessage:
                  'Annotations require a time based chart to work. Add a date histogram.',
              }
            ),
            longMessage: '',
          });
        }

        const errorMessages = getAnnotationLayerErrors(layer, annotation.id, dataViews);
        errors.push(...errorMessages);
      });
    });

    // check if the layers in the state are compatible with this type of chart
    if (state && state.layers.length > 1) {
      // Order is important here: Y Axis is fundamental to exist to make it valid
      const yLayerValidation = validateLayersForDimension(
        'y',
        state.layers,
        ({ accessors }) => accessors == null || accessors.length === 0 // has no accessor
      );
      if (!yLayerValidation.valid) {
        errors.push(yLayerValidation.error);
      }

      const breakDownLayerValidation = validateLayersForDimension(
        'break_down',
        state.layers,
        ({ splitAccessor, seriesType }) =>
          seriesType.includes('percentage') && splitAccessor == null // check if no split accessor
      );
      if (!breakDownLayerValidation.valid) {
        errors.push(breakDownLayerValidation.error);
      }
    }
    // temporary fix for #87068
    errors.push(
      ...checkXAccessorCompatibility(state, datasourceLayers).map<UserMessage>(
        ({ shortMessage, longMessage }) => ({
          severity: 'error',
          uniqueId: XY_X_WRONG_DATA_TYPE,
          fixableInEditor: true,
          displayLocations: [{ id: 'visualization' }],
          shortMessage,
          longMessage,
        })
      )
    );

    for (const layer of getDataLayers(state.layers)) {
      const datasourceAPI = datasourceLayers[layer.layerId];
      if (datasourceAPI) {
        for (const accessor of layer.accessors) {
          const operation = datasourceAPI.getOperationForColumnId(accessor);
          if (operation && operation.dataType !== 'number') {
            errors.push({
              uniqueId: XY_Y_WRONG_DATA_TYPE,
              severity: 'error',
              fixableInEditor: true,
              displayLocations: [{ id: 'visualization' }],
              shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureYShort', {
                defaultMessage: `Wrong data type for {axis}.`,
                values: {
                  axis: getAxisName('y', { isHorizontal: isHorizontalChart(state.layers) }),
                },
              }),
              longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureYLong', {
                defaultMessage: `The dimension {label} provided for the {axis} has the wrong data type. Expected number but have {dataType}`,
                values: {
                  label: operation.label,
                  dataType: operation.dataType,
                  axis: getAxisName('y', { isHorizontal: isHorizontalChart(state.layers) }),
                },
              }),
            });
          }
        }
      }
    }

    const warnings: UserMessage[] = [];

    if (state?.layers.length > 0 && activeData) {
      const filteredLayers = [
        ...getDataLayers(state.layers),
        ...getReferenceLayers(state.layers),
      ].filter(({ accessors }) => accessors.length > 0);

      const accessorsWithArrayValues = [];

      for (const layer of filteredLayers) {
        const { layerId, accessors } = layer;
        const rows = activeData?.[layerId] && activeData[layerId].rows;
        if (!rows) {
          break;
        }
        const columnToLabel = getColumnToLabelMap(layer, datasourceLayers[layerId]);
        for (const accessor of accessors) {
          const hasArrayValues = rows.some((row) => Array.isArray(row[accessor]));
          if (hasArrayValues) {
            accessorsWithArrayValues.push(columnToLabel[accessor]);
          }
        }
      }

      accessorsWithArrayValues.forEach((label) =>
        warnings.push({
          uniqueId: XY_RENDER_ARRAY_VALUES,
          severity: 'warning',
          fixableInEditor: true,
          displayLocations: [{ id: 'toolbar' }],
          shortMessage: '',
          longMessage: (
            <FormattedMessage
              key={label}
              id="xpack.lens.xyVisualization.arrayValues"
              defaultMessage="{label} contains array values. Your visualization may not render as expected."
              values={{
                label: <strong>{label}</strong>,
              }}
            />
          ),
        })
      );
    }

    const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
    const dataLayers = getDataLayers(state.layers);
    const axisGroups = getAxesConfiguration(dataLayers, shouldRotate, frame.activeData);
    const logAxisGroups = axisGroups.filter(
      ({ groupId }) =>
        (groupId === 'left' && state.yLeftScale === 'log') ||
        (groupId === 'right' && state.yRightScale === 'log')
    );

    if (logAxisGroups.length > 0) {
      logAxisGroups
        .map((axis) => {
          const mixedDomainSeries = axis.series.filter((series) => {
            let hasNegValues = false;
            let hasPosValues = false;
            const arr = activeData?.[series.layer]?.rows ?? [];
            for (let index = 0; index < arr.length; index++) {
              const value = arr[index][series.accessor];

              if (value < 0) {
                hasNegValues = true;
              } else {
                hasPosValues = true;
              }

              if (hasNegValues && hasPosValues) {
                return true;
              }
            }

            return false;
          });
          return {
            ...axis,
            mixedDomainSeries,
          };
        })
        .forEach((axisGroup) => {
          if (axisGroup.mixedDomainSeries.length === 0) return;
          const { groupId } = axisGroup;

          warnings.push({
            // TODO: can we push the group into the metadata and use a correct unique ID here?
            uniqueId: `${XY_MIXED_LOG_SCALE}${groupId}`,
            severity: 'warning',
            shortMessage: '',
            longMessage: (
              <FormattedMessage
                id="xpack.lens.xyVisualization.mixedLogScaleWarning"
                defaultMessage="When the {axisName} axis is set to logarithmic scale, the dataset should not contain positive and negative data."
                values={{
                  axisName:
                    groupId === 'left' ? (
                      <FormattedMessage
                        id="xpack.lens.xyVisualization.mixedLogScaleWarningLeft"
                        defaultMessage="left"
                      />
                    ) : (
                      <FormattedMessage
                        id="xpack.lens.xyVisualization.mixedLogScaleWarningRight"
                        defaultMessage="right"
                      />
                    ),
                }}
              />
            ),
            displayLocations: [{ id: 'toolbar' }],
            fixableInEditor: true,
          });

          axisGroup.mixedDomainSeries.forEach(({ accessor }) => {
            warnings.push({
              // TODO: can we push the group into the metadata and use a correct unique ID here?
              uniqueId: `${XY_MIXED_LOG_SCALE_DIMENSION}${accessor}`,
              severity: 'warning',
              shortMessage: '',
              longMessage: (
                <FormattedMessage
                  id="xpack.lens.xyVisualization.mixedLogScaleDimensionWarning"
                  defaultMessage="This metric is using logarithmic scale and should not contain positive and negative data."
                />
              ),
              displayLocations: [{ id: 'dimensionButton', dimensionId: accessor }],
              fixableInEditor: true,
            });
          });
        });
    }

    const info = getNotifiableFeatures(state, frame, paletteService, fieldFormats);

    return errors.concat(warnings, info);
  },