Skip to content

Commit

Permalink
new test template with custom field extensions using the new hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
jbolda committed Feb 15, 2023
1 parent 1324ee3 commit 1a540cf
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 5 deletions.
4 changes: 4 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ catalog:
target: ../../templates/standard-microservice/template.yaml
rules:
- allow: [Template]
- type: file
target: ../../templates/echo-repo-with-auth/template.yaml
rules:
- allow: [Template]

humanitec:
orgId: the-frontside-software-inc
Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"@backstage/plugin-search-react": "^1.4.0",
"@backstage/plugin-tech-radar": "^0.6.0",
"@backstage/plugin-techdocs": "^1.4.3",
"@backstage/plugin-techdocs-react": "^1.1.2",
"@backstage/plugin-techdocs-module-addons-contrib": "^1.0.9",
"@backstage/plugin-techdocs-react": "^1.1.2",
"@backstage/plugin-user-settings": "^0.6.2",
"@backstage/theme": "^0.2.16",
"@frontside/backstage-plugin-effection-inspector": "^0.1.5",
Expand Down
10 changes: 9 additions & 1 deletion packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
} from '@backstage/plugin-catalog-import';
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { NextScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
import { allFieldExtensions } from './scaffolder';
import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import {
Expand Down Expand Up @@ -105,7 +107,13 @@ const routes = (
path="/create/legacy"
element={<ScaffolderPage groups={[]} />}
/>
<Route path="/create" element={<NextScaffolderPage FormProps={{ noHtml5Validate: true }} />} />
<Route path="/create" element={<NextScaffolderPage FormProps={{ noHtml5Validate: true }} />} >
<ScaffolderFieldExtensions>
{allFieldExtensions.map(Component => (
<Component />
))}
</ScaffolderFieldExtensions>
</Route>
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route
path="/tech-radar"
Expand Down
51 changes: 51 additions & 0 deletions packages/app/src/scaffolder/GithubAuth/GithubAuthExtension.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useMemo, useState } from 'react';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { useAuth, RequestUserCredentials } from 'scaffolder-frontend-auth';

export const GithubAuth = (props: FieldExtensionComponentProps<string>) => {
const { uiSchema, onChange, rawErrors } = props;
const uiOptions = useMemo(() => uiSchema?.['ui:options'] ?? {}, [uiSchema]);

const requestUserCredentials =
uiOptions?.requestUserCredentials &&
typeof uiOptions?.requestUserCredentials === 'object'
? (uiOptions?.requestUserCredentials as RequestUserCredentials)
: undefined;

const token = useAuth({
url: 'https://github.com',
requestUserCredentials,
});

return (
<FormControl margin="normal" required error={rawErrors?.length > 0}>
<InputLabel htmlFor="ownerWithRepo">owner/repo</InputLabel>
<Input
id="ownerWithRepo"
aria-describedby="ownerRepoField"
onChange={e => onChange(e.target?.value)}
/>
<FormHelperText id="ownerRepoField">
{`The owner/repo combination to read metadata, e.g. thefrontside/playhouse${
token ? `, using the token ending with ${token?.slice(-5)}` : ''
}`}
</FormHelperText>
</FormControl>
);
};

export const validateOwnerRepoCombination = (
value: string,
validation: { addError: (arg0: string) => void },
) => {
const parts = value?.split('/');

if (parts?.length !== 2) {
validation.addError(`Needs an owner/project format.`);
}
};
23 changes: 23 additions & 0 deletions packages/app/src/scaffolder/GithubAuth/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// packages/app/src/scaffolder/ValidateKebabCase/extensions.ts

/*
This is where the magic happens and creates the custom field extension.
Note that if you're writing extensions part of a separate plugin,
then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`.
*/

import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
import {
GithubAuth,
validateOwnerRepoCombination,
} from './GithubAuthExtension';

export const GithubAuthFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'GithubAuth',
component: GithubAuth,
validation: validateOwnerRepoCombination,
}),
);
3 changes: 3 additions & 0 deletions packages/app/src/scaffolder/GithubAuth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// packages/app/src/scaffolder/ValidateKebabCase/index.ts

export { GithubAuthFieldExtension } from './extensions';
92 changes: 92 additions & 0 deletions packages/app/src/scaffolder/GithubQuery/GithubQueryExtension.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { useCallback, useMemo, useState } from 'react';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
// eslint-disable-next-line import/no-extraneous-dependencies
import Autocomplete from '@material-ui/lab/Autocomplete';
// eslint-disable-next-line import/no-extraneous-dependencies
import { TextField } from '@material-ui/core';
// eslint-disable-next-line import/no-extraneous-dependencies
import {
useFetchWithAuth,
RequestUserCredentials,
} from 'scaffolder-frontend-auth';

export const GithubQuery = (props: FieldExtensionComponentProps<string>) => {
const { uiSchema, onChange, rawErrors, formData, required } = props;
const [ownerInput, setOwnerInput] = useState<string>('thefrontside');
const [owner, setOwner] = useState<string>('thefrontside');
const uiOptions = useMemo(() => uiSchema?.['ui:options'] ?? {}, [uiSchema]);

const requestUserCredentials =
uiOptions?.requestUserCredentials &&
typeof uiOptions?.requestUserCredentials === 'object'
? (uiOptions?.requestUserCredentials as RequestUserCredentials)
: undefined;

const { value, loading, error } = useFetchWithAuth({
url: 'https://github.com',
requestUserCredentials,
fetchOpts: {
url: `https://api.github.com/orgs/${owner}/repos`,
options: {
headers: {
Accept: 'application/vnd.github+json',
Authorization: 'Bearer ',
// this isn't allowed so *shrugs*
// per_page: '100',
},
},
headersRequiringToken: ['Authorization'],
},
});

const onSelect = useCallback(
(_: any, selectValue: string | null) => {
onChange(selectValue ?? undefined);
},
[onChange],
);

return (
<>
<FormControl margin="normal" required error={rawErrors?.length > 0}>
<InputLabel htmlFor="owner">organization</InputLabel>
<Input
id="owner"
aria-describedby="ownerField"
value={ownerInput}
onChange={e => setOwnerInput(e.target?.value)}
onBlur={() => setOwner(ownerInput)}
/>
<FormHelperText id="ownerField">
The owner to query a list of repositories
</FormHelperText>
</FormControl>
<FormControl margin="normal" error={rawErrors?.length > 0 && !formData}>
<Autocomplete
value={(formData as string) || ''}
loading={loading}
onChange={onSelect}
options={value?.map((repo: { name: string }) => repo.name) ?? []}
autoSelect
renderInput={params => (
<TextField
{...params}
margin="dense"
FormHelperTextProps={{
margin: 'dense',
style: { marginLeft: 0 },
}}
variant="outlined"
required={required}
InputProps={params.InputProps}
/>
)}
/>
</FormControl>
</>
);
};
19 changes: 19 additions & 0 deletions packages/app/src/scaffolder/GithubQuery/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// packages/app/src/scaffolder/ValidateKebabCase/extensions.ts

/*
This is where the magic happens and creates the custom field extension.
Note that if you're writing extensions part of a separate plugin,
then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`.
*/

import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
import { GithubQuery } from './GithubQueryExtension';

export const GithubQueryFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'GithubQuery',
component: GithubQuery,
}),
);
3 changes: 3 additions & 0 deletions packages/app/src/scaffolder/GithubQuery/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// packages/app/src/scaffolder/ValidateKebabCase/index.ts

export { GithubQueryFieldExtension } from './extensions';
7 changes: 7 additions & 0 deletions packages/app/src/scaffolder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { GithubAuthFieldExtension } from './GithubAuth';
import { GithubQueryFieldExtension } from './GithubQuery';

export const allFieldExtensions = [
GithubAuthFieldExtension,
GithubQueryFieldExtension,
];
74 changes: 74 additions & 0 deletions templates/echo-repo-with-auth/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: echo-repo-with-auth
title: Echo Repo With Auth
description: A test template for working with the custom auth hook
spec:
owner: [email protected]
type: service
parameters:
- title: Github Custom Auth
required:
- customRepoUrl
properties:
customRepoUrl: # creating github repo and for registering catalog component
title: Repository
type: string
ui:field: GithubAuth
ui:options:
requestUserCredentials:
secretsKey: USER_CUSTOM_OAUTH_TOKEN
additionalScopes:
github:
- workflow
allowedHosts:
- github.com
- title: Github Auth And Query
required:
- customQueried
properties:
customQueried: # creating github repo and for registering catalog component
title: Repository
type: string
ui:field: GithubQuery
ui:options:
requestUserCredentials:
additionalScopes:
github:
- workflow
allowedHosts:
- github.com
- title: RepoUrlPicker
required:
- builtInRepoUrl
properties:
builtInRepoUrl: # creating github repo and for registering catalog component
title: Repository
type: string
ui:field: RepoUrlPicker
ui:options:
# https://backstage.io/docs/features/software-templates/writing-templates#using-the-users-oauth-token
requestUserCredentials:
secretsKey: USER_BUILTIN_OAUTH_TOKEN
additionalScopes:
github:
- workflow
allowedHosts:
- github.com

steps:
- name: Read Environment
id: environment
action: backend:get-environment

output:
links:
- title: Repository Custom
url: ${{ parameters.customRepoUrl }} # link to the remote repository
- title: Repository Queried
url: ${{ parameters.customQueried }} # link to the remote repository
- title: Repository Built-In
url: ${{ parameters.builtInRepoUrl }} # link to the remote repository
- title: Environment Org ID
url: ${{ steps['environment'].output.orgId }} # link to the remote repository
23 changes: 20 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9434,13 +9434,20 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==

"@types/react-dom@*", "@types/react-dom@<18.0.0", "@types/react-dom@^17", "@types/react-dom@^18.0.0":
"@types/react-dom@*", "@types/react-dom@<18.0.0":
version "17.0.18"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.18.tgz#8f7af38f5d9b42f79162eea7492e5a1caff70dc2"
integrity sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==
dependencies:
"@types/react" "^17"

"@types/react-dom@^18.0.0":
version "18.0.10"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352"
integrity sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==
dependencies:
"@types/react" "*"

"@types/react-redux@^7.1.20":
version "7.1.24"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0"
Expand Down Expand Up @@ -15564,11 +15571,21 @@ graphql-ws@^5.4.1:
resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.9.1.tgz#9c0fa48ceb695d61d574ed3ab21b426729e87f2d"
integrity sha512-mL/SWGBwIT9Meq0NlfS55yXXTOeWPMbK7bZBEZhFu46bcGk1coTx2Sdtzxdk+9yHWngD+Fk1PZDWaAutQa9tpw==

graphql@*, [email protected], "graphql@^15.0.0 || ^16.0.0", graphql@^15.5.0, graphql@^15.5.1, graphql@^16.0.0, graphql@^16.3.0, graphql@^16.5.0, graphql@^16.6.0:
graphql@*, "graphql@^15.0.0 || ^16.0.0", graphql@^16.0.0, graphql@^16.3.0, graphql@^16.5.0, graphql@^16.6.0:
version "16.6.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb"
integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==

[email protected]:
version "16.5.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85"
integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==

graphql@^15.5.0, graphql@^15.5.1:
version "15.8.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38"
integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==

gtoken@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-6.1.0.tgz#62938c679b364662ce21077858e0db3cfe025363"
Expand Down Expand Up @@ -22278,7 +22295,7 @@ react-universal-interface@^0.6.2:
resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"
integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==

react-use@^17.2.4, react-use@^17.3.2:
react-use@^17.2.4, react-use@^17.3.2, react-use@^17.4.0:
version "17.4.0"
resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.4.0.tgz#cefef258b0a6c534a5c8021c2528ac6e1a4cdc6d"
integrity sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q==
Expand Down

0 comments on commit 1a540cf

Please sign in to comment.