lodash#identity TypeScript Examples

The following examples show how to use lodash#identity. 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: utilities.ts    From react-native-jigsaw with MIT License 7 votes vote down vote up
export function extractBorderAndMarginStyles(
  style: StyleProp<any>,
  additionalBorderStyles?: string[],
  additionalMarginStyles?: string[]
) {
  const flatStyle = StyleSheet.flatten(style || {});

  const borderStyles = pickBy(
    pick(flatStyle, [
      ...borderStyleNames,
      ...(additionalBorderStyles ? additionalBorderStyles : []),
    ]),
    identity
  );

  const marginStyles = pickBy(
    pick(flatStyle, [
      ...marginStyleNames,
      ...(additionalMarginStyles ? additionalMarginStyles : []),
    ]),
    identity
  );

  return { borderStyles, marginStyles };
}
Example #2
Source File: flow.spec.ts    From s-libs with MIT License 6 votes vote down vote up
describe('flow()', () => {
  //
  // stolen from https://github.com/lodash/lodash
  //

  it('should supply each function with the return value of the previous', () => {
    const increment = (x: number): number => x + 1;
    const square = (x: number): number => x * x;
    const fixed = (n: number): string => n.toFixed(1);

    expect(flow(increment, square, fixed)(2)).toBe('9.0');
  });

  it('should return an identity function when no arguments are given', () => {
    expect(flow()('a')).toBe('a');
  });

  it('should work with a curried function and `_.head`', () => {
    const curried: any = curry(identity);
    const combined: any = flow(head as any, curried);
    expect(combined([1])).toBe(1);
  });
});
Example #3
Source File: flow-right.spec.ts    From s-libs with MIT License 6 votes vote down vote up
describe('flowRight()', () => {
  //
  // stolen from https://github.com/lodash/lodash
  //

  it('should supply each function with the return value of the previous', () => {
    const increment = (x: number): number => x + 1;
    const square = (x: number): number => x * x;
    const fixed = (n: number): string => n.toFixed(1);

    expect(flowRight(fixed, square, increment)(2)).toBe('9.0');
  });

  it('should return an identity function when no arguments are given', () => {
    expect(flowRight()('a')).toBe('a');
  });

  it('should work with a curried function and `_.head`', () => {
    const curried: any = curry(identity);
    const combined: any = flowRight(head as any, curried);

    expect(combined([1])).toBe(1);
  });
});
Example #4
Source File: traceSummary.ts    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
// What's the total duration of the spans in this trace?
// 最后结束的(时间戳+耗时最大)- 第一个调用的时间戳
export function traceDuration(spans: any) {
  function makeList({ timestamp, duration }: any) {
    if (!timestamp) {
      return [];
    } else if (!duration) {
      return [timestamp];
    }
    return [timestamp, timestamp + duration];
  }
  // turns (timestamp, timestamp + duration) into an ordered list
  const timestamps = fp.flow(fp.flatMap(makeList), fp.sortBy(identity))(spans);

  if (timestamps.length < 2) {
    return null;
  }
  const firstTime = head(timestamps);
  const lastTime = last(timestamps);
  return lastTime - firstTime;
}
Example #5
Source File: MjmlToJson.ts    From easy-email with MIT License 6 votes vote down vote up
export function getMetaDataFromMjml(data?: IChildrenItem): {
  [key: string]: any;
} {
  const mjmlHtmlAttributes = data?.children
    ?.filter((item) => item.tagName === 'mj-html-attributes')
    .map((item) => item.children)
    .flat()
    .filter((item) => item && item.attributes.class === 'easy-email')
    .reduce((obj: { [key: string]: any }, item) => {
      if (!item) return obj;
      const name = item.attributes['attribute-name'];
      const isMultipleAttributes = Boolean(
        item.attributes['multiple-attributes']
      );
      obj[name] = isMultipleAttributes
        ? pickBy(
            {
              ...item.attributes,
              'attribute-name': undefined,
              'multiple-attributes': undefined,
              class: undefined,
            },
            identity
          )
        : item.attributes[name];
      return obj;
    }, {});

  return pickBy(mjmlHtmlAttributes, identity);
}
Example #6
Source File: sumObjectValues.ts    From hub with Apache License 2.0 5 votes vote down vote up
sumObjectValues = (data: { [key: string]: number | undefined }): number => {
  const cleanData = pickBy(data, identity);
  if (isEmpty(cleanData)) return 0;
  return Object.values(cleanData).reduce((a, b) => a! + b!) || 0;
}
Example #7
Source File: bitriseApi.client.ts    From backstage with Apache License 2.0 5 votes vote down vote up
async getBuilds(
    appSlug: string,
    params?: BitriseQueryParams,
  ): Promise<BitriseBuildListResponse> {
    const baseUrl = await this.discoveryApi.getBaseUrl('proxy');
    let url = `${baseUrl}/bitrise/apps/${appSlug}/builds`;

    if (params) {
      url = `${url}?${qs.stringify(pickBy(params, identity))}`;
    }

    const response = await fetch(url);
    const data = await response.json();
    const builds: BitriseBuildResponseItem[] = data.data;

    return {
      data: builds.map(
        (build: BitriseBuildResponseItem): BitriseBuildResult => {
          const duration = String(
            Math.round(
              Interval.fromDateTimes(
                DateTime.fromISO(build.started_on_worker_at),
                DateTime.fromISO(build.finished_at),
              ).length('minutes'),
            ),
          );

          return {
            id: build.build_number,
            source: build.commit_view_url,
            status: build.status,
            statusText: build.status_text,
            buildSlug: build.slug,
            message: `${build.branch}`,
            workflow: build.triggered_workflow,
            commitHash: `${build.commit_hash}`,
            triggerTime: build.triggered_at,
            duration: `${duration} minutes`,
            appSlug,
          };
        },
      ),
      paging: data.paging,
    };
  }
Example #8
Source File: AnnotateLocationEntityProcessor.ts    From backstage with Apache License 2.0 5 votes vote down vote up
async preProcessEntity(
    entity: Entity,
    location: LocationSpec,
    _: CatalogProcessorEmit,
    originLocation: LocationSpec,
  ): Promise<Entity> {
    const { integrations } = this.options;
    let viewUrl;
    let editUrl;
    let sourceLocation;

    if (location.type === 'url') {
      const scmIntegration = integrations.byUrl(location.target);

      viewUrl = location.target;
      editUrl = scmIntegration?.resolveEditUrl(location.target);

      const sourceUrl = scmIntegration?.resolveUrl({
        url: './',
        base: location.target,
      });

      if (sourceUrl) {
        sourceLocation = stringifyLocationRef({
          type: 'url',
          target: sourceUrl,
        });
      }
    }

    return merge(
      {
        metadata: {
          annotations: pickBy(
            {
              [ANNOTATION_LOCATION]: stringifyLocationRef(location),
              [ANNOTATION_ORIGIN_LOCATION]:
                stringifyLocationRef(originLocation),
              [ANNOTATION_VIEW_URL]: viewUrl,
              [ANNOTATION_EDIT_URL]: editUrl,
              [ANNOTATION_SOURCE_LOCATION]: sourceLocation,
            },
            identity,
          ),
        },
      },
      entity,
    );
  }
Example #9
Source File: AnnotateScmSlugEntityProcessor.ts    From backstage with Apache License 2.0 5 votes vote down vote up
async preProcessEntity(
    entity: Entity,
    location: LocationSpec,
  ): Promise<Entity> {
    if (entity.kind !== 'Component' || location.type !== 'url') {
      return entity;
    }

    const scmIntegration = this.opts.scmIntegrationRegistry.byUrl(
      location.target,
    );

    if (!scmIntegration) {
      return entity;
    }

    let annotation;
    switch (scmIntegration.type) {
      case 'github':
        annotation = GITHUB_ACTIONS_ANNOTATION;
        break;
      case 'gitlab':
        annotation = GITLAB_ACTIONS_ANNOTATION;
        break;
      default:
        return entity;
    }

    let projectSlug = entity.metadata.annotations?.[annotation];
    if (!projectSlug) {
      const gitUrl = parseGitUrl(location.target);
      projectSlug = `${gitUrl.owner}/${gitUrl.name}`;
    }

    return merge(
      {
        metadata: {
          annotations: pickBy(
            {
              [annotation]: projectSlug,
            },
            identity,
          ),
        },
      },
      entity,
    );
  }
Example #10
Source File: MetricValue.tsx    From abacus with GNU General Public License v2.0 5 votes vote down vote up
metricValueFormatData: Record<string, MetricValueFormat> = {
  count: {
    unit: '',
    prefix: '',
    postfix: '',
    transform: identity,
    formatter: (n: number): string => n.toLocaleString(undefined),
  },
  conversion: {
    unit: '%',
    prefix: '',
    postfix: '%',
    transform: (x: number): number => x * 100,
    formatter: standardNumberFormatter,
  },
  conversion_difference: {
    unit: 'pp',
    prefix: '',
    postfix: (
      <DashedTooltip title='Percentage points.'>
        <span>pp</span>
      </DashedTooltip>
    ),
    transform: (x: number): number => x * 100,
    formatter: standardNumberFormatter,
  },
  revenue: {
    unit: 'USD',
    prefix: '',
    postfix: <>&nbsp;USD</>,
    transform: identity,
    formatter: usdFormatter,
  },
  revenue_difference: {
    unit: 'USD',
    prefix: '',
    postfix: <>&nbsp;USD</>,
    transform: identity,
    formatter: usdFormatter,
  },
}
Example #11
Source File: base-data-set.ts    From S2 with MIT License 5 votes vote down vote up
/**
   * 获得字段格式方法
   * @param field
   */
  public getFieldFormatter(field: string): Formatter {
    return get(this.getFieldMeta(field, this.meta), 'formatter', identity);
  }
Example #12
Source File: getGeneralGlobals.ts    From next-core with GNU General Public License v3.0 5 votes vote down vote up
function getIndividualGlobal(
  variableName: string,
  {
    collectCoverage,
    widgetId,
    app,
    storyboardFunctions,
    isStoryboardFunction,
  }: GeneralGlobalsOptions
): unknown {
  switch (variableName) {
    case "BASE_URL":
      return collectCoverage ? "/next" : getBasePath().replace(/\/$/, "");
    case "FN":
      return storyboardFunctions;
    case "IMG":
      return collectCoverage
        ? fakeImageFactory()
        : widgetId
        ? widgetImagesFactory(widgetId)
        : imagesFactory(app.id, app.isBuildPush);
    case "I18N":
      return collectCoverage
        ? identity
        : widgetId
        ? widgetI18nFactory(widgetId)
        : getFixedT(null, getI18nNamespace("app", app.id));
    case "I18N_TEXT":
      return collectCoverage ? fakeI18nText : i18nText;
    case "PERMISSIONS":
      return {
        check: collectCoverage ? fakeCheckPermissions : checkPermissions,
      };
    case "THEME":
      return {
        getTheme: collectCoverage ? () => "light" : getTheme,
      };
    case "console":
      return isStoryboardFunction ? getReadOnlyProxy(console) : undefined;
    case "location":
      return collectCoverage
        ? {
            href: "http://localhost:3000/functions/test",
            origin: "http://localhost:3000",
          }
        : { href: location.href, origin: location.origin };
  }
}
Example #13
Source File: min-by.spec.ts    From s-libs with MIT License 5 votes vote down vote up
describe('minBy()', () => {
  it('can sort by any primitive', () => {
    expect(minBy([0, -1, 1], identity)).toBe(-1);
    expect(minBy([true, false, true], identity)).toBe(false);
    expect(minBy(['b', 'a', 'c'], identity)).toBe('a');
  });

  //
  // stolen from https://github.com/lodash/lodash
  //

  it('should provide correct iteratee arguments', () => {
    const spy = jasmine.createSpy();
    minBy([1, 2, 3], spy);
    expect(spy.calls.first().args).toEqual([1]);
  });

  it('should treat sparse arrays as dense', () => {
    const array = [1];
    array[2] = 3;
    const spy = jasmine.createSpy().and.returnValue(true);

    minBy(array, spy);

    expectCallsAndReset(spy, [1], [undefined], [3]);
  });

  it('should not iterate custom properties of arrays', () => {
    const array = [1];
    (array as any).a = 1;
    const spy = jasmine.createSpy();

    minBy(array, spy);

    expectCallsAndReset(spy, [1]);
  });

  it('should ignore changes to `length`', () => {
    const array = [1];
    const spy = jasmine.createSpy().and.callFake(() => {
      array.push(2);
      return true;
    });

    minBy(array, spy);

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('should work with extremely large arrays', () => {
    expect(minBy(range(0, 5e5), identity)).toBe(0);
  });

  it('should work with an `iteratee`', () => {
    expect(minBy([1, 2, 3], (n) => -n)).toBe(3);
  });

  it('should work when `iteratee` returns +/-Infinity', () => {
    const value = Infinity;
    const object = { a: value };

    const actual = minBy([object, { a: value }], (obj: { a: number }) => obj.a);

    expect(actual).toBe(object);
  });
});
Example #14
Source File: traceSummary.ts    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
export function getServiceNames(span: any) {
  return fp.flow(
    fp.map((ep: any) => ep.serviceName),
    fp.filter((name) => name != null && name !== ''),
    fp.uniqBy(identity),
  )(endpointsForSpan(span));
}
Example #15
Source File: removeUndefined.ts    From nextclade with MIT License 5 votes vote down vote up
/**
 * Removes object keys which have `undefined` value
 */
export function removeUndefined<T>(obj: Record<string, T | undefined>) {
  return pickBy(obj, identity) as Record<string, T>
}
Example #16
Source File: max-by.spec.ts    From s-libs with MIT License 5 votes vote down vote up
describe('maxBy()', () => {
  it('can sort by any primitive', () => {
    expect(maxBy([0, 1, -1], identity)).toBe(1);
    expect(maxBy([false, true, false], identity)).toBe(true);
    expect(maxBy(['b', 'c', 'a'], identity)).toBe('c');
  });

  //
  // stolen from https://github.com/lodash/lodash
  //

  it('should provide correct iteratee arguments', () => {
    const spy = jasmine.createSpy();
    maxBy([1, 2, 3], spy);
    expect(spy.calls.first().args).toEqual([1]);
  });

  it('should treat sparse arrays as dense', () => {
    const array = [1];
    array[2] = 3;
    const spy = jasmine.createSpy().and.returnValue(true);

    maxBy(array, spy);

    expectCallsAndReset(spy, [1], [undefined], [3]);
  });

  it('should not iterate custom properties of arrays', () => {
    const array = [1];
    (array as any).a = 1;
    const spy = jasmine.createSpy();

    maxBy(array, spy);

    expectCallsAndReset(spy, [1]);
  });

  it('should ignore changes to `length`', () => {
    const array = [1];
    const spy = jasmine.createSpy().and.callFake(() => {
      array.push(2);
      return true;
    });

    maxBy(array, spy);

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('should work with extremely large arrays', () => {
    expect(maxBy(range(0, 5e5), identity)).toBe(499999);
  });

  it('should work with an `iteratee`', () => {
    expect(maxBy([1, 2, 3], (n) => -n)).toBe(1);
  });

  it('should work when `iteratee` returns +/-Infinity', () => {
    const value = -Infinity;
    const object = { a: value };

    const actual = maxBy([object, { a: value }], (obj: { a: number }) => obj.a);

    expect(actual).toBe(object);
  });
});
Example #17
Source File: loaders-explorer.service.ts    From nestjs-mercurius with MIT License 4 votes vote down vote up
private createContextCallback<T extends Record<string, any>>(
    instance: T,
    prototype: any,
    wrapper: InstanceWrapper,
    moduleRef: Module,
    resolver: any,
    isRequestScoped: boolean,
    transform: Function = identity,
  ) {
    const paramsFactory = this.gqlParamsFactory;
    const fieldResolverEnhancers = this.gqlOptions.fieldResolverEnhancers || [];

    const contextOptions = {
      guards: fieldResolverEnhancers.includes('guards'),
      filters: fieldResolverEnhancers.includes('filters'),
      interceptors: fieldResolverEnhancers.includes('interceptors'),
    };

    if (isRequestScoped) {
      const loaderCallback = async (...args: any[]) => {
        const gqlContext = paramsFactory.exchangeKeyForValue(
          GqlParamtype.CONTEXT,
          undefined,
          args,
        );
        let contextId: ContextId;
        if (gqlContext && gqlContext[REQUEST_CONTEXT_ID]) {
          contextId = gqlContext[REQUEST_CONTEXT_ID];
        } else if (
          gqlContext &&
          gqlContext.req &&
          gqlContext.req[REQUEST_CONTEXT_ID]
        ) {
          contextId = gqlContext.req[REQUEST_CONTEXT_ID];
        } else {
          contextId = createContextId();
          Object.defineProperty(gqlContext, REQUEST_CONTEXT_ID, {
            value: contextId,
            enumerable: false,
            configurable: false,
            writable: false,
          });
        }

        this.registerContextProvider(gqlContext, contextId);
        const contextInstance = await this.injector.loadPerContext(
          instance,
          moduleRef,
          moduleRef.providers,
          contextId,
        );
        const callback = this.externalContextCreator.create(
          contextInstance,
          transform(contextInstance[resolver.methodName]),
          resolver.methodName,
          PARAM_ARGS_METADATA,
          paramsFactory,
          contextId,
          wrapper.id,
          contextOptions,
          'graphql',
        );
        return callback(...args);
      };

      return this.registerFieldMiddlewareIfExists(
        loaderCallback,
        instance,
        resolver.methodName,
      );
    }

    const loaderCallback = this.externalContextCreator.create(
      instance,
      prototype[resolver.methodName],
      resolver.methodName,
      PARAM_ARGS_METADATA,
      paramsFactory,
      undefined,
      undefined,
      contextOptions,
      'graphql',
    );

    return this.registerFieldMiddlewareIfExists(
      loaderCallback,
      instance,
      resolver.methodName,
    );
  }
Example #18
Source File: MetricAssignmentResults.tsx    From abacus with GNU General Public License v2.0 4 votes vote down vote up
/**
 * Display results for a MetricAssignment
 */
export default function MetricAssignmentResults({
  strategy,
  metricAssignment,
  metric,
  analysesByStrategyDateAsc,
  experiment,
  recommendation,
  variationDiffKey,
}: {
  strategy: AnalysisStrategy
  metricAssignment: MetricAssignment
  metric: Metric
  analysesByStrategyDateAsc: Record<AnalysisStrategy, Analysis[]>
  experiment: ExperimentFull
  recommendation: Recommendations.Recommendation
  variationDiffKey: string
}): JSX.Element | null {
  const classes = useStyles()

  const [isShowObservedData, setIsShowObservedData] = useState<boolean>(false)
  const toggleIsShowObservedData = () => {
    setIsShowObservedData((isShowObservedData) => !isShowObservedData)
  }

  const isConversion = metric.parameterType === MetricParameterType.Conversion
  const estimateTransform: (estimate: number | null) => number | null = isConversion
    ? (estimate: number | null) => estimate && estimate * 100
    : identity
  const analyses = analysesByStrategyDateAsc[strategy]
  const latestAnalysis = _.last(analyses)
  const latestEstimates = latestAnalysis?.metricEstimates
  if (!latestAnalysis || !latestEstimates) {
    return <MissingAnalysisMessage />
  }

  const [_changeVariationId, baseVariationId] = variationDiffKey.split('_')

  const dates = analyses.map(({ analysisDatetime }) => analysisDatetime.toISOString())

  const plotlyDataVariationGraph: Array<Partial<PlotData>> = [
    ..._.flatMap(experiment.variations, (variation, index) => {
      return [
        {
          name: `${variation.name}: lower bound`,
          x: dates,
          y: analyses
            .map(
              ({ metricEstimates }) => metricEstimates && metricEstimates.variations[variation.variationId].bottom_95,
            )
            .map(estimateTransform),
          line: {
            color: Visualizations.variantColors[index],
          },
          mode: 'lines' as const,
          type: 'scatter' as const,
        },
        {
          name: `${variation.name}: upper bound`,
          x: dates,
          y: analyses
            .map(({ metricEstimates }) => metricEstimates && metricEstimates.variations[variation.variationId].top_95)
            .map(estimateTransform),
          line: {
            color: Visualizations.variantColors[index],
          },
          fill: 'tonexty' as const,
          fillcolor: Visualizations.variantColors[index],
          mode: 'lines' as const,
          type: 'scatter' as const,
        },
      ]
    }),
  ]

  const plotlyDataDifferenceGraph: Array<Partial<PlotData>> = [
    {
      name: `difference: 99% lower bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_99)
        .map(estimateTransform),
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 99% upper bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_99)
        .map(estimateTransform),
      fill: 'tonexty',
      fillcolor: 'rgba(0,0,0,.2)',
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 95% lower bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_95)
        .map(estimateTransform),
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 95% upper bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_95)
        .map(estimateTransform),
      fill: 'tonexty',
      fillcolor: 'rgba(0,0,0,.2)',
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 50% lower bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_50)
        .map(estimateTransform),
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 50% upper bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_50)
        .map(estimateTransform),
      fill: 'tonexty',
      fillcolor: 'rgba(0,0,0,.2)',
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: 'ROPE: lower bound',
      x: dates,
      y: analyses.map((_) => -metricAssignment.minDifference).map(estimateTransform),
      line: {
        color: 'rgba(0,0,0,.4)',
        dash: 'dash',
      },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: 'ROPE: upper bound',
      x: dates,
      y: analyses.map((_) => metricAssignment.minDifference).map(estimateTransform),
      line: {
        color: 'rgba(0,0,0,.4)',
        dash: 'dash',
      },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
  ]

  return (
    <div className={clsx(classes.root, 'analysis-detail-panel')}>
      <Typography className={classes.dataTableHeader}>Summary</Typography>
      <TableContainer component={Paper}>
        <Table>
          <TableBody>
            <TableRow>
              <TableCell>
                <Typography variant='h5' gutterBottom className={classes.recommendation}>
                  <AnalysisDisplay {...{ experiment, analysis: recommendation }} />
                </Typography>
                {recommendation.decision === Recommendations.Decision.ManualAnalysisRequired && (
                  <Typography variant='body1' gutterBottom>
                    <strong> Different strategies are recommending conflicting variations! </strong>
                  </Typography>
                )}
                <Typography variant='body1'>
                  {getOverviewMessage(experiment, recommendation)}{' '}
                  <Link
                    href={`https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#reading-the-data`}
                    target='_blank'
                  >
                    Learn more
                  </Link>
                </Typography>
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography variant='body1' gutterBottom>
                  The absolute change in the {isConversion ? 'conversion rate' : 'ARPU'} of{' '}
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={latestEstimates.diffs[variationDiffKey].bottom_95}
                    displayPositiveSign
                    displayUnit={false}
                  />{' '}
                  to{' '}
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={latestEstimates.diffs[variationDiffKey].top_95}
                    displayPositiveSign
                  />{' '}
                  is {recommendation.statisticallySignificant ? '' : ' not '}
                  statistically different from zero because the interval
                  {recommendation.statisticallySignificant ? ' excludes ' : ' includes '}
                  zero.{' '}
                  {
                    explanationLine2[
                      recommendation.practicallySignificant as Recommendations.PracticalSignificanceStatus
                    ]
                  }
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={-metricAssignment.minDifference}
                    displayPositiveSign
                    displayUnit={false}
                  />{' '}
                  to{' '}
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={metricAssignment.minDifference}
                    displayPositiveSign
                  />
                  .
                </Typography>
                <strong>Last analyzed:</strong>{' '}
                <DatetimeText datetime={latestAnalysis.analysisDatetime} excludeTime={true} />.
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <strong>Metric description:</strong> {metric.description}
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </TableContainer>
      <Typography className={classes.dataTableHeader}>Analysis</Typography>
      <TableContainer component={Paper}>
        <Table className={classes.coolTable}>
          <TableHead>
            <TableRow>
              <TableCell>Variant</TableCell>
              <TableCell align='right'>
                {metric.parameterType === MetricParameterType.Revenue
                  ? 'Average revenue per user (ARPU) interval'
                  : 'Conversion rate interval'}
              </TableCell>
              <TableCell align='right'>Absolute change</TableCell>
              <TableCell align='right'>Relative change (lift)</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {experiment.variations.map((variation) => (
              <React.Fragment key={variation.variationId}>
                <TableRow>
                  <TableCell
                    component='th'
                    scope='row'
                    variant='head'
                    valign='top'
                    className={clsx(classes.rowHeader, classes.headerCell, classes.credibleIntervalHeader)}
                  >
                    <span className={classes.monospace}>{variation.name}</span>
                  </TableCell>
                  <TableCell className={classes.monospace} align='right'>
                    <MetricValueInterval
                      intervalName={'the metric value'}
                      metricParameterType={metric.parameterType}
                      bottomValue={latestEstimates.variations[variation.variationId].bottom_95}
                      topValue={latestEstimates.variations[variation.variationId].top_95}
                      displayPositiveSign={false}
                    />
                  </TableCell>
                  <TableCell className={classes.monospace} align='right'>
                    {variation.isDefault ? (
                      'Baseline'
                    ) : (
                      <MetricValueInterval
                        intervalName={'the absolute change between variations'}
                        metricParameterType={metric.parameterType}
                        isDifference={true}
                        bottomValue={latestEstimates.diffs[`${variation.variationId}_${baseVariationId}`].bottom_95}
                        topValue={latestEstimates.diffs[`${variation.variationId}_${baseVariationId}`].top_95}
                      />
                    )}
                  </TableCell>
                  <TableCell className={classes.monospace} align='right'>
                    {variation.isDefault ? (
                      'Baseline'
                    ) : (
                      <MetricValueInterval
                        intervalName={'the relative change between variations'}
                        metricParameterType={MetricParameterType.Conversion}
                        bottomValue={Analyses.ratioToDifferenceRatio(
                          latestEstimates.ratios[`${variation.variationId}_${baseVariationId}`].bottom_95,
                        )}
                        topValue={Analyses.ratioToDifferenceRatio(
                          latestEstimates.ratios[`${variation.variationId}_${baseVariationId}`].top_95,
                        )}
                      />
                    )}
                  </TableCell>
                </TableRow>
              </React.Fragment>
            ))}
          </TableBody>
        </Table>
      </TableContainer>
      <Typography className={classes.analysisFinePrint}>
        95% Credible Intervals (CIs). <strong> Experimenter-set minimum practical difference: </strong>{' '}
        <MetricValue
          value={metricAssignment.minDifference}
          metricParameterType={metric.parameterType}
          isDifference={true}
        />
        .
      </Typography>
      {dates.length > 1 ? (
        <div className={classes.metricEstimatePlots}>
          <Plot
            layout={{
              ...Visualizations.plotlyLayoutDefault,
              title: isConversion
                ? `Conversion rate estimates by variation (%)`
                : `Revenue estimates by variation (USD)`,
            }}
            data={plotlyDataVariationGraph}
            className={classes.metricEstimatePlot}
          />
          <Plot
            layout={{
              ...Visualizations.plotlyLayoutDefault,
              title: isConversion
                ? `Conversion rate difference estimates (percentage points)`
                : `Revenue difference estimates (USD)`,
            }}
            data={plotlyDataDifferenceGraph}
            className={classes.metricEstimatePlot}
          />
        </div>
      ) : (
        <Typography variant='body1' className={classes.noPlotMessage}>
          Past values will be plotted once we have more than one day of results.
        </Typography>
      )}
      <Typography
        className={clsx(classes.dataTableHeader, classes.clickable)}
        onClick={toggleIsShowObservedData}
        role='button'
      >
        {isShowObservedData ? (
          <ExpandMore className={classes.expandCollapseIcon} />
        ) : (
          <ChevronRight className={classes.expandCollapseIcon} />
        )}
        &quot;Observed&quot; data
      </Typography>
      {isShowObservedData && (
        <>
          <TableContainer component={Paper}>
            <Table className={classes.coolTable}>
              <TableHead>
                <TableRow>
                  <TableCell>Variant</TableCell>
                  <TableCell align='right'>Users</TableCell>
                  <TableCell align='right'>
                    {metric.parameterType === MetricParameterType.Revenue ? 'Revenue' : 'Conversions'}
                  </TableCell>
                  <TableCell align='right'>
                    {metric.parameterType === MetricParameterType.Revenue
                      ? 'Average revenue per user (ARPU)'
                      : 'Conversion rate'}
                  </TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {experiment.variations.map((variation) => (
                  <React.Fragment key={variation.variationId}>
                    <TableRow>
                      <TableCell
                        component='th'
                        scope='row'
                        variant='head'
                        valign='top'
                        className={clsx(classes.rowHeader, classes.headerCell, classes.credibleIntervalHeader)}
                      >
                        <span className={classes.monospace}>{variation.name}</span>
                      </TableCell>
                      <TableCell className={classes.monospace} align='right'>
                        {latestAnalysis.participantStats[`variation_${variation.variationId}`].toLocaleString()}
                      </TableCell>
                      <TableCell className={classes.monospace} align='right'>
                        <MetricValue
                          value={
                            latestAnalysis.participantStats[`variation_${variation.variationId}`] *
                            latestEstimates.variations[variation.variationId].mean
                          }
                          metricParameterType={
                            metric.parameterType === MetricParameterType.Conversion
                              ? MetricParameterType.Count
                              : metric.parameterType
                          }
                        />
                      </TableCell>
                      <TableCell className={classes.monospace} align='right'>
                        <MetricValue
                          value={latestEstimates.variations[variation.variationId].mean}
                          metricParameterType={metric.parameterType}
                        />
                      </TableCell>
                    </TableRow>
                  </React.Fragment>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
          <Typography variant='caption' gutterBottom>
            <Link href='https://wp.me/PCYsg-Fqg/#observed-data-uses-posterior-means' target='_blank'>
              &quot;Observed&quot; data as produced from our model, not raw observed data.
            </Link>{' '}
            For illustrative purposes only.
          </Typography>
        </>
      )}
    </div>
  )
}
Example #19
Source File: partial.spec.ts    From s-libs with MIT License 4 votes vote down vote up
describe('partial()', () => {
  it('does not alter the `this` binding', () => {
    function fn(this: any): any {
      return this;
    }

    expect(partial(fn.bind(2))()).toBe(2);
    expect(partial(fn)()).toBe(undefined);
  });

  it('sets the `length` property', () => {
    const fn = (_a: any, _b: any, _c: any): void => {
      // blah
    };

    expect(partial(fn).length).toBe(3);
    expect(partial(fn, 1).length).toBe(2);
    expect(partial(fn, 1, 2).length).toBe(1);
    expect(partial(fn, 1, 2, 3).length).toBe(0);
  });

  //
  // stolen from https://github.com/lodash/lodash
  //

  it('partially applies arguments', () => {
    expect(partial(identity as (value: string) => string, 'a')()).toBe('a');
  });

  it('creates a function that can be invoked with additional arguments', () => {
    const fn = (a: string, b: string): string[] => [a, b];

    const par = partial(fn, 'a');

    expect(par('b')).toEqual(['a', 'b']);
  });

  it('works when there are no partially applied arguments and the created function is invoked without additional arguments', () => {
    const fn = function (): number {
      return arguments.length;
    };

    const par = partial(fn);

    expect(par()).toBe(0);
  });

  it('works when there are no partially applied arguments and the created function is invoked with additional arguments', () => {
    expect(partial(identity as (value: string) => string)('a')).toBe('a');
  });

  it('should ensure `new par` is an instance of `func`', () => {
    const object = {};
    function Foo(value = false): false | {} {
      return value && object;
    }

    const par = partial(Foo);

    expect(new (par as any)() instanceof Foo).toBeTruthy();
    expect(new (par as any)(true)).toBe(object);
  });

  it('should clone metadata for created functions', () => {
    function greet(greeting: string, name: string): string {
      return greeting + ' ' + name;
    }

    const par1 = partial(greet, 'hi');
    const par2 = partial(par1, 'barney');
    const par3 = partial(par1, 'pebbles');

    expect(par1('fred')).toBe('hi fred');
    expect(par2()).toBe('hi barney');
    expect(par3()).toBe('hi pebbles');
  });

  it('should work with curried functions', () => {
    const fn = (a: number, b: number, c: number): number => a + b + c;
    const curried = curry(partial(fn, 1), 2);

    expect(curried(2, 3)).toBe(6);
    expect(curried(2)(3)).toBe(6);
  });
});
Example #20
Source File: memoize.spec.ts    From s-libs with MIT License 4 votes vote down vote up
describe('memoize()', () => {
  //
  // stolen from https://github.com/lodash/lodash
  //

  it('should memoize results based on the first argument given', () => {
    const memoized = memoize((a: number, b: number, c: number) => a + b + c);

    expect(memoized(1, 2, 3)).toBe(6);
    expect(memoized(1, 3, 5)).toBe(6);
  });

  it('should support a `resolver`', () => {
    const fn = (a: number, b: number, c: number): number => a + b + c;

    const memoized = memoize(fn, fn);

    expect(memoized(1, 2, 3)).toBe(6);
    expect(memoized(1, 3, 5)).toBe(9);
  });

  it('should use `this` binding of function for `resolver`', () => {
    function fn(this: any, a: number): any {
      return a + this.b + this.c;
    }

    const memoized = memoize(fn, fn);

    const object = { memoized, b: 2, c: 3 };
    expect(object.memoized(1)).toBe(6);

    object.b = 3;
    object.c = 5;
    expect(object.memoized(1)).toBe(9);
  });

  it('should not error if `resolver` is nullish', () => {
    expect(isFunction(memoize(noop))).toBeTruthy();
    expect(isFunction(memoize(noop, null as any))).toBeTruthy();
    expect(isFunction(memoize(noop, undefined))).toBeTruthy();
  });

  it('should check cache for own properties', () => {
    const props = [
      'constructor',
      'hasOwnProperty',
      'isPrototypeOf',
      'propertyIsEnumerable',
      'toLocaleString',
      'toString',
      'valueOf',
    ];

    const memoized = memoize(identity);

    for (const value of props) {
      expect(memoized(value)).toBe(value);
    }
  });

  it('should cache the `__proto__` key', () => {
    const array: any[] = [];
    const key = '__proto__';
    let count = 0;

    function func(_arg: any): any[] {
      ++count;
      return array;
    }

    let memoized = memoize(func);
    memoized(key);
    memoized(key);
    expect(count).toBe(1);
    expect(memoized.cache.get(key)).toBe(array);
    expect(memoized.cache.delete(key)).toBe(true);

    memoized = memoize(func, identity);
    memoized(key);
    memoized(key);
    expect(count).toBe(2);
    expect(memoized.cache.get(key)).toBe(array);
    expect(memoized.cache.delete(key)).toBe(true);
  });
});
Example #21
Source File: every.spec.ts    From s-libs with MIT License 4 votes vote down vote up
describe('every()', () => {
  //
  // stolen from https://github.com/lodash/lodash
  //

  it('returns `true` if `predicate` returns truthy for all elements', () => {
    expect(every([true, 1, 'a'], identity)).toBe(true);
  });

  it('should return `true` for empty collections', () => {
    for (const empty of [[], {}, null, undefined, false, 0, NaN, '']) {
      expect(every(empty, identity)).toBe(true);
    }
  });

  it('should return `false` as soon as `predicate` returns falsey', () => {
    let count = 0;

    const result = every([true, null, true], (value) => {
      ++count;
      return value;
    });

    expect(result).toBe(false);
    expect(count).toBe(2);
  });

  it('should work with collections of `undefined` values', () => {
    expect(every([undefined, undefined, undefined], identity)).toBe(false);
  });

  it('should provide correct iteratee arguments', () => {
    const spy = jasmine.createSpy();
    every([1, 2, 3], spy);
    expect(spy.calls.first().args).toEqual([1, 0]);
  });

  it('should treat sparse arrays as dense', () => {
    const array = [1];
    array[2] = 3;
    const spy = jasmine.createSpy().and.returnValue(true);

    every(array, spy);

    expectCallsAndReset(spy, [1, 0], [undefined, 1], [3, 2]);
  });

  it('should not iterate custom properties of arrays', () => {
    const array = [1];
    (array as any).a = 1;
    const spy = jasmine.createSpy().and.returnValue(true);

    every(array, spy);

    expectCallsAndReset(spy, [1, 0]);
  });

  it('iterates over own string keyed properties of objects', () => {
    const object = { a: 1 };
    const spy = jasmine.createSpy().and.returnValue(true);

    every(object, spy);

    expectCallsAndReset(spy, [1, 'a']);
  });

  it('should ignore changes to `length`', () => {
    const array = [1];
    const spy = jasmine.createSpy().and.callFake(() => {
      array.push(2);
      return true;
    });

    every(array, spy);

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('should ignore added `object` properties', () => {
    const object: any = { a: 1 };
    const spy = jasmine.createSpy().and.callFake(() => {
      object.b = 2;
      return true;
    });

    every(object, spy);

    expect(spy).toHaveBeenCalledTimes(1);
  });
});
Example #22
Source File: JsonToMjml.ts    From easy-email with MIT License 4 votes vote down vote up
export function JsonToMjml(options: JsonToMjmlOption): string {
  const {
    data,
    idx = 'content',
    context = data,
    mode = 'production',
    dataSource = {},
  } = options;
  if (
    (isBoolean(data?.data?.hidden) && data?.data?.hidden) ||
    data?.data?.hidden === 'true'
  ) {
    return '';
  }

  const att = pickBy(
    {
      ...data.attributes,
    },
    identity
  );

  const isTest = mode === 'testing';
  const keepClassName = isProductionMode(options) ? options.keepClassName : false;
  const placeholder = isTest ? renderPlaceholder(data.type) : '';

  if (isTest && idx) {
    att['css-class'] = classnames(
      att['css-class'],
      'email-block',
      getNodeIdxClassName(idx),
      getNodeTypeClassName(data.type)
    );
  }

  if (keepClassName) {
    att['css-class'] = classnames(att['css-class'], getNodeTypeClassName(data.type));
  }

  if (isTest && data.type === BasicType.TEXT) {
    att['css-class'] = classnames(att['css-class'], MERGE_TAG_CLASS_NAME);
  }

  if (data.type === BasicType.PAGE) {
    att['css-class'] = classnames(att['css-class'], 'mjml-body');
  }

  const attributeStr = Object.keys(att)
    .filter((key) => att[key] !== '') // filter att=""
    .map((key) => {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-call
      const val = isString(att[key]) ? att[key].replace(/"/gm, '') : att[key];
      return `${key}="${val}"`;
    })
    .join(' ');

  const block = BlockManager.getBlockByType(data.type);

  if (!block) {
    throw new Error(
      `Can not find ${data.type} block!!! Have you registered this block ?`
    );
  }

  if (block.render) {
    const transformBlockData = block.render(
      data,
      idx,
      mode,
      context,
      dataSource
    );

    if (!transformBlockData) return '';

    const transformData = isValidElement(transformBlockData)
      ? parseReactBlockToBlockData(transformBlockData)
      : transformBlockData;

    att['css-class'] = [
      ...new Set(
        classnames(
          isTest && getPreviewClassName(idx, data.type),
          transformData?.['attributes']?.['css-class']
        ).split(' ')
      ),
    ].join(' ');
    return JsonToMjml({
      data: {
        ...transformData,
        attributes: {
          ...transformData.attributes,
          'css-class': att['css-class'],
        },
      },
      idx: null,
      context: context,
      dataSource,
      mode,
      keepClassName
    });
  }

  const children = data.children
    .map((child, index) => {
      let childIdx = idx ? getChildIdx(idx, index) : null;
      if (data.type === BasicType.TEMPLATE) {
        childIdx = getChildIdx(data.data.value.idx, index);
        if (!data.data.value.idx) {
          childIdx = null;
        }
      }
      return JsonToMjml({
        data: child,
        idx: childIdx,
        dataSource,
        context,
        mode,
        keepClassName
      });
    })
    .join('\n');

  switch (data.type) {
    case BasicType.TEMPLATE:
      return children || data.data.value.content;
    case BasicType.PAGE:
      const metaData = generaMjmlMetaData(data);
      const value: IPage['data']['value'] = data.data.value;

      const breakpoint = value.breakpoint
        ? `<mj-breakpoint width="${data.data.value.breakpoint}" />`
        : '';

      const nonResponsive = !value.responsive
        ? `<mj-raw>
            <meta name="viewport" />
           </mj-raw>
           <mj-style inline="inline">.mjml-body { width: ${data.attributes.width || '600px'
        }; margin: 0px auto; }</mj-style>`
        : '';
      const styles =
        value.headStyles
          ?.map(
            (style) =>
              `<mj-style ${style.inline ? 'inline="inline"' : ''}>${style.content
              }</mj-style>`
          )
          .join('\n') || '';

      const userStyle = value['user-style']
        ? `<mj-style ${value['user-style'].inline ? 'inline="inline"' : ''}>${value['user-style'].content
        }</mj-style>`
        : '';

      const extraHeadContent = value.extraHeadContent ? `<mj-raw>${value.extraHeadContent}</mj-raw>` : '';

      return `
        <mjml>
          <mj-head>
              ${metaData}
              ${nonResponsive}
              ${styles}
              ${userStyle}
              ${breakpoint}
              ${extraHeadContent}
              ${value.fonts
          ?.filter(Boolean)
          .map(
            (item) =>
              `<mj-font name="${item.name}" href="${item.href}" />`
          )}
            <mj-attributes>
              ${value.headAttributes}
              ${value['font-family']
          ? `<mj-all font-family="${value['font-family'].replace(/"/gm, '')}" />`
          : ''
        }
              ${value['font-size']
          ? `<mj-text font-size="${value['font-size']}" />`
          : ''
        }
              ${value['text-color']
          ? `<mj-text color="${value['text-color']}" />`
          : ''
        }
        ${value['line-height']
          ? `<mj-text line-height="${value['line-height']}" />`
          : ''
        }
        ${value['font-weight']
          ? `<mj-text font-weight="${value['font-weight']}" />`
          : ''
        }
              ${value['content-background-color']
          ? `<mj-wrapper background-color="${value['content-background-color']}" />
                     <mj-section background-color="${value['content-background-color']}" />
                    `
          : ''
        }

            </mj-attributes>
          </mj-head>
          <mj-body ${attributeStr}>
            ${children}
          </mj-body>
        </mjml>
        `;
    case BasicType.COLUMN:
      return `
              <mj-column ${attributeStr}>
               ${children || placeholder}
              </mj-column>
            `;
    case BasicType.SECTION:
      return `
              <mj-section ${attributeStr}>
               ${children || `<mj-column>${placeholder}</mj-column>`}
              </mj-section>
            `;
    case BasicType.GROUP:
      return `
              <mj-group ${attributeStr}>
               ${children || `<mj-column>${placeholder}</mj-column>`}
              </mj-group>
            `;
    case BasicType.WRAPPER:
      return `
              <mj-wrapper ${attributeStr}>
               ${children ||
        `<mj-section><mj-column>${placeholder}</mj-column></mj-section>`
        }
              </mj-wrapper>
            `;
    case BasicType.CAROUSEL:
      const carouselImages = (data as ICarousel).data.value.images
        .map((image, index) => {
          const imageAttributeStr = Object.keys(image)
            .filter((key) => key !== 'content' && att[key] !== '') // filter att=""
            .map((key) => `${key}="${image[key as keyof typeof image]}"`)
            .join(' ');
          return `
          <mj-carousel-image ${imageAttributeStr} />
          `;
        })
        .join('\n');

      return `
        <mj-carousel ${attributeStr}>
         ${carouselImages}
        </mj-carousel>
      `;
    case BasicType.NAVBAR:
      const links = (data as INavbar).data.value.links
        .map((link, index) => {
          const linkAttributeStr = Object.keys(link)
            .filter((key) => key !== 'content' && att[key] !== '') // filter att=""
            .map((key) => `${key}="${link[key as keyof typeof link]}"`)
            .join(' ');
          return `
          <mj-navbar-link ${linkAttributeStr}>${link.content}</mj-navbar-link>
          `;
        })
        .join('\n');
      return `
              <mj-navbar ${attributeStr}>
               ${links}
              </mj-navbar>
            `;
    case BasicType.SOCIAL:
      const elements = (data as ISocial).data.value.elements
        .map((element, index) => {
          const elementAttributeStr = Object.keys(element)
            .filter((key) => key !== 'content' && att[key] !== '') // filter att=""
            .map((key) => `${key}="${element[key as keyof typeof element]}"`)
            .join(' ');
          return `
          <mj-social-element ${elementAttributeStr}>${element.content}</mj-social-element>
          `;
        })
        .join('\n');
      return `
              <mj-social ${attributeStr}>
               ${elements}
              </mj-social>
            `;
    case BasicType.RAW:
      return `
              <mj-raw ${attributeStr}>
                ${data.data.value?.content}
              </mj-raw>
            `;
    case BasicType.IMAGE:
      if (mode === 'testing') {
        const url = data.attributes.src;
        if (
          url === '' ||
          /{{([\s\S]+?)}}/g.test(url) ||
          /\*\|([^\|\*]+)\|\*/g.test(url)
        ) {
          return `<mj-image src="${getImg(
            'IMAGE_59'
          )}"  ${attributeStr}></mj-image>`;
        }
      }
      return `<mj-image ${attributeStr}></mj-image>`;
    default:
      return `
          <mj-${data.type} ${attributeStr}>
           ${children || data.data.value?.content || ''}
          </mj-${data.type}>
        `;
  }
}
Example #23
Source File: Picker.tsx    From react-native-jigsaw with MIT License 4 votes vote down vote up
Picker: React.FC<PickerProps> = ({
  error,
  options = [],
  onValueChange,
  defaultValue,
  Icon,
  style,
  placeholder,
  value,
  disabled = false,
  theme,
  assistiveText,
  label,
  iconColor = unstyledColor,
  iconSize = 24,
  leftIconMode = "inset",
  leftIconName,
  placeholderTextColor = unstyledColor,
  rightIconName,
  type = "solid",
}) => {
  const androidPickerRef = React.useRef<any | undefined>(undefined);

  const [internalValue, setInternalValue] = React.useState<string | undefined>(
    value || defaultValue
  );

  const [pickerVisible, setPickerVisible] = React.useState(false);

  const togglePickerVisible = () => {
    setPickerVisible(!pickerVisible);
  };

  React.useEffect(() => {
    if (value != null) {
      setInternalValue(value);
    }
  }, [value]);

  React.useEffect(() => {
    if (defaultValue != null) {
      setInternalValue(defaultValue);
    }
  }, [defaultValue]);

  React.useEffect(() => {
    if (pickerVisible && androidPickerRef.current) {
      androidPickerRef?.current?.focus();
    }
  }, [pickerVisible, androidPickerRef]);

  const normalizedOptions = normalizeOptions(options);

  const pickerOptions = placeholder
    ? [{ value: placeholder, label: placeholder }, ...normalizedOptions]
    : normalizedOptions;

  const { colors } = theme;

  const { viewStyles, textStyles } = extractStyles(style);

  const additionalBorderStyles = ["backgroundColor"];

  const additionalMarginStyles = [
    "bottom",
    "height",
    "left",
    "maxHeight",
    "maxWidth",
    "minHeight",
    "minWidth",
    "overflow",
    "position",
    "right",
    "top",
    "width",
    "zIndex",
  ];

  const {
    borderStyles: extractedBorderStyles,
    marginStyles: extractedMarginStyles,
  } = extractBorderAndMarginStyles(
    viewStyles,
    additionalBorderStyles,
    additionalMarginStyles
  );

  const borderStyles = {
    ...{
      ...(type === "solid"
        ? {
            borderTopLeftRadius: 5,
            borderTopRightRadius: 5,
            borderBottomRightRadius: 5,
            borderBottomLeftRadius: 5,
            borderTopWidth: 1,
            borderRightWidth: 1,
            borderLeftWidth: 1,
          }
        : {}),
      borderBottomWidth: 1,
      borderColor: unstyledColor,
      borderStyle: "solid",
    },
    ...extractedBorderStyles,
    ...(error ? { borderColor: errorColor } : {}),
    ...(disabled
      ? { borderColor: "transparent", backgroundColor: disabledColor }
      : {}),
  };

  const marginStyles = {
    height: 60,
    ...extractedMarginStyles,
  };

  const stylesWithoutBordersAndMargins = omit(viewStyles, [
    ...borderStyleNames,
    ...marginStyleNames,
    ...additionalBorderStyles,
    ...additionalMarginStyles,
  ]);

  const selectedLabel =
    internalValue &&
    ((pickerOptions as unknown as PickerOption[]).find(
      (option) => option.value === internalValue
    )?.label ??
      internalValue);

  const labelText = label ? (
    <Text
      style={{
        textAlign: textStyles.textAlign,
        color: unstyledColor,
        fontSize: 12,
        paddingBottom: 4,
      }}
    >
      {label}
    </Text>
  ) : null;

  const leftIconOutset = leftIconMode === "outset";

  const leftIcon = leftIconName ? (
    <Icon
      name={leftIconName}
      color={disabled ? unstyledColor : iconColor}
      size={iconSize}
      style={{
        marginRight: 4,
        marginLeft: 4,
      }}
    />
  ) : null;

  const rightIcon = rightIconName ? (
    <Icon
      name={rightIconName}
      color={disabled ? unstyledColor : iconColor}
      size={iconSize}
      style={{
        marginRight: -10,
        marginLeft: 8,
      }}
    />
  ) : null;

  const textAlign = textStyles?.textAlign;

  const calculateLeftPadding = () => {
    if (leftIconOutset) {
      if (textAlign === "center") {
        return iconSize - Math.abs(8 - iconSize);
      }

      return iconSize + 8;
    }

    return 0;
  };

  const assistiveTextLabel = assistiveText ? (
    <Text
      style={{
        textAlign,
        width: "100%",
        paddingLeft: calculateLeftPadding(),
        color: unstyledColor,
        fontSize: 12,
        paddingTop: 4,
      }}
    >
      {assistiveText}
    </Text>
  ) : null;

  const primaryTextStyle = {
    color: unstyledColor,
    fontSize: 14,
    ...pickBy(textStyles, identity),
    ...(placeholder === internalValue ? { color: placeholderTextColor } : {}),
    ...(disabled ? { color: unstyledColor } : {}),
  };

  const handleValueChange = (newValue: string, itemIndex: number) => {
    if (!placeholder || itemIndex > 0) {
      onValueChange?.(newValue, itemIndex);
    }
    setInternalValue(newValue);
  };

  return (
    /* marginsContainer */
    <View style={[styles.marginsContainer, marginStyles]}>
      {/* touchableContainer */}
      <Touchable
        disabled={disabled}
        onPress={togglePickerVisible}
        style={styles.touchableContainer}
      >
        {/* outsetContainer */}
        <View
          pointerEvents="none"
          style={[
            styles.outsetContainer,
            stylesWithoutBordersAndMargins,
            !leftIconOutset ? (borderStyles as PickerProps["style"]) : {},
          ]}
        >
          {leftIcon}

          {/* insetContainer */}
          <View
            style={[
              styles.insetContainer,
              leftIconOutset ? (borderStyles as PickerProps["style"]) : {},
            ]}
          >
            {/* primaryTextContainer */}
            <View style={styles.primaryTextContainer}>
              {labelText}

              <Text style={primaryTextStyle}>
                {String(selectedLabel ?? placeholder)}
              </Text>
            </View>

            {rightIcon}
          </View>
        </View>
        {assistiveTextLabel}
      </Touchable>

      {/* iosPicker */}
      {isIos && pickerVisible ? (
        <Portal>
          <View
            style={[
              styles.iosPicker,
              {
                backgroundColor: colors.divider,
              },
            ]}
          >
            <SafeAreaView style={styles.iosSafeArea}>
              <Button
                Icon={Icon}
                type="text"
                onPress={togglePickerVisible}
                style={styles.iosButton}
              >
                {"Close"}
              </Button>

              <NativePicker
                style={styles.iosNativePicker}
                selectedValue={internalValue}
                onValueChange={handleValueChange}
              >
                {(pickerOptions as unknown as PickerOption[]).map((option) => (
                  <NativePicker.Item
                    label={option.label}
                    value={option.value}
                    key={option.value}
                  />
                ))}
              </NativePicker>
            </SafeAreaView>
          </View>
        </Portal>
      ) : null}

      {/* nonIosPicker */}
      {!isIos && pickerVisible ? (
        <NativePicker
          enabled={pickerVisible}
          selectedValue={internalValue}
          onValueChange={handleValueChange}
          style={styles.nonIosPicker}
          ref={androidPickerRef}
          onBlur={() => setPickerVisible(false)}
        >
          {(pickerOptions as unknown as PickerOption[]).map((option) => (
            <NativePicker.Item
              label={option.label}
              value={option.value}
              key={option.value}
            />
          ))}
        </NativePicker>
      ) : null}
    </View>
  );
}
Example #24
Source File: useSpreadSheet.ts    From S2 with MIT License 4 votes vote down vote up
export function useSpreadSheet(props: SheetComponentsProps) {
  const forceUpdate = useUpdate();
  const s2Ref = React.useRef<SpreadSheet>();
  const containerRef = React.useRef<HTMLDivElement>();
  const wrapperRef = React.useRef<HTMLDivElement>();

  const {
    spreadsheet: customSpreadSheet,
    dataCfg,
    options,
    themeCfg,
    sheetType,
    onSheetUpdate = identity,
  } = props;
  /** 保存重渲 effect 的 deps */
  const updatePrevDepsRef = React.useRef<[S2DataConfig, S2Options, ThemeCfg]>([
    dataCfg,
    options,
    themeCfg,
  ]);

  const { loading, setLoading } = useLoading(s2Ref.current, props.loading);
  const pagination = usePagination(s2Ref.current, props);

  useEvents(props, s2Ref.current);

  const renderSpreadSheet = React.useCallback(
    (container: HTMLDivElement) => {
      const s2Options = getSheetComponentOptions(options);
      const s2Constructor: S2Constructor = [container, dataCfg, s2Options];
      if (customSpreadSheet) {
        return customSpreadSheet(...s2Constructor);
      }
      if (sheetType === 'table') {
        return new TableSheet(container, dataCfg, s2Options);
      }
      return new PivotSheet(container, dataCfg, s2Options);
    },
    [sheetType, options, dataCfg, customSpreadSheet],
  );

  const buildSpreadSheet = React.useCallback(() => {
    setLoading(true);
    s2Ref.current = renderSpreadSheet(containerRef.current);
    s2Ref.current.setThemeCfg(props.themeCfg);
    s2Ref.current.render();
    setLoading(false);

    // 子 hooks 内使用了 s2Ref.current 作为 dep
    // forceUpdate 一下保证子 hooks 能 rerender
    forceUpdate();

    props.getSpreadSheet?.(s2Ref.current);
  }, [props, renderSpreadSheet, setLoading, forceUpdate]);

  // init
  React.useEffect(() => {
    buildSpreadSheet();
    return () => {
      s2Ref.current.destroy();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // 重渲 effect:dataCfg, options or theme changed
  useUpdateEffect(() => {
    const [prevDataCfg, prevOptions, prevThemeCfg] = updatePrevDepsRef.current;
    updatePrevDepsRef.current = [dataCfg, options, themeCfg];

    let reloadData = false;
    let reBuildDataSet = false;
    if (!Object.is(prevDataCfg, dataCfg)) {
      reloadData = true;
      s2Ref.current?.setDataCfg(dataCfg);
    }

    if (!Object.is(prevOptions, options)) {
      if (!Object.is(prevOptions?.hierarchyType, options?.hierarchyType)) {
        // 自定义树目录需要重新构建 CustomTreePivotDataSet
        reBuildDataSet = true;
        reloadData = true;
        s2Ref.current?.setDataCfg(dataCfg);
      }
      s2Ref.current?.setOptions(options);
      s2Ref.current?.changeSheetSize(options.width, options.height);
    }
    if (!Object.is(prevThemeCfg, themeCfg)) {
      s2Ref.current?.setThemeCfg(themeCfg);
    }

    /**
     * onSheetUpdate 交出控制权
     * 由传入方决定最终的 render 模式
     */
    const renderOptions = onSheetUpdate({
      reloadData,
      reBuildDataSet,
    });

    s2Ref.current?.render(renderOptions.reloadData, {
      reBuildDataSet: renderOptions.reBuildDataSet,
    });
  }, [dataCfg, options, themeCfg, onSheetUpdate]);

  useResize({
    s2: s2Ref.current,
    container: containerRef.current,
    wrapper: wrapperRef.current,
    adaptive: props.adaptive,
  });

  return {
    s2Ref,
    containerRef,
    wrapperRef,
    loading,
    setLoading,
    pagination,
  };
}