Skip to content

Commit

Permalink
Merge pull request #3 from usdigitalresponse/kevee/ts-convert
Browse files Browse the repository at this point in the history
Convert to Typescript, refactor
  • Loading branch information
kevee authored Oct 26, 2022
2 parents 6c0ee0b + bc4e152 commit fb602db
Show file tree
Hide file tree
Showing 80 changed files with 14,167 additions and 17,199 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ module.exports = {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['react', 'react-hooks'],
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'react/prop-types': 0,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Test
on:
- push

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Setup
run: npm install

- name: Test
run: npm run test
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.0.3] - 2022-10-25

### Added

- This changelog :tada:

### Changed

- Moved all code to Typescript
- Refactored code to be more readable
- Added automated testing
- Added list to Airtable marketplace in the Readme

### Fixed

- Fixed bug where _Date & time_ generator never generated a time

## [0.0.2] - 2022-10-20

### Added

- Project was published to the Airtable marketplace

## [0.0.1] - 2022-09-20

Initial project
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Test](https://github.com/usdigitalresponse/airtable-extension-random-record-generator/actions/workflows/test.yml/badge.svg)](https://github.com/usdigitalresponse/airtable-extension-random-record-generator/actions/workflows/test.yml)

# Random record generator

An Airtable extension that makes generating fake records for the purpose of demonstrating bases.
Expand All @@ -6,7 +8,11 @@ An Airtable extension that makes generating fake records for the purpose of demo

## Installation

This extension is not yet published in the Airtable Marketplace. To install it into a base:
Add this extension [through the Airtable marketplace](https://airtable.com/marketplace/blkuZ6kbfEjCTxSNV/random-record-generator).

## Development

To start developing on this extension:

- Open your base, click **Extensions**, then **Add an extension**
- Click the small plus sign (sometimes it says "Build a custom extension" next to it)
Expand Down
3 changes: 1 addition & 2 deletions block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"version": "1.0",
"frontendEntry": "./frontend/index.js",
"frontendTestingEntry": "./test/index.js"
"frontendEntry": "./frontend/index.tsx"
}
29 changes: 22 additions & 7 deletions frontend/random-name-generator.js → frontend/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,53 @@ import {
colors,
} from '@airtable/blocks/ui'
import React from 'react'
import GenerateRecordForm from './form'
import GenerateRecordForm from './components/form'

const RandomRecordGeneratorApp = () => {
/**
* The main app component. Fetches the currently selected table using
* the cursor, checks access to create records in the table, then
* renders the generate record form.
* @returns
*/
const RandomRecordGeneratorApp: React.FC = () => {
const base = useBase()
const cursor = useCursor()
const activeTable = cursor.activeTableId
const table = activeTable && base.getTableByIdIfExists(activeTable)

// There is no active table.
if (!table) {
return (
<Box padding="1rem">
<Box padding={2}>
<Heading size="xlarge">Random Record Generator</Heading>
<Text textColor={colorUtils.getHexForColor(colors.GRAY_BRIGHT)}>
Select a table to get started.
</Text>
</Box>
)
}

const checkTablePermission = table.checkPermissionsForCreateRecord()

return (
<Box width="90%" margin="1.5rem auto">
<Box width="90%" marginY={2} marginX="auto">
{table && (
<>
<Text>
<Text variant="paragraph">
Generate random records for <strong>{table.name}</strong>.{' '}
</Text>

{checkTablePermission.hasPermission ? (
<GenerateRecordForm table={table} />
) : (
<Text textColor={colorUtils.getHexForColor(colors.RED_DARK_1)}>
{checkTablePermission.reasonDisplayString}
<Text
variant="paragraph"
textColor={colorUtils.getHexForColor(colors.RED_DARK_1)}
>
{
// @ts-ignore
checkTablePermission.reasonDisplayString
}
</Text>
)}
</>
Expand Down
81 changes: 81 additions & 0 deletions frontend/components/form/field-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react'
import { FormField, Box, colors, Text, Select } from '@airtable/blocks/ui'
import { Table, Field } from '@airtable/blocks/models'
import Sample from './sample'

interface Props {
table: Table
field: Field
fieldSettings: object
setFieldSettings: (fieldSettings: object) => void
generators: any
}

/**
* A field select - used to select a generator for a field.
* @returns
*/
const FieldSelect: React.FC<Props> = ({
table,
field,
fieldSettings,
setFieldSettings,
generators,
}) => {
const createPermission = table.checkPermissionsForCreateRecord({
[field.id]: null,
})

return (
<Box
display="flex"
flexWrap="wrap"
flexDirection="row"
key={field.id}
borderBottom="thick"
marginBottom={2}
paddingY={2}
>
<Box width="50%">
{createPermission.hasPermission ? (
<FormField label={field.name}>
<Select
value={fieldSettings[field.id]}
onChange={(newValue) => {
const newSettings = { ...fieldSettings }
// @ts-ignore
newSettings[field.id] = newValue
setFieldSettings(newSettings)
}}
options={[
{ label: 'None', value: false },
...generators
.filter((generator) => generator.types.includes(field.type))
.map((generator) => ({
label: generator.name,
value: generator.id,
})),
]}
/>
</FormField>
) : (
<Text textColor={colors.RED_DARK_1}>
{
// @ts-ignore
createPermission.reasonDisplayString
}
</Text>
)}
</Box>
<Box width="50%">
{fieldSettings[field.id] && (
<Box marginLeft={4}>
<Sample generatorId={fieldSettings[field.id]} field={field} />
</Box>
)}
</Box>
</Box>
)
}

export default FieldSelect
133 changes: 133 additions & 0 deletions frontend/components/form/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Box, Button, ConfirmationDialog, useBase } from '@airtable/blocks/ui'
import { Table } from '@airtable/blocks/models'
import generateContent from '../../lib/generators/generate-content'
import GenerateResults from '../results'
import FieldSelect from './field-select'
import NumberOfRecords from './number-records'
import React, { useState } from 'react'

interface Props {
table: Table
}

/**
* Main form component. Handles the form state and generates records.
* @returns
*/
const GenerateRecordForm: React.FC<Props> = ({ table }) => {
const base = useBase()

const [fieldSettings, setFieldSettings] = useState(
Object.fromEntries(table.fields.map((field) => [field.id, false]))
)
const [numberOfRecords, setNumberOfRecords] = useState(100)
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isGenerating, setIsGenerating] = useState(false)
const [generated, setGenerated] = useState(0)
const [hasError, setHasError] = useState(false)

const { generators } = generateContent(base)

/**
* Generate records callback. Creates a new generator
* object then creates records, updating the status state
* as each record is created.
*/
const generateRecords = async () => {
const batchSize = 10
const recordsToInsert = []
for (let i = 0; i < numberOfRecords; i++) {
const { generate } = generateContent(base)
const record = {}
for (const [fieldId, generatorId] of Object.entries(fieldSettings)) {
if (generatorId) {
record[fieldId] = await generate({
generatorId,
field: table.getFieldById(fieldId),
})
}
}
recordsToInsert.push({ fields: record })
}
let i = 0
while (i < recordsToInsert.length) {
const recordBatch = recordsToInsert.slice(i, i + batchSize)
await table.createRecordsAsync(recordBatch)
i += recordBatch.length
setGenerated(i)
}
}

// If we are generating records, return the results component
if (isGenerating) {
return (
<GenerateResults
generated={generated}
numberOfRecords={numberOfRecords}
onDone={() => {
setIsGenerating(false)
setGenerated(0)
setFieldSettings(
Object.fromEntries(table.fields.map((field) => [field.id, false]))
)
}}
/>
)
}

// The main form component

return (
<Box marginTop={2}>
{isDialogOpen && (
<ConfirmationDialog
title="Are you sure?"
body={
<>
This will generate {numberOfRecords} records in{' '}
<strong>{table.name}</strong>.
</>
}
onConfirm={() => {
setIsDialogOpen(false)
setIsGenerating(true)
generateRecords()
}}
onCancel={() => setIsDialogOpen(false)}
/>
)}
<form>
<NumberOfRecords
setHasError={setHasError}
numberOfRecords={numberOfRecords}
setNumberOfRecords={setNumberOfRecords}
/>
<Box marginY={2} paddingY={2} borderTop="thick">
{table.fields.map((field) => (
<FieldSelect
key={field.id}
table={table}
field={field}
fieldSettings={fieldSettings}
setFieldSettings={setFieldSettings}
generators={generators}
/>
))}
</Box>
<Box>
<Button
variant="primary"
disabled={hasError}
onClick={() => {
setIsDialogOpen(true)
}}
>
Generate records
</Button>
</Box>
</form>
</Box>
)
}

export default GenerateRecordForm
Loading

0 comments on commit fb602db

Please sign in to comment.