react-table#CellProps TypeScript Examples

The following examples show how to use react-table#CellProps. 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: columns.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 6 votes vote down vote up
createColumns: CreateColumns = () => [
  {
    Header: 'Details',
    Cell: ({ row: { original } }: CellProps<Domain>) => (
      <Link to={`/inventory/domain/${original.id}`}>
        <FaSearch className="margin-x-auto display-block" />
      </Link>
    )
  },
  {
    Header: 'Organization',
    accessor: (e) => e.organization.name,
    id: 'organizationName',
    Filter: ColumnFilter
  },
  {
    Header: 'Domain',
    accessor: 'name',
    id: 'reverseName',
    Filter: ColumnFilter
  },
  {
    Header: 'IP',
    accessor: 'ip',
    Filter: ColumnFilter
  },
  {
    Header: 'Ports',
    id: 'port',
    disableSortBy: true,
    accessor: ({ services }) =>
      services.map((service) => service.port).join(', '),
    Filter: ColumnFilter
  },
  {
    Header: 'Services',
    id: 'service',
    disableSortBy: true,
    accessor: (domain) => getServiceNames(domain),
    Filter: ColumnFilter
  },
  {
    Header: 'Vulnerabilities',
    id: 'vulnerability',
    accessor: (domain) =>
      domain.vulnerabilities &&
      domain.vulnerabilities
        .map((vulnerability) => vulnerability.cve)
        .join(', '),
    Filter: ColumnFilter
  },
  {
    Header: 'Last Seen',
    id: 'updatedAt',
    accessor: ({ updatedAt }) =>
      `${formatDistanceToNow(parseISO(updatedAt))} ago`,
    disableFilters: true
  },
  {
    Header: 'First Seen',
    id: 'createdAt',
    accessor: ({ createdAt }) =>
      `${formatDistanceToNow(parseISO(createdAt))} ago`,
    disableFilters: true
  }
]
Example #2
Source File: columns.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 6 votes vote down vote up
createGroupedColumns = () =>
  [
    {
      Header: 'Vulnerability',
      accessor: 'title',
      Cell: ({ value, row }: CellProps<Vulnerability>) =>
        row.original.cve ? (
          <a
            href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            {value} {extLink}
          </a>
        ) : (
          <p>{row.original.title}</p>
        ),
      width: 800,
      Filter: ColumnFilter
    },
    {
      Header: 'Severity',
      id: 'severity',
      accessor: ({ severity }) => (
        <span
          style={{
            borderBottom: `6px solid ${getSeverityColor({
              id: severity ?? ''
            })}`,
            width: '80px'
          }}
        >
          {severity}
        </span>
      ),
      width: 100,
      Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
    },
    {
      Header: 'Count',
      accessor: 'cnt',
      disableFilters: true
    },
    {
      Header: 'Description',
      accessor: 'description',
      disableFilters: true
    }
  ] as Column<Vulnerability>[]
Example #3
Source File: RawQueryFetcher.tsx    From companion-kit with MIT License 5 votes vote down vote up
function TableForObject(props: { data: any }): JSX.Element {

    const arr = props.data && props.data.array;
    if (!arr || !Array.isArray(arr) || !arr.length) {
        return (
            <>
                {JSON.stringify(props.data || '', null, 2)}
            </>
        );
    }

    const keysSet = new Set<string>();
    arr.forEach(obj => {
        Object.keys(obj).forEach(k => {
            keysSet.add(k);
        });
    });
    const keys = Array.from(keysSet);

    const columns = React.useMemo(
        () => keys.map(k => ({
            Header: k,
            accessor: k,
            Cell: (row: CellProps<any>) => {
                const o = row.cell.value;
                if (o == null) {
                    return null;
                }
                if (typeof o === 'object') {
                    return <JSONTree data={o} />;
                }
                return o + '';
            },
        })),
        [ keys ],
    );

    return Table({ columns, data: arr });
}
Example #4
Source File: ScanTasksView.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
ScanTasksView: React.FC = () => {
  const { apiPost, token } = useAuthContext();
  const [scanTasks, setScanTasks] = useState<ScanTask[]>([]);
  const [totalResults, setTotalResults] = useState(0);
  const [errors, setErrors] = useState<Errors>({});

  const killScanTask = async (index: number) => {
    try {
      const row = scanTasks[index];
      await apiPost(`/scan-tasks/${row.id}/kill`, { body: {} });
      setScanTasks(
        Object.assign([], scanTasks, {
          [index]: {
            ...row,
            status: 'failed'
          }
        })
      );
    } catch (e) {
      setErrors({
        global:
          e.status === 422 ? 'Unable to kill scan' : e.message ?? e.toString()
      });
      console.log(e);
    }
  };

  const renderExpanded = (row: Row<ScanTask>) => {
    const { original } = row;
    return (
      <div className={classes.expandedRoot}>
        {original.fargateTaskArn && (
          <>
            <h4>
              Logs
              {original.fargateTaskArn?.match('.*/(.*)') && (
                <a
                  target="_blank"
                  rel="noopener noreferrer"
                  href={`https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/${process
                    .env
                    .REACT_APP_FARGATE_LOG_GROUP!}/log-events/worker$252Fmain$252F${
                    (original.fargateTaskArn.match('.*/(.*)') || [])[1]
                  }`}
                >
                  {' '}
                  (View all on CloudWatch)
                </a>
              )}
            </h4>

            <Log
              token={token ?? ''}
              url={`${process.env.REACT_APP_API_URL}/scan-tasks/${original.id}/logs`}
            />
          </>
        )}

        <h4>Input</h4>
        <small>
          <pre>{JSON.stringify(JSON.parse(original.input), null, 2)}</pre>
        </small>
        <h4>Output</h4>
        <small>
          <pre>{original.output || 'None'}</pre>
        </small>

        {row.original.status !== 'finished' &&
          row.original.status !== 'failed' && (
            <>
              <h4>Actions</h4>
              <a
                href="# "
                onClick={(e) => {
                  e.preventDefault();
                  killScanTask(row.index);
                }}
              >
                Kill
              </a>
            </>
          )}
      </div>
    );
  };

  const columns: Column<ScanTask>[] = [
    {
      Header: 'ID',
      accessor: 'id',
      Filter: ColumnFilter,
      disableSortBy: true,
      disableFilters: true
    },
    {
      Header: 'Status',
      accessor: 'status',
      Filter: selectFilter([
        'created',
        'queued',
        'requested',
        'started',
        'finished',
        'failed'
      ]),
      disableSortBy: true
    },
    {
      Header: 'Name',
      id: 'name',
      accessor: ({ scan }) => scan?.name,
      Filter: selectFilter([
        // TODO: sync this with the SCAN_SCHEMA
        'censys',
        'amass',
        'findomain',
        'portscanner',
        'wappalyzer',
        'censysIpv4',
        'censysCertificates',
        'sslyze',
        'searchSync',
        'cve',
        'dotgov',
        'webscraper',
        'intrigueIdent',
        'shodan',
        'hibp',
        'lookingGlass',
        'dnstwist',
        'peCybersixgill',
        'peHibpSync',
        'peShodan',
        'peDomMasq',
        'rootDomainSync'
      ]),
      disableSortBy: true
    },
    {
      Header: 'Created At',
      id: 'createdAt',
      accessor: ({ createdAt }) => dateAccessor(createdAt),
      disableFilters: true
    },
    {
      Header: 'Finished At',
      id: 'finishedAt',
      accessor: ({ finishedAt }) => dateAccessor(finishedAt),
      disableFilters: true
    },
    {
      Header: 'Details',
      Cell: ({ row }: CellProps<ScanTask>) => (
        <span
          {...row.getToggleRowExpandedProps()}
          className="text-center display-block"
        >
          {row.isExpanded ? <FaMinus /> : <FaPlus />}
        </span>
      ),
      disableFilters: true
    }
  ];
  const PAGE_SIZE = 25;

  const fetchScanTasks = useCallback(
    async (query: Query<ScanTask>) => {
      const { page, sort, filters } = query;
      try {
        const { result, count } = await apiPost<ApiResponse>(
          '/scan-tasks/search',
          {
            body: {
              page,
              sort: sort[0]?.id ?? 'createdAt',
              order: sort[0]?.desc ? 'DESC' : 'ASC',
              filters: filters
                .filter((f) => Boolean(f.value))
                .reduce(
                  (accum, next) => ({
                    ...accum,
                    [next.id]: next.value
                  }),
                  {}
                )
            }
          }
        );
        setScanTasks(result);
        setTotalResults(count);
      } catch (e) {
        console.error(e);
      }
    },
    [apiPost]
  );

  const renderPagination = (table: TableInstance<ScanTask>) => (
    <Paginator table={table} totalResults={totalResults} />
  );

  return (
    <>
      {errors.global && <p className={classes.error}>{errors.global}</p>}
      <Table<ScanTask>
        renderPagination={renderPagination}
        columns={columns}
        data={scanTasks}
        pageCount={Math.ceil(totalResults / PAGE_SIZE)}
        fetchData={fetchScanTasks}
        pageSize={PAGE_SIZE}
        initialSortBy={[
          {
            id: 'createdAt',
            desc: true
          }
        ]}
        renderExpanded={renderExpanded}
      />
    </>
  );
}
Example #5
Source File: ScansView.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
ScansView: React.FC = () => {
  const { apiGet, apiPost, apiDelete } = useAuthContext();
  const [showModal, setShowModal] = useState<Boolean>(false);
  const [selectedRow, setSelectedRow] = useState<number>(0);
  const [scans, setScans] = useState<Scan[]>([]);
  const [organizationOptions, setOrganizationOptions] = useState<
    OrganizationOption[]
  >([]);
  const [tags, setTags] = useState<OrganizationTag[]>([]);
  const [scanSchema, setScanSchema] = useState<ScanSchema>({});

  const columns: Column<Scan>[] = [
    {
      Header: 'Run',
      id: 'run',
      Cell: ({ row }: { row: { index: number } }) => (
        <div
          style={{ textAlign: 'center' }}
          onClick={() => {
            runScan(row.index);
          }}
        >
          <FaPlayCircle />
        </div>
      ),
      disableFilters: true
    },
    {
      Header: 'Name',
      accessor: 'name',
      width: 200,
      id: 'name',
      disableFilters: true
    },
    {
      Header: 'Tags',
      accessor: ({ tags }) => tags.map((tag) => tag.name).join(', '),
      width: 150,
      minWidth: 150,
      id: 'tags',
      disableFilters: true
    },
    {
      Header: 'Mode',
      accessor: ({ name }) =>
        scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
      width: 150,
      minWidth: 150,
      id: 'mode',
      disableFilters: true
    },
    {
      Header: 'Frequency',
      accessor: ({ frequency, isSingleScan }) => {
        let val, unit;
        if (frequency < 60 * 60) {
          val = frequency / 60;
          unit = 'minute';
        } else if (frequency < 60 * 60 * 24) {
          val = frequency / (60 * 60);
          unit = 'hour';
        } else {
          val = frequency / (60 * 60 * 24);
          unit = 'day';
        }
        if (isSingleScan) {
          return 'Single Scan';
        }
        return `Every ${val} ${unit}${val === 1 ? '' : 's'}`;
      },
      width: 200,
      id: 'frequency',
      disableFilters: true
    },
    {
      Header: 'Last Run',
      accessor: (args: Scan) => {
        return !args.lastRun ||
          new Date(args.lastRun).getTime() === new Date(0).getTime()
          ? 'None'
          : `${formatDistanceToNow(parseISO(args.lastRun))} ago`;
      },
      width: 200,
      id: 'lastRun',
      disableFilters: true
    },
    {
      Header: 'Edit',
      id: 'edit',
      Cell: ({ row }: CellProps<Scan>) => (
        <Link to={`/scans/${row.original.id}`} style={{ color: 'black' }}>
          <FaEdit />
        </Link>
      ),
      disableFilters: true
    },
    {
      Header: 'Delete',
      id: 'delete',
      Cell: ({ row }: { row: { index: number } }) => (
        <span
          onClick={() => {
            setShowModal(true);
            setSelectedRow(row.index);
          }}
        >
          <FaTimes />
        </span>
      ),
      disableFilters: true
    },
    {
      Header: 'Description',
      accessor: ({ name }) => scanSchema[name]?.description,
      width: 200,
      maxWidth: 200,
      id: 'description',
      disableFilters: true
    }
  ];
  const [errors, setErrors] = useState<Errors>({});

  const [values] = useState<ScanFormValues>({
    name: 'censys',
    arguments: '{}',
    organizations: [],
    frequency: 1,
    frequencyUnit: 'minute',
    isGranular: false,
    isUserModifiable: false,
    isSingleScan: false,
    tags: []
  });

  React.useEffect(() => {
    document.addEventListener('keyup', (e) => {
      //Escape
      if (e.keyCode === 27) {
        setShowModal(false);
      }
    });
  }, [apiGet]);

  const fetchScans = useCallback(async () => {
    try {
      const { scans, organizations, schema } = await apiGet<{
        scans: Scan[];
        organizations: Organization[];
        schema: ScanSchema;
      }>('/scans/');
      const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
      setScans(scans);
      setScanSchema(schema);
      setOrganizationOptions(
        organizations.map((e) => ({ label: e.name, value: e.id }))
      );
      setTags(tags);
    } catch (e) {
      console.error(e);
    }
  }, [apiGet]);

  const deleteRow = async (index: number) => {
    try {
      const row = scans[index];
      await apiDelete(`/scans/${row.id}`, { body: {} });
      setScans(scans.filter((scan) => scan.id !== row.id));
    } catch (e) {
      setErrors({
        global:
          e.status === 422 ? 'Unable to delete scan' : e.message ?? e.toString()
      });
      console.log(e);
    }
  };

  const onSubmit = async (body: ScanFormValues) => {
    try {
      // For now, parse the arguments as JSON. We'll want to add a GUI for this in the future
      body.arguments = JSON.parse(body.arguments);
      setFrequency(body);

      const scan = await apiPost('/scans/', {
        body: {
          ...body,
          organizations: body.organizations
            ? body.organizations.map((e) => e.value)
            : [],
          tags: body.tags ? body.tags.map((e) => ({ id: e.value })) : []
        }
      });
      setScans(scans.concat(scan));
    } catch (e) {
      setErrors({
        global: e.message ?? e.toString()
      });
      console.log(e);
    }
  };

  const invokeScheduler = async () => {
    setErrors({ ...errors, scheduler: '' });
    try {
      await apiPost('/scheduler/invoke', { body: {} });
    } catch (e) {
      console.error(e);
      setErrors({ ...errors, scheduler: 'Invocation failed.' });
    }
  };

  /**
   * Manually runs a single scan, then immediately invokes the
   * scheduler so the scan is run.
   * @param index Row index
   */
  const runScan = async (index: number) => {
    const row = scans[index];
    try {
      await apiPost(`/scans/${row.id}/run`, { body: {} });
    } catch (e) {
      console.error(e);
      setErrors({ ...errors, scheduler: 'Run failed.' });
    }
    await invokeScheduler();
  };

  return (
    <>
      <Table<Scan> columns={columns} data={scans} fetchData={fetchScans} />
      <br></br>
      <Button type="submit" outline onClick={invokeScheduler}>
        Manually run scheduler
      </Button>
      {errors.scheduler && <p className={classes.error}>{errors.scheduler}</p>}
      <h2>Add a scan</h2>
      {errors.global && <p className={classes.error}>{errors.global}</p>}
      <ScanForm
        organizationOption={organizationOptions}
        tags={tags}
        propValues={values}
        onSubmit={onSubmit}
        type="create"
        scanSchema={scanSchema}
      ></ScanForm>
      <ImportExport<Scan>
        name="scans"
        fieldsToExport={['name', 'arguments', 'frequency']}
        onImport={async (results) => {
          // TODO: use a batch call here instead.
          const createdScans = [];
          for (const result of results) {
            createdScans.push(
              await apiPost('/scans/', {
                body: {
                  ...result,
                  // These fields are initially parsed as strings, so they need
                  // to be converted to objects.
                  arguments: JSON.parse(
                    ((result.arguments as unknown) as string) || ''
                  )
                }
              })
            );
          }
          setScans(scans.concat(...createdScans));
        }}
        getDataToExport={() =>
          scans.map((scan) => ({
            ...scan,
            arguments: JSON.stringify(scan.arguments)
          }))
        }
      />

      {showModal && (
        <div>
          <Overlay />
          <ModalContainer>
            <Modal
              actions={
                <>
                  <Button
                    outline
                    type="button"
                    onClick={() => {
                      setShowModal(false);
                    }}
                  >
                    Cancel
                  </Button>
                  <Button
                    type="button"
                    onClick={() => {
                      deleteRow(selectedRow);
                      setShowModal(false);
                    }}
                  >
                    Delete
                  </Button>
                </>
              }
              title={<h2>Delete scan?</h2>}
            >
              <p>
                Are you sure you would like to delete the{' '}
                <code>{scans[selectedRow].name}</code> scan?
              </p>
            </Modal>
          </ModalContainer>
        </div>
      )}
    </>
  );
}
Example #6
Source File: columns.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
createColumns = (updateVulnerability: any) =>
  [
    {
      Header: 'Vulnerability',
      accessor: 'title',
      Cell: ({ value, row }: CellProps<Vulnerability>) =>
        row.original.cve ? (
          <a
            href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            {value} {extLink}
          </a>
        ) : (
          <p>{row.original.title}</p>
        ),
      width: 800,
      Filter: ColumnFilter
    },
    {
      Header: 'Severity',
      id: 'severity',
      accessor: ({ severity, substate }) => (
        <span
          style={{
            borderBottom: `6px solid ${getSeverityColor({
              id: severity ?? ''
            })}`,
            width: '80px'
          }}
          // className={substate === 'unconfirmed' ? classes.severity : undefined}
        >
          {severity}
        </span>
      ),
      width: 100,
      Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
    },
    {
      Header: 'KEV',
      accessor: 'isKev',
      Cell: ({ value, row }: CellProps<Vulnerability>) =>
        value ? (
          <a
            href={`https://www.cisa.gov/known-exploited-vulnerabilities-catalog`}
            target="_blank"
            rel="noopener noreferrer"
          >
            Yes {extLink}
          </a>
        ) : (
          <p>No</p>
        ),
      width: 50,
      Filter: selectFilter([
        { value: 'true', label: 'Yes' },
        { value: 'false', label: 'No' }
      ])
    },
    {
      Header: 'Domain',
      id: 'domain',
      accessor: ({ domain }) => (
        <Link to={`/inventory/domain/${domain?.id}`}>{domain?.name}</Link>
      ),
      width: 800,
      Filter: ColumnFilter
    },
    {
      Header: 'Product',
      id: 'cpe',
      accessor: ({ cpe, service }) => {
        const product =
          service &&
          service.products.find(
            (product) => cpe && product.cpe && cpe.includes(product.cpe)
          );
        if (product)
          return product.name + (product.version ? ' ' + product.version : '');
        else return cpe;
      },
      width: 100,
      Filter: ColumnFilter
    },
    {
      Header: 'Days Open',
      id: 'createdAt',
      accessor: ({ createdAt, actions = [] }) => {
        // Calculates the total number of days a vulnerability has been open
        let daysOpen = 0;
        let lastOpenDate = createdAt;
        let lastState = 'open';
        actions.reverse();
        for (const action of actions) {
          if (action.state === 'closed' && lastState === 'open') {
            daysOpen += differenceInCalendarDays(
              parseISO(action.date),
              parseISO(lastOpenDate)
            );
            lastState = 'closed';
          } else if (action.state === 'open' && lastState === 'closed') {
            lastOpenDate = action.date;
            lastState = 'open';
          }
        }
        if (lastState === 'open') {
          daysOpen += differenceInCalendarDays(
            new Date(),
            parseISO(lastOpenDate)
          );
        }
        return daysOpen;
      },
      disableFilters: true
    },
    {
      Header: 'Status',
      id: 'state',
      width: 100,
      maxWidth: 200,
      accessor: 'state',
      Filter: selectFilter([
        'open',
        'open (unconfirmed)',
        'open (exploitable)',
        'closed',
        'closed (false positive)',
        'closed (accepted risk)',
        'closed (remediated)'
      ]),
      Cell: ({ row }: CellProps<Vulnerability>) => (
        <Dropdown
          id="state-dropdown"
          name="state-dropdown"
          onChange={(e) => {
            updateVulnerability(row.index, {
              substate: e.target.value
            });
          }}
          value={row.original.substate}
          style={{ display: 'inline-block', width: '200px' }}
        >
          <option value="unconfirmed">Open (Unconfirmed)</option>
          <option value="exploitable">Open (Exploitable)</option>
          <option value="false-positive">Closed (False Positive)</option>
          <option value="accepted-risk">Closed (Accepted Risk)</option>
          <option value="remediated">Closed (Remediated)</option>
        </Dropdown>
      )
    },
    {
      Header: 'Details',
      Cell: ({ row }: CellProps<Vulnerability>) => (
        <Link
          to={`/inventory/vulnerability/${row.original.id}`}
          style={{
            fontSize: '14px',
            cursor: 'pointer',
            color: '#484D51',
            textDecoration: 'none'
          }}
        >
          DETAILS
        </Link>
      ),
      disableFilters: true
    }
  ] as Column<Vulnerability>[]
Example #7
Source File: CalculatedPropertyTable.tsx    From viewer-components-react with MIT License 4 votes vote down vote up
CalculatedPropertyTable = ({
  iModelId,
  mappingId,
  groupId,
  setSelectedCalculatedProperty,
  setGroupModifyView,
  onCalculatedPropertyModify,
  isLoadingCalculatedProperties: isLoadingGroupProperties,
  calculatedProperties,
  refreshCalculatedProperties,
  selectedCalculatedProperty,
}: CalculatedPropertyTableProps) => {
  const apiContext = useContext(ApiContext);
  const [
    showCalculatedPropertyDeleteModal,
    setShowCalculatedPropertyDeleteModal,
  ] = useState<boolean>(false);

  const calculatedPropertiesColumns = useMemo(
    () => [
      {
        Header: "Table",
        columns: [
          {
            id: "propertyName",
            Header: "Calculated Property",
            accessor: "propertyName",
            Cell: (value: CellProps<CalculatedPropertyType>) => (
              <div
                className='iui-anchor'
                onClick={() => onCalculatedPropertyModify(value)}
              >
                {value.row.original.propertyName}
              </div>
            ),
          },
          {
            id: "dropdown",
            Header: "",
            width: 80,
            Cell: (value: CellProps<CalculatedPropertyType>) => {
              return (
                <DropdownMenu
                  menuItems={(close: () => void) => [
                    <MenuItem
                      key={0}
                      onClick={() => onCalculatedPropertyModify(value)}
                      icon={<SvgEdit />}
                    >
                      Modify
                    </MenuItem>,
                    <MenuItem
                      key={1}
                      onClick={() => {
                        setSelectedCalculatedProperty(value.row.original);
                        setShowCalculatedPropertyDeleteModal(true);
                        close();
                      }}
                      icon={<SvgDelete />}
                    >
                      Remove
                    </MenuItem>,
                  ]}
                >
                  <IconButton styleType='borderless'>
                    <SvgMore
                      style={{
                        width: "16px",
                        height: "16px",
                      }}
                    />
                  </IconButton>
                </DropdownMenu>
              );
            },
          },
        ],
      },
    ],
    [onCalculatedPropertyModify, setSelectedCalculatedProperty],
  );

  return (
    <>
      <Button
        startIcon={<SvgAdd />}
        styleType='high-visibility'
        onClick={() => {
          setGroupModifyView(PropertyMenuView.ADD_CALCULATED_PROPERTY);
        }}
      >
        Add Calculated Property
      </Button>
      <Table<CalculatedPropertyType>
        data={calculatedProperties}
        density='extra-condensed'
        columns={calculatedPropertiesColumns}
        emptyTableContent='No Calculated Properties'
        isSortable
        isLoading={isLoadingGroupProperties}
      />

      <DeleteModal
        entityName={selectedCalculatedProperty?.propertyName ?? ""}
        show={showCalculatedPropertyDeleteModal}
        setShow={setShowCalculatedPropertyDeleteModal}
        onDelete={async () => {
          const reportingClientApi = new ReportingClient(apiContext.prefix);
          await reportingClientApi.deleteCalculatedProperty(
            apiContext.accessToken,
            iModelId,
            mappingId,
            groupId,
            selectedCalculatedProperty?.id ?? "",
          );
        }}
        refresh={refreshCalculatedProperties}
      />
    </>
  );
}
Example #8
Source File: CustomCalculationTable.tsx    From viewer-components-react with MIT License 4 votes vote down vote up
CustomCalculationTable = ({
  iModelId,
  mappingId,
  groupId,
  setSelectedCustomCalculation,
  setGroupModifyView,
  onCustomCalculationModify,
  isLoadingCustomCalculations,
  customCalculations,
  refreshCustomCalculations,
  selectedCustomCalculation,
}: CustomCalculationTableProps) => {
  const apiContext = useContext(ApiContext);
  const [
    showCustomCalculationDeleteModal,
    setShowCustomCalculationDeleteModal,
  ] = useState<boolean>(false);

  const CustomCalculationsColumns = useMemo(
    () => [
      {
        Header: "Table",
        columns: [
          {
            id: "propertyName",
            Header: "Custom Calculation",
            accessor: "propertyName",
            Cell: (value: CellProps<CustomCalculationType>) => (
              <div
                className='iui-anchor'
                onClick={() => onCustomCalculationModify(value)}
              >
                {value.row.original.propertyName}
              </div>
            ),
          },
          {
            id: "formula",
            Header: "Formula",
            accessor: "formula",
          },
          {
            id: "dropdown",
            Header: "",
            width: 80,
            Cell: (value: CellProps<CustomCalculationType>) => {
              return (
                <DropdownMenu
                  menuItems={(close: () => void) => [
                    <MenuItem
                      key={0}
                      onClick={() => onCustomCalculationModify(value)}
                      icon={<SvgEdit />}
                    >
                      Modify
                    </MenuItem>,
                    <MenuItem
                      key={1}
                      onClick={() => {
                        setSelectedCustomCalculation(value.row.original);
                        setShowCustomCalculationDeleteModal(true);
                        close();
                      }}
                      icon={<SvgDelete />}
                    >
                      Remove
                    </MenuItem>,
                  ]}
                >
                  <IconButton styleType='borderless'>
                    <SvgMore
                      style={{
                        width: "16px",
                        height: "16px",
                      }}
                    />
                  </IconButton>
                </DropdownMenu>
              );
            },
          },
        ],
      },
    ],
    [onCustomCalculationModify, setSelectedCustomCalculation],
  );

  return (
    <>
      <Button
        startIcon={<SvgAdd />}
        styleType='high-visibility'
        onClick={() => {
          setGroupModifyView(PropertyMenuView.ADD_CUSTOM_CALCULATION);
        }}
      >
        Add Custom Calculation
      </Button>
      <Table<CustomCalculationType>
        data={customCalculations}
        density='extra-condensed'
        columns={CustomCalculationsColumns}
        emptyTableContent='No Custom Calculations'
        isSortable
        isLoading={isLoadingCustomCalculations}
      />

      <DeleteModal
        entityName={selectedCustomCalculation?.propertyName ?? ""}
        show={showCustomCalculationDeleteModal}
        setShow={setShowCustomCalculationDeleteModal}
        onDelete={async () => {
          const reportingClientApi = new ReportingClient(apiContext.prefix);
          await reportingClientApi.deleteCustomCalculation(
            apiContext.accessToken,
            iModelId,
            mappingId,
            groupId,
            selectedCustomCalculation?.id ?? "",
          );
        }}
        refresh={refreshCustomCalculations}
      />
    </>
  );
}
Example #9
Source File: GroupPropertyTable.tsx    From viewer-components-react with MIT License 4 votes vote down vote up
GroupPropertyTable = ({
  iModelId,
  mappingId,
  groupId,
  selectedGroupProperty,
  onGroupPropertyModify,
  setSelectedGroupProperty,
  isLoadingGroupProperties,
  groupProperties,
  refreshGroupProperties,
  setGroupModifyView,
}: GroupPropertyTableProps) => {
  const apiContext = useContext(ApiContext);
  const [showGroupPropertyDeleteModal, setShowGroupPropertyDeleteModal] =
    useState<boolean>(false);

  const groupPropertiesColumns = useMemo(
    () => [
      {
        Header: "Table",
        columns: [
          {
            id: "propertyName",
            Header: "Property",
            accessor: "propertyName",
            Cell: (value: CellProps<GroupPropertyType>) => (
              <div
                className='iui-anchor'
                onClick={() => onGroupPropertyModify(value)}
              >
                {value.row.original.propertyName}
              </div>
            ),
          },
          {
            id: "dropdown",
            Header: "",
            width: 80,
            Cell: (value: CellProps<GroupPropertyType>) => {
              return (
                <DropdownMenu
                  menuItems={(close: () => void) => [
                    <MenuItem
                      key={0}
                      onClick={() => onGroupPropertyModify(value)}
                      icon={<SvgEdit />}
                    >
                      Modify
                    </MenuItem>,
                    <MenuItem
                      key={1}
                      onClick={() => {
                        setSelectedGroupProperty(value.row.original);
                        setShowGroupPropertyDeleteModal(true);
                        close();
                      }}
                      icon={<SvgDelete />}
                    >
                      Remove
                    </MenuItem>,
                  ]}
                >
                  <IconButton styleType='borderless'>
                    <SvgMore
                      style={{
                        width: "16px",
                        height: "16px",
                      }}
                    />
                  </IconButton>
                </DropdownMenu>
              );
            },
          },
        ],
      },
    ],
    [onGroupPropertyModify, setSelectedGroupProperty],
  );

  return (
    <>
      <Button
        startIcon={<SvgAdd />}
        styleType='high-visibility'
        onClick={() => {
          setGroupModifyView(PropertyMenuView.ADD_GROUP_PROPERTY);
        }}
      >
        Add Property
      </Button>
      <Table<GroupPropertyType>
        data={groupProperties}
        density='extra-condensed'
        columns={groupPropertiesColumns}
        emptyTableContent='No Group Properties'
        isSortable
        isLoading={isLoadingGroupProperties}
      />
      <DeleteModal
        entityName={selectedGroupProperty?.propertyName ?? ""}
        show={showGroupPropertyDeleteModal}
        setShow={setShowGroupPropertyDeleteModal}
        onDelete={async () => {
          const reportingClientApi = new ReportingClient(apiContext.prefix);
          await reportingClientApi.deleteGroupProperty(
            apiContext.accessToken,
            iModelId,
            mappingId,
            groupId,
            selectedGroupProperty?.id ?? "",
          );
        }}
        refresh={refreshGroupProperties}
      />
    </>
  );
}
Example #10
Source File: Grouping.tsx    From viewer-components-react with MIT License 4 votes vote down vote up
Groupings = ({ mapping, goBack }: GroupsTreeProps) => {
  const iModelConnection = useActiveIModelConnection() as IModelConnection;
  const apiContext = useContext(ApiContext);
  const iModelId = useActiveIModelConnection()?.iModelId as string;
  const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [groupsView, setGroupsView] = useState<GroupsView>(GroupsView.GROUPS);
  const [selectedGroup, setSelectedGroup] = useState<GroupType | undefined>(
    undefined,
  );
  const hilitedElements = useRef<Map<string, string[]>>(new Map());
  const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({});
  const [isLoadingQuery, setLoadingQuery] = useState<boolean>(false);
  const [groups, setGroups] = useState<Group[]>([]);

  useEffect(() => {
    void fetchGroups(setGroups, iModelId, mapping.id ?? "", setIsLoading, apiContext);
  }, [apiContext, iModelId, mapping.id, setIsLoading]);

  const refresh = useCallback(async () => {
    setGroupsView(GroupsView.GROUPS);
    setSelectedGroup(undefined);
    setGroups([]);
    await fetchGroups(setGroups, iModelId, mapping.id ?? "", setIsLoading, apiContext);
  }, [apiContext, iModelId, mapping.id, setGroups]);

  const addGroup = () => {
    // TODO Retain selection in view without emphasizes. Goal is to make it so we can distinguish
    // hilited elements from regular elements without emphasis due to it blocking selection. For now clearing
    // selection.
    clearEmphasizedElements();
    setGroupsView(GroupsView.ADD);
  };

  const onModify = useCallback((value) => {
    clearEmphasizedElements();
    setSelectedGroup(value.row.original);
    setGroupsView(GroupsView.MODIFYING);
  }, []);

  const openProperties = useCallback((value) => {
    clearEmphasizedElements();
    setSelectedGroup(value.row.original);
    setGroupsView(GroupsView.PROPERTIES);
  }, []);

  const groupsColumns = useMemo(
    () => [
      {
        Header: "Table",
        columns: [
          {
            id: "groupName",
            Header: "Group",
            accessor: "groupName",
            Cell: (value: CellProps<GroupType>) => (
              <>
                {isLoadingQuery ? (
                  value.row.original.groupName
                ) : (
                  <div
                    className='iui-anchor'
                    onClick={(e) => {
                      e.stopPropagation();
                      openProperties(value);
                    }}
                  >
                    {value.row.original.groupName}
                  </div>
                )}
              </>
            ),
          },
          {
            id: "description",
            Header: "Description",
            accessor: "description",
          },
          {
            id: "dropdown",
            Header: "",
            width: 80,
            Cell: (value: CellProps<GroupType>) => {
              return (
                <div onClick={(e) => e.stopPropagation()}>
                  <DropdownMenu
                    disabled={isLoadingQuery}
                    menuItems={(close: () => void) => [
                      <MenuItem
                        key={0}
                        onClick={() => onModify(value)}
                        icon={<SvgEdit />}
                      >
                        Modify
                      </MenuItem>,
                      <MenuItem
                        key={1}
                        onClick={() => openProperties(value)}
                        icon={<SvgList />}
                      >
                        Properties
                      </MenuItem>,
                      <MenuItem
                        key={2}
                        onClick={() => {
                          setSelectedGroup(value.row.original);
                          setShowDeleteModal(true);
                          close();
                        }}
                        icon={<SvgDelete />}
                      >
                        Remove
                      </MenuItem>,
                    ]}
                  >
                    <IconButton
                      disabled={isLoadingQuery}
                      styleType='borderless'
                    >
                      <SvgMore
                        style={{
                          width: "16px",
                          height: "16px",
                        }}
                      />
                    </IconButton>
                  </DropdownMenu>
                </div>
              );
            },
          },
        ],
      },
    ],
    [isLoadingQuery, onModify, openProperties],
  );

  // Temp
  const stringToColor = function (str: string) {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = str.charCodeAt(i) + ((hash << 5) - hash);
    }
    let colour = "#";
    for (let i = 0; i < 3; i++) {
      const value = (hash >> (i * 8)) & 0xff;
      colour += (`00${value.toString(16)}`).substr(-2);
    }
    return colour;
  };

  const onSelect = useCallback(
    async (selectedData: GroupType[] | undefined) => {
      clearEmphasizedElements();
      if (selectedData && selectedData.length > 0) {
        setLoadingQuery(true);
        let allIds: string[] = [];
        for (const row of selectedData) {
          const query = row.query ?? "";
          if (hilitedElements.current.has(query)) {
            const hilitedIds = hilitedElements.current.get(query) ?? [];
            visualizeElements(hilitedIds, stringToColor(row.id ?? ""));
            allIds = allIds.concat(hilitedIds);
          } else {
            try {
              const ids: string[] = await fetchIdsFromQuery(
                query,
                iModelConnection,
              );
              if (ids.length === 0) {
                toaster.warning(`${row.groupName}'s query is valid but produced no results.`);
              }
              const hiliteIds = await visualizeElementsById(
                ids,
                stringToColor(row.id ?? ""),
                iModelConnection,
              );
              hilitedElements.current.set(query, hiliteIds);

              allIds = allIds.concat(ids);
            } catch {
              const index = groups.findIndex((group) => group.id === row.id);
              setSelectedRows((rowIds) => {
                const selectedRowIds = { ...rowIds };
                delete selectedRowIds[index];
                return selectedRowIds;
              });
              toaster.negative(`Could not load ${row.groupName}. Query could not be resolved.`);

            }
          }
        }
        await zoomToElements(allIds);
        setLoadingQuery(false);
      }
    },
    [iModelConnection, groups],
  );

  const controlledState = useCallback(
    (state) => {
      return {
        ...state,
        selectedRowIds: { ...selectedRows },
      };
    },
    [selectedRows],
  );

  const propertyMenuGoBack = useCallback(async () => {
    clearEmphasizedElements();
    setGroupsView(GroupsView.GROUPS);
    await refresh();
  }, [refresh]);

  const tableStateReducer = (
    newState: TableState,
    action: ActionType,
    _previousState: TableState,
    instance?: TableInstance,
  ): TableState => {
    switch (action.type) {
      case actions.toggleRowSelected: {
        const newSelectedRows = {
          ...selectedRows,
        };
        if (action.value) {
          newSelectedRows[action.id] = true;
        } else {
          delete newSelectedRows[action.id];
        }
        setSelectedRows(newSelectedRows);
        newState.selectedRowIds = newSelectedRows;
        break;
      }
      case actions.toggleAllRowsSelected: {
        if (!instance?.rowsById) {
          break;
        }
        const newSelectedRows = {} as Record<string, boolean>;
        if (action.value) {
          Object.keys(instance.rowsById).forEach(
            (id) => (newSelectedRows[id] = true),
          );
        }
        setSelectedRows(newSelectedRows);
        newState.selectedRowIds = newSelectedRows;
        break;
      }
      default:
        break;
    }
    return newState;
  };

  switch (groupsView) {
    case GroupsView.ADD:
      return (
        <GroupAction
          iModelId={iModelId}
          mappingId={mapping.id ?? ""}
          goBack={async () => {
            clearEmphasizedElements();
            setGroupsView(GroupsView.GROUPS);
            await refresh();
          }}
        />
      );
    case GroupsView.MODIFYING:
      return selectedGroup ? (
        <GroupAction
          iModelId={iModelId}
          mappingId={mapping.id ?? ""}
          group={selectedGroup}
          goBack={async () => {
            clearEmphasizedElements();
            setGroupsView(GroupsView.GROUPS);
            await refresh();
          }}
        />
      ) : null;
    case GroupsView.PROPERTIES:
      return selectedGroup ? (
        <PropertyMenu
          iModelId={iModelId}
          mappingId={mapping.id ?? ""}
          group={selectedGroup}
          goBack={propertyMenuGoBack}
        />
      ) : null;
    default:
      return (
        <>
          <WidgetHeader
            title={mapping.mappingName ?? ""}
            disabled={isLoading || isLoadingQuery}
            returnFn={async () => {
              clearEmphasizedElements();
              await goBack();
            }}
          />
          <div className='groups-container'>
            <Button
              startIcon={
                isLoadingQuery ? <ProgressRadial size="small" indeterminate /> : <SvgAdd />
              }
              styleType='high-visibility'
              disabled={isLoadingQuery}
              onClick={() => addGroup()}
            >
              {isLoadingQuery ? "Loading Group(s)" : "Add Group"}
            </Button>
            <Table<GroupType>
              data={groups}
              density='extra-condensed'
              columns={groupsColumns}
              emptyTableContent='No Groups available.'
              isSortable
              isSelectable
              onSelect={onSelect}
              isLoading={isLoading}
              isRowDisabled={() => isLoadingQuery}
              stateReducer={tableStateReducer}
              useControlledState={controlledState}
            />
          </div>
          <DeleteModal
            entityName={selectedGroup?.groupName ?? ""}
            show={showDeleteModal}
            setShow={setShowDeleteModal}
            onDelete={async () => {
              const reportingClientApi = new ReportingClient(apiContext.prefix);
              await reportingClientApi.deleteGroup(
                apiContext.accessToken,
                iModelId,
                mapping.id ?? "",
                selectedGroup?.id ?? "",
              );
            }}
            refresh={refresh}
          />
        </>
      );
  }
}
Example #11
Source File: Mapping.tsx    From viewer-components-react with MIT License 4 votes vote down vote up
Mappings = () => {
  const apiContext = useContext(ApiContext);
  const iModelId = useActiveIModelConnection()?.iModelId as string;
  const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
  const [showImportModal, setShowImportModal] = useState<boolean>(false);
  const [showBlockingOverlay, setShowBlockingOverlay] = useState<boolean>(false);
  const [mappingView, setMappingView] = useState<MappingView>(
    MappingView.MAPPINGS
  );
  const [selectedMapping, setSelectedMapping] = useState<
  Mapping | undefined
  >(undefined);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [mappings, setMappings] = useState<Mapping[]>([]);

  useEffect(() => {
    void fetchMappings(setMappings, iModelId, setIsLoading, apiContext);
  }, [apiContext, iModelId, setIsLoading]);

  useEffect(() => {
    const removeListener =
      Presentation.selection.selectionChange.addListener(onSelectionChanged);
    return () => {
      removeListener();
    };
  }, []);

  const refresh = useCallback(async () => {
    setMappingView(MappingView.MAPPINGS);
    setSelectedMapping(undefined);
    setMappings([]);
    await fetchMappings(setMappings, iModelId, setIsLoading, apiContext);
  }, [apiContext, iModelId, setMappings]);

  const addMapping = async () => {
    setMappingView(MappingView.ADDING);
  };

  const mappingsColumns = useMemo(
    () => [
      {
        Header: "Table",
        columns: [
          {
            id: "mappingName",
            Header: "Mapping Name",
            accessor: "mappingName",
            Cell: (value: CellProps<{ mappingName: string }>) => (
              <div
                className="iui-anchor"
                onClick={() => {
                  setSelectedMapping(value.row.original);
                  setMappingView(MappingView.GROUPS);
                }}
              >
                {value.row.original.mappingName}
              </div>
            ),
          },
          {
            id: "description",
            Header: "Description",
            accessor: "description",
          },
          {
            id: "dropdown",
            Header: "",
            width: 80,
            Cell: (value: CellProps<MappingType>) => {
              return (
                <DropdownMenu
                  menuItems={(close: () => void) => [
                    <MenuItem
                      key={0}
                      onClick={() => {
                        setSelectedMapping(value.row.original);
                        setMappingView(MappingView.MODIFYING);
                      }}
                      icon={<SvgEdit />}
                    >
                      Modify
                    </MenuItem>,

                    <MenuItem
                      key={1}
                      onClick={async () => {
                        setSelectedMapping(value.row.original);
                        setShowBlockingOverlay(true);
                        close();
                        await toggleExtraction(apiContext, iModelId, value.row.original);
                        await refresh();
                        setShowBlockingOverlay(false);
                      }}
                      icon={<SvgProcess />}
                    >
                      {value.row.original.extractionEnabled ? "Disable extraction" : "Enable extraction"}
                    </MenuItem>,

                    <MenuItem
                      key={2}
                      onClick={() => {
                        setSelectedMapping(value.row.original);
                        setShowDeleteModal(true);
                        close();
                      }}
                      icon={<SvgDelete />}
                    >
                      Remove
                    </MenuItem>,
                  ]}
                >
                  <IconButton styleType="borderless">
                    <SvgMore
                      style={{
                        width: "16px",
                        height: "16px",
                      }}
                    />
                  </IconButton>
                </DropdownMenu>
              );
            },
          },
        ],
      },
    ],
    [apiContext, iModelId, refresh]
  );

  switch (mappingView) {
    case MappingView.ADDING:
      return <MappingAction iModelId={iModelId} returnFn={refresh} />;
    case MappingView.MODIFYING:
      return (
        <MappingAction
          iModelId={iModelId}
          mapping={selectedMapping}
          returnFn={refresh}
        />
      );
    case MappingView.GROUPS:
      return (
        <Groupings
          mapping={selectedMapping as Mapping}
          goBack={refresh}
        />
      );
    default:
      return (
        <>
          <BlockingOverlay isVisible={showBlockingOverlay} />
          <WidgetHeader title="Mappings" />
          <div className="mappings-container">
            <div className="table-toolbar">
              <Button
                startIcon={<SvgAdd />}
                onClick={async () => addMapping()}
                styleType="high-visibility"
              >
                New
              </Button>
              <ButtonGroup onClick={() => setShowImportModal(true)}>
                <IconButton title="Import Mappings">
                  <SvgImport />
                </IconButton>
              </ButtonGroup>
            </div>
            <Table<MappingType>
              data={mappings}
              density="extra-condensed"
              columns={mappingsColumns}
              emptyTableContent="No Mappings available."
              isSortable
              isLoading={isLoading}
            />
          </div>
          <DeleteModal
            entityName={selectedMapping?.mappingName ?? ""}
            show={showDeleteModal}
            setShow={setShowDeleteModal}
            onDelete={async () => {
              const reportingClientApi = new ReportingClient(apiContext.prefix);
              await reportingClientApi.deleteMapping(
                apiContext.accessToken,
                iModelId,
                selectedMapping?.id ?? ""
              );
            }}
            refresh={refresh}
          />
          <MappingImportWizardModal
            show={showImportModal}
            setShow={setShowImportModal}
            onFinish={refresh}
          />
        </>
      );
  }
}
Example #12
Source File: PropertyMenu.tsx    From viewer-components-react with MIT License 4 votes vote down vote up
PropertyMenu = ({
  iModelId,
  mappingId,
  group,
  goBack,
  hideGroupProps = false,
  hideCalculatedProps = false,
  hideCustomCalculationProps = false,
}: PropertyModifyProps) => {
  const groupId = group.id ?? "";

  const apiContext = useContext(ApiContext);

  const iModelConnection = useActiveIModelConnection() as IModelConnection;
  const [propertyMenuView, setPropertyMenuView] = useState<PropertyMenuView>(
    PropertyMenuView.DEFAULT,
  );
  const [selectedGroupProperty, setSelectedGroupProperty] = useState<
  GroupPropertyType | undefined
  >(undefined);
  const [selectedCalculatedProperty, setSelectedCalculatedProperty] = useState<
  CalculatedPropertyType | undefined
  >(undefined);
  const [selectedCustomCalculation, setSelectedCustomCalculation] = useState<
  CustomCalculationType | undefined
  >(undefined);
  const [isInformationPanelOpen, setIsInformationPanelOpen] =
    useState<boolean>(false);
  const [resolvedHiliteIds, setResolvedHiliteIds] = useState<string[]>([]);
  const [keySet, setKeySet] = useState<KeySet>();
  const [isLoading, setIsLoading] = useState<boolean>(true);

  const fetchGroupProperties = useMemo(
    () => {
      const reportingClientApi = new ReportingClient(apiContext.prefix);
      return async () => reportingClientApi.getGroupProperties(apiContext.accessToken, iModelId, mappingId, groupId);
    },
    [apiContext, iModelId, mappingId, groupId],
  );
  const { isLoading: isLoadingGroupProperties, data: groupProperties, refreshData: refreshGroupProperties } =
    useCombinedFetchRefresh<GroupPropertyType>(fetchGroupProperties);

  const fetchCalculatedProperties = useMemo(
    () => {
      const reportingClientApi = new ReportingClient(apiContext.prefix);
      return async () => reportingClientApi.getCalculatedProperties(apiContext.accessToken, iModelId, mappingId, groupId);
    },
    [apiContext, iModelId, mappingId, groupId],
  );
  const { isLoading: isLoadingCalculatedProperties, data: calculatedProperties, refreshData: refreshCalculatedProperties } =
    useCombinedFetchRefresh<CalculatedPropertyType>(fetchCalculatedProperties);

  const fetchCustomCalculations = useMemo(
    () => {
      const reportingClientApi = new ReportingClient(apiContext.prefix);
      return async () => reportingClientApi.getCustomCalculations(apiContext.accessToken, iModelId, mappingId, groupId);
    },
    [apiContext, iModelId, mappingId, groupId],
  );
  const { isLoading: isLoadingCustomCalculations, data: customCalculations, refreshData: refreshCustomCalculations } =
    useCombinedFetchRefresh<CustomCalculationType>(fetchCustomCalculations);

  const properties = useMemo(() => convertToPropertyMap(groupProperties, calculatedProperties), [groupProperties, calculatedProperties]);

  useEffect(() => {
    const initialize = async () => {
      try {
        const ids = await fetchIdsFromQuery(group.query ?? "", iModelConnection);
        if (ids.length === 0) {
          toaster.warning("The query is valid but produced no results.");
          await goBack();
        }
        const keys = await manufactureKeys(ids, iModelConnection);
        setKeySet(keys);
        Presentation.selection.clearSelection(
          "GroupingMappingWidget",
          iModelConnection,
        );
        clearEmphasizedElements();
        const resolvedIds = await visualizeElementsByKeys(keys, "red");
        await zoomToElements(resolvedIds);
        setResolvedHiliteIds(resolvedIds);
        setIsLoading(false);
      } catch {
        toaster.negative(`Could not load ${group.groupName}.`);
        await goBack();
      }
    };
    void initialize();
  }, [iModelConnection, group.query, goBack, group.groupName]);

  const onGroupPropertyModify = useCallback(
    (value: CellProps<GroupPropertyType>) => {
      setSelectedGroupProperty(value.row.original);
      setPropertyMenuView(PropertyMenuView.MODIFY_GROUP_PROPERTY);
    },
    [],
  );

  const onCalculatedPropertyModify = useCallback(
    (value: CellProps<CalculatedPropertyType>) => {
      setSelectedCalculatedProperty(value.row.original);
      setPropertyMenuView(PropertyMenuView.MODIFY_CALCULATED_PROPERTY);
    },
    [],
  );

  const onCustomCalculationModify = useCallback(
    (value: CellProps<CustomCalculationType>) => {
      setSelectedCustomCalculation(value.row.original);
      setPropertyMenuView(PropertyMenuView.MODIFY_CUSTOM_CALCULATION);
    },
    [],
  );

  const groupPropertyReturn = useCallback(async (modified: boolean) => {
    setPropertyMenuView(PropertyMenuView.DEFAULT);
    modified && await refreshGroupProperties();
  }, [refreshGroupProperties]);

  const calculatedPropertyReturn = useCallback(async (modified: boolean) => {
    visualizeElements(resolvedHiliteIds, "red");
    await zoomToElements(resolvedHiliteIds);
    setPropertyMenuView(PropertyMenuView.DEFAULT);
    modified && await refreshCalculatedProperties();
  }, [resolvedHiliteIds, refreshCalculatedProperties]);

  const customCalculationReturn = useCallback(async (modified: boolean) => {
    setPropertyMenuView(PropertyMenuView.DEFAULT);
    modified && await refreshCustomCalculations();
  }, [refreshCustomCalculations]);

  if (isLoading) {
    return (
      <div className='loading-overlay'>
        <Text>Loading Group</Text>
        <ProgressRadial indeterminate />
        <Text>Please wait...</Text>
      </div>
    );
  }

  switch (propertyMenuView) {
    case PropertyMenuView.ADD_GROUP_PROPERTY:
      return (
        <GroupPropertyAction
          iModelId={iModelId}
          mappingId={mappingId}
          groupId={groupId}
          keySet={keySet ?? new KeySet()}
          returnFn={groupPropertyReturn}
        />
      );
    case PropertyMenuView.MODIFY_GROUP_PROPERTY:
      return (
        <GroupPropertyAction
          iModelId={iModelId}
          mappingId={mappingId}
          groupId={groupId}
          keySet={keySet ?? new KeySet()}
          groupPropertyId={selectedGroupProperty?.id ?? ""}
          groupPropertyName={selectedGroupProperty?.propertyName ?? ""}
          returnFn={groupPropertyReturn}
        />
      );
    case PropertyMenuView.ADD_CALCULATED_PROPERTY:
      return (
        <CalculatedPropertyAction
          iModelId={iModelId}
          mappingId={mappingId}
          groupId={groupId}
          ids={resolvedHiliteIds}
          returnFn={calculatedPropertyReturn}
        />
      );
    case PropertyMenuView.MODIFY_CALCULATED_PROPERTY:
      return (
        <CalculatedPropertyAction
          iModelId={iModelId}
          mappingId={mappingId}
          groupId={groupId}
          property={selectedCalculatedProperty}
          ids={resolvedHiliteIds}
          returnFn={calculatedPropertyReturn}
        />
      );
    case PropertyMenuView.ADD_CUSTOM_CALCULATION:
      return (
        <CustomCalculationAction
          iModelId={iModelId}
          mappingId={mappingId}
          groupId={groupId}
          properties={properties}
          returnFn={customCalculationReturn}
        />
      );
    case PropertyMenuView.MODIFY_CUSTOM_CALCULATION:
      return (
        <CustomCalculationAction
          iModelId={iModelId}
          mappingId={mappingId}
          groupId={groupId}
          properties={properties}
          customCalculation={selectedCustomCalculation}
          returnFn={customCalculationReturn}
        />
      );
    default:
      return (
        <InformationPanelWrapper className='property-menu-wrapper'>
          <div className='property-header'>
            <WidgetHeader
              title={`${group.groupName ?? ""}`}
              returnFn={goBack}
            />
            <IconButton
              styleType='borderless'
              onClick={() => setIsInformationPanelOpen(true)}
            >
              <SvgProperties />
            </IconButton>
          </div>
          <div className='property-menu-container'>
            {!hideGroupProps && (
              <div className='property-table'>
                <GroupPropertyTable
                  iModelId={iModelId}
                  mappingId={mappingId}
                  groupId={groupId}
                  onGroupPropertyModify={onGroupPropertyModify}
                  setSelectedGroupProperty={setSelectedGroupProperty}
                  setGroupModifyView={setPropertyMenuView}
                  isLoadingGroupProperties={isLoadingGroupProperties}
                  groupProperties={groupProperties}
                  refreshGroupProperties={refreshGroupProperties}
                  selectedGroupProperty={selectedGroupProperty}
                />
              </div>
            )}

            {!hideCalculatedProps && (
              <div className='property-table'>
                <CalculatedPropertyTable
                  iModelId={iModelId}
                  mappingId={mappingId}
                  groupId={groupId}
                  onCalculatedPropertyModify={onCalculatedPropertyModify}
                  setSelectedCalculatedProperty={setSelectedCalculatedProperty}
                  setGroupModifyView={setPropertyMenuView}
                  isLoadingCalculatedProperties={isLoadingCalculatedProperties}
                  calculatedProperties={calculatedProperties}
                  refreshCalculatedProperties={refreshCalculatedProperties}
                  selectedCalculatedProperty={selectedCalculatedProperty}
                />
              </div>
            )}
            {!hideCustomCalculationProps && (
              <div className='property-table'>
                <CustomCalculationTable
                  iModelId={iModelId}
                  mappingId={mappingId}
                  groupId={groupId}
                  onCustomCalculationModify={onCustomCalculationModify}
                  setSelectedCustomCalculation={setSelectedCustomCalculation}
                  setGroupModifyView={setPropertyMenuView}
                  isLoadingCustomCalculations={isLoadingCustomCalculations}
                  customCalculations={customCalculations}
                  refreshCustomCalculations={refreshCustomCalculations}
                  selectedCustomCalculation={selectedCustomCalculation}
                />
              </div>
            )}
          </div>
          <InformationPanel
            className='information-panel'
            isOpen={isInformationPanelOpen}
          >
            <InformationPanelHeader
              onClose={() => setIsInformationPanelOpen(false)}
            >
              <Text variant='subheading'>{`${group.groupName ?? ""
              } Information`}</Text>
            </InformationPanelHeader>
            <InformationPanelBody>
              <div className='information-body'>
                <LabeledTextarea
                  label='Query'
                  rows={15}
                  readOnly
                  defaultValue={group.query ?? ""}
                />
              </div>
            </InformationPanelBody>
          </InformationPanel>
        </InformationPanelWrapper>
      );
  }
}