feat: Resource type description is now displayed when creating a new resource (#2834)

This commit is contained in:
Guillaume Bouvignies
2023-12-12 06:58:00 +01:00
committed by GitHub
parent bbf3a56e88
commit c06a56ab78
7 changed files with 107 additions and 80 deletions
+3 -2
View File
@@ -84,7 +84,8 @@ async function pull(opts: GlobalOptions) {
(y) =>
y.name === x.name &&
typeof y.schema !== "string" &&
deepEqual(y.schema, x.schema)
deepEqual(y.schema, x.schema) &&
y.description === x.description
)
) {
log.info("skipping " + x.name + " (same as current)");
@@ -106,7 +107,7 @@ const command = new Command()
.name("hub")
.description("Hub related commands. EXPERIMENTAL. INTERNAL USE ONLY.")
.command("pull")
.description("pull any supported defintions. EXPERIMENTAL.")
.description("pull any supported definitions. EXPERIMENTAL.")
.action(pull as any);
export default command;
@@ -1,5 +1,5 @@
<script lang="ts">
import { OauthService, ResourceService } from '$lib/gen'
import { OauthService, type ResourceType } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptySchema, emptyString } from '$lib/utils'
import SchemaForm from './SchemaForm.svelte'
@@ -10,7 +10,8 @@
import Popup from './common/popup/Popup.svelte'
import Button from './common/button/Button.svelte'
export let resource_type: string
export let resourceType: string
export let resourceTypeInfo: ResourceType | undefined
export let args: Record<string, any> | any = {}
export let linkedSecret: string | undefined = undefined
export let isValid = true
@@ -25,14 +26,11 @@
supabaseWizard = (await OauthService.listOAuthConnects())['supabase_wizard'] != undefined
}
async function loadSchema() {
if (!resourceTypeInfo) return
rawCode = '{}'
viewJsonSchema = false
try {
const rt = await ResourceService.getResourceType({
workspace: $workspaceStore!,
path: resource_type
})
schema = rt.schema
schema = resourceTypeInfo.schema
notFound = false
} catch (e) {
notFound = true
@@ -68,7 +66,7 @@
}
}
$: resource_type == 'postgresql' && isSupabaseAvailable()
$: resourceType == 'postgresql' && isSupabaseAvailable()
let connectionString = ''
let validConnectionString = true
@@ -111,8 +109,8 @@
right: 'As JSON'
}}
/>
<TestConnection {resource_type} {args} />
{#if resource_type == 'postgresql'}
<TestConnection {resourceType} {args} />
{#if resourceType == 'postgresql'}
<Popup
let:close
floatingConfig={{
@@ -158,7 +156,7 @@
</div>
</Popup>
{/if}
{#if resource_type == 'postgresql' && supabaseWizard}
{#if resourceType == 'postgresql' && supabaseWizard}
<a
target="_blank"
href="/api/oauth/connect/supabase_wizard"
@@ -171,7 +169,7 @@
</div>
{:else}
<p class="italic text-tertiary text-xs mb-4"
>No corresponding resource type found in your workspace for {resource_type}. Define the value in
>No corresponding resource type found in your workspace for {resourceType}. Define the value in
JSON directly</p
>
{/if}
+59 -46
View File
@@ -6,7 +6,7 @@
airtable: {
img: '/airtable_connect.png',
instructions: [
'Click to <a href="https://airtable.com/create/tokens" target="_blank" rel=”noopener noreferrer”>https://airtable.com/create/tokens</a>',
'Go to <a href="https://airtable.com/create/tokens" target="_blank" rel=”noopener noreferrer”>https://airtable.com/create/tokens</a>',
'Click on "Create new token"',
'Set a name, specify the scopes or the access level and click on "Create token"',
'Copy the token'
@@ -24,11 +24,6 @@
'Find "API Token"'
]
},
git_repository: {
instructions: [
'Only token-based authentication it supported for this resource type right now. When authentication is needed, the username and token can be included directly in the URL (i.e. https://[USERNAME]:[TOKEN]@github.com/[ORG|USER]/[REPO_NAME].git).'
]
},
mailchimp: {
img: '/mailchimp_connect.png',
instructions: [
@@ -63,8 +58,14 @@
<script lang="ts">
import { oauthStore, workspaceStore } from '$lib/stores'
import IconedResourceType from './IconedResourceType.svelte'
import { OauthService, ResourceService, VariableService, type TokenResponse } from '$lib/gen'
import { emptyString, truncateRev } from '$lib/utils'
import {
OauthService,
ResourceService,
VariableService,
type TokenResponse,
type ResourceType
} from '$lib/gen'
import { emptyString, truncateRev, urlize } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import Path from './Path.svelte'
import { Button, Drawer, Skeleton } from './common'
@@ -76,6 +77,7 @@
import { sendUserToast } from '$lib/toast'
import OauthScopes from './OauthScopes.svelte'
import DarkModeObserver from './DarkModeObserver.svelte'
import Markdown from 'svelte-exmarkdown'
export let newPageOAuth = false
@@ -100,8 +102,8 @@
| undefined = undefined
let args: any = {}
$: linkedSecretCandidates = apiTokenApps[resource_type]?.linkedSecret
? ([apiTokenApps[resource_type]?.linkedSecret] as string[])
$: linkedSecretCandidates = apiTokenApps[resourceType]?.linkedSecret
? ([apiTokenApps[resourceType]?.linkedSecret] as string[])
: args != undefined
? Object.keys(args).filter((x) =>
['token', 'secret', 'key', 'pass', 'private'].some((y) => x.toLowerCase().includes(y))
@@ -142,7 +144,8 @@
let description = ''
let drawer: Drawer
let resource_type = ''
let resourceType = ''
let resourceTypeInfo: ResourceType | undefined = undefined
let step = 1
let no_back = false
@@ -154,11 +157,11 @@
value = ''
description = ''
no_back = false
resource_type = rt ?? ''
resourceType = rt ?? ''
await loadConnects()
const connect = connects?.[resource_type]
const connect = connects?.[resourceType]
if (connect) {
scopes = connect.scopes
extra_params = Object.entries(connect.extra_params ?? {})
@@ -172,7 +175,7 @@
}
export function openFromOauth(rt: string) {
resource_type = rt
resourceType = rt
value = $oauthStore?.access_token!
valueToken = $oauthStore!
$oauthStore = undefined
@@ -220,10 +223,14 @@
async function next() {
if (step == 1 && manual) {
resourceTypeInfo = await ResourceService.getResourceType({
workspace: $workspaceStore!,
path: resourceType
})
step += 1
args = {}
} else if (step == 1 && !manual) {
const url = new URL(`/api/oauth/connect/${resource_type}`, window.location.origin)
const url = new URL(`/api/oauth/connect/${resourceType}`, window.location.origin)
url.searchParams.append('scopes', scopes.join('+'))
if (extra_params.length > 0) {
extra_params.forEach(([key, value]) => url.searchParams.append(key, value))
@@ -260,7 +267,7 @@
refresh_token: valueToken.refresh_token ?? '',
expires_in: valueToken.expires_in,
owner: path.split('/').slice(0, 2).join('/'),
client: resource_type
client: resourceType
}
})
)
@@ -278,7 +285,7 @@
value: v,
is_secret: true,
description: emptyString(description)
? `${manual ? 'Token' : 'OAuth token'} for ${resource_type}`
? `${manual ? 'Token' : 'OAuth token'} for ${resourceType}`
: description,
is_oauth: !manual,
account: account
@@ -291,7 +298,7 @@
await ResourceService.createResource({
workspace: $workspaceStore!,
requestBody: {
resource_type,
resource_type: resourceType,
path,
value: resourceValue,
description
@@ -313,14 +320,14 @@
$: isGoogleSignin =
step == 1 &&
(resource_type == 'google' ||
resource_type == 'gmail' ||
resource_type == 'gcal' ||
resource_type == 'gdrive' ||
resource_type == 'gsheets')
(resourceType == 'google' ||
resourceType == 'gmail' ||
resourceType == 'gcal' ||
resourceType == 'gdrive' ||
resourceType == 'gsheets')
$: disabled =
(step == 1 && resource_type == '') ||
(step == 1 && resourceType == '') ||
(step == 2 &&
value == '' &&
args &&
@@ -388,11 +395,11 @@
<Button
size="sm"
variant="border"
color={key === resource_type ? 'blue' : 'light'}
btnClasses={key === resource_type ? '!border-2' : 'm-[1px]'}
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = false
resource_type = key
resourceType = key
scopes = values.scopes
extra_params = Object.entries(values.extra_params ?? {})
}}
@@ -414,9 +421,9 @@
>
</div>
{/if}
{#if manual == false && resource_type != ''}
{#if manual == false && resourceType != ''}
<h3>Scopes</h3>
{#if !manual && resource_type != ''}
{#if !manual && resourceType != ''}
<OauthScopes bind:scopes />
{/if}
{/if}
@@ -437,11 +444,11 @@
<Button
size="sm"
variant="border"
color={key === resource_type ? 'blue' : 'light'}
btnClasses={key === resource_type ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
manual = true
resource_type = key
resourceType = key
next()
dispatch('click')
}}
@@ -462,11 +469,11 @@
<Button
size="sm"
variant="border"
color={key === resource_type ? 'blue' : 'light'}
btnClasses={key === resource_type ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
manual = true
resource_type = key
resourceType = key
next()
dispatch('click')
}}
@@ -486,38 +493,44 @@
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder={resource_type}
namePlaceholder={resourceType}
kind="resource"
/>
{#if apiTokenApps[resource_type]}
{#if apiTokenApps[resourceType]}
<h2 class="mt-4 mb-2">Instructions</h2>
<div class="pl-10">
<ol class="list-decimal">
{#each apiTokenApps[resource_type].instructions as step}
{#each apiTokenApps[resourceType].instructions as step}
<li>
{@html step}
</li>
{/each}
</ol>
</div>
{#if apiTokenApps[resource_type].img}
{#if apiTokenApps[resourceType].img}
<div class="mt-4 w-full overflow-hidden">
<img class="m-auto max-h-60" alt="connect" src={apiTokenApps[resource_type].img} />
<img class="m-auto max-h-60" alt="connect" src={apiTokenApps[resourceType].img} />
</div>
{/if}
{:else if !emptyString(resourceTypeInfo?.description)}
<h2 class="mt-4 mb-2">Resource type description</h2>
<div>
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
{/if}
{#if resource_type == 'postgresql' || resource_type == 'mysql' || resource_type == 'mongodb'}
{#if resourceType == 'postgresql' || resourceType == 'mysql' || resourceType == 'mongodb'}
<WhitelistIp />
{/if}
<h2 class="mt-4">Value</h2>
<div class="mt-4">
{#key resource_type}
{#key resourceTypeInfo}
<ApiConnectForm
{linkedSecret}
{linkedSecretCandidates}
{resource_type}
{resourceType}
{resourceTypeInfo}
bind:args
bind:isValid
/>
@@ -529,15 +542,15 @@
{:else}
<Path
initialPath=""
namePlaceholder={resource_type}
namePlaceholder={resourceType}
bind:error={pathError}
bind:path
kind="resource"
/>
{#if apiTokenApps[resource_type] || !manual}
{#if apiTokenApps[resourceType] || !manual}
<ul class="mt-10">
<li>
1. A secret variable containing the {apiTokenApps[resource_type]?.linkedSecret ??
1. A secret variable containing the {apiTokenApps[resourceType]?.linkedSecret ??
'token'}
<span class="font-bold">{truncateRev(value, 5, '*****')}</span>
will be stored a
@@ -157,7 +157,7 @@
right: 'As JSON'
}}
/>
<TestConnection resource_type={resourceToEdit?.resource_type} {args} />
<TestConnection resourceType={resourceToEdit?.resource_type} {args} />
</div>
<div class="text-sm">
{#if loadingSchema}
@@ -7,7 +7,7 @@
import { workspaceStore } from '$lib/stores'
import { tryEvery } from '$lib/utils'
export let resource_type: string | undefined
export let resourceType: string | undefined
export let args: Record<string, any> | any = {}
const scripts: {
@@ -70,10 +70,10 @@
let loading = false
async function testConnection() {
if (!resource_type) return
if (!resourceType) return
loading = true
const resourceScript = scripts[resource_type]
const resourceScript = scripts[resourceType]
const job = await JobService.runScriptPreview({
workspace: $workspaceStore!,
@@ -124,7 +124,7 @@
}
</script>
{#if Object.keys(scripts).includes(resource_type || '')}
{#if Object.keys(scripts).includes(resourceType || '')}
<Button
spacingSize="sm"
size="xs"
+3 -15
View File
@@ -1,20 +1,8 @@
<script lang="ts">
import { urlize } from '$lib/utils'
export let text: string
const parseURL = (input) => {
if (input.startsWith('http://') || input.startsWith('https://')) {
return `<a href="${input}" target="_blank" rel="noopener noreferrer">${input}</a>`
} else {
return input
}
}
const parseInput = (input) => {
if (!input) return ''
const words = input.split(' ')
return words.map((word) => parseURL(word)).join(' ')
}
$: parsed = text ? text.split('\n').map(parseInput).join('\n') : ''
$: parsed = text ? urlize(text, 'html') : ''
</script>
{@html parsed}
+27
View File
@@ -764,3 +764,30 @@ export function computeShow(argName: string, expr: string | undefined, args: any
}
return true
}
function urlizeTokenInternal(token: string, formatter: "html" | "md"): string {
if (token.startsWith('http://') || token.startsWith('https://')) {
if (formatter == "html") {
return `<a href="${token}" target="_blank" rel="noopener noreferrer">${token}</a>`
} else {
return `[${token}](${token})`
}
} else {
return token
}
}
export function urlize(input: string, formatter: "html" | "md"): string {
if (!input) return ''
return input
.split('\n')
.map(line => {
return line
.split(' ')
.map((word) => urlizeTokenInternal(word, formatter))
.join(' ')
})
.join('\n')
}