Browse Source

Sentry Functions: Environment Variable Frontend (#37580)

* feat(integrations): frontend changes to render env variables

* less hacky way of getting environment variables
Vignesh P 2 years ago
parent
commit
ceb4d0acd6

+ 4 - 0
static/app/types/integrations.tsx

@@ -533,6 +533,10 @@ export type SentryFunction = {
   code: string;
   name: string;
   slug: string;
+  env_variables?: Array<{
+    name: string;
+    value: string;
+  }>;
   events?: string[];
   overview?: string;
 };

+ 43 - 2
static/app/views/settings/organizationDeveloperSettings/sentryFunctionDetails.tsx

@@ -19,11 +19,13 @@ import {t, tct} from 'sentry/locale';
 import {SentryFunction} from 'sentry/types';
 import useApi from 'sentry/utils/useApi';
 
+import SentryFunctionEnvironmentVariables from './sentryFunctionsEnvironmentVariables';
 import SentryFunctionSubscriptions from './sentryFunctionSubscriptions';
 
 class SentryFunctionFormModel extends FormModel {
   getTransformedData() {
     const data = super.getTransformedData() as Record<string, any>;
+
     const events: string[] = [];
     if (data.onIssue) {
       events.push('issue');
@@ -34,7 +36,26 @@ class SentryFunctionFormModel extends FormModel {
     if (data.onComment) {
       events.push('comment');
     }
+    delete data.onIssue;
+    delete data.onError;
+    delete data.onComment;
     data.events = events;
+
+    const envVariables: EnvVariable[] = [];
+    let i = 0;
+    while (data[`env-variable-name-${i}`]) {
+      if (data[`env-variable-value-${i}`]) {
+        envVariables.push({
+          name: data[`env-variable-name-${i}`],
+          value: data[`env-variable-value-${i}`],
+        });
+      }
+      delete data[`env-variable-name-${i}`];
+      delete data[`env-variable-value-${i}`];
+      i++;
+    }
+    data.envVariables = envVariables;
+
     const {...output} = data;
     return output;
   }
@@ -44,6 +65,10 @@ type Props = {
   sentryFunction?: SentryFunction;
 } & WrapperProps;
 
+type EnvVariable = {
+  name: string;
+  value: string;
+};
 const formFields: Field[] = [
   {
     name: 'name',
@@ -96,6 +121,10 @@ function SentryFunctionDetails(props: Props) {
     form.current.setValue('onComment', events.includes('comment'));
   }, [events]);
 
+  const [envVariables, setEnvVariables] = useState(
+    sentryFunction?.env_variables || [{name: '', value: ''}]
+  );
+
   const handleSubmitError = err => {
     let errorMessage = t('Unknown Error');
     if (err.status >= 400 && err.status < 500) {
@@ -153,15 +182,27 @@ function SentryFunctionDetails(props: Props) {
           initialData={{
             code: defaultCode,
             events,
+            envVariables,
             ...props.sentryFunction,
           }}
           onSubmitError={handleSubmitError}
           onSubmitSuccess={handleSubmitSuccess}
         >
           <JsonForm forms={[{title: t('Sentry Function Details'), fields: formFields}]} />
-          <SentryFunctionSubscriptions events={events} setEvents={setEvents} />
           <Panel>
-            <PanelHeader>Write your Code Below</PanelHeader>
+            <PanelHeader>{t('Webhooks')}</PanelHeader>
+            <PanelBody>
+              <SentryFunctionSubscriptions events={events} setEvents={setEvents} />
+            </PanelBody>
+          </Panel>
+          <Panel>
+            <SentryFunctionEnvironmentVariables
+              envVariables={envVariables}
+              setEnvVariables={setEnvVariables}
+            />
+          </Panel>
+          <Panel>
+            <PanelHeader>{t('Write your Code Below')}</PanelHeader>
             <PanelBody>
               <Editor
                 height="40vh"

+ 13 - 21
static/app/views/settings/organizationDeveloperSettings/sentryFunctionSubscriptions.tsx

@@ -1,8 +1,5 @@
 import styled from '@emotion/styled';
 
-import {Panel, PanelBody, PanelHeader} from 'sentry/components/panels';
-import {t} from 'sentry/locale';
-
 import {EVENT_CHOICES} from './constants';
 import SubscriptionBox from './subscriptionBox';
 
@@ -25,24 +22,19 @@ function SentryFunctionSubscriptions(props: Props) {
   }
 
   return (
-    <Panel>
-      <PanelHeader>{t('Webhooks')}</PanelHeader>
-      <PanelBody>
-        <SentryFunctionsSubscriptionGrid>
-          {EVENT_CHOICES.map(resource => (
-            <SubscriptionBox
-              key={resource}
-              disabledFromPermissions={false}
-              webhookDisabled={false}
-              checked={props.events.includes(resource)}
-              resource={resource}
-              onChange={onChange}
-              isNew={resource === 'comment'}
-            />
-          ))}
-        </SentryFunctionsSubscriptionGrid>
-      </PanelBody>
-    </Panel>
+    <SentryFunctionsSubscriptionGrid>
+      {EVENT_CHOICES.map(resource => (
+        <SubscriptionBox
+          key={resource}
+          disabledFromPermissions={false}
+          webhookDisabled={false}
+          checked={props.events.includes(resource)}
+          resource={resource}
+          onChange={onChange}
+          isNew={resource === 'comment'}
+        />
+      ))}
+    </SentryFunctionsSubscriptionGrid>
   );
 }
 

+ 136 - 0
static/app/views/settings/organizationDeveloperSettings/sentryFunctionsEnvironmentVariables.tsx

@@ -0,0 +1,136 @@
+import styled from '@emotion/styled';
+
+import Button from 'sentry/components/button';
+import {InputField} from 'sentry/components/forms';
+import {PanelBody, PanelHeader} from 'sentry/components/panels';
+import {IconAdd, IconDelete} from 'sentry/icons';
+import {t, tct} from 'sentry/locale';
+import space from 'sentry/styles/space';
+
+type Props = {
+  envVariables: {
+    name: string;
+    value: string;
+  }[];
+  setEnvVariables: (envVariables) => void;
+};
+
+function SentryFunctionEnvironmentVariables(props: Props) {
+  const {envVariables, setEnvVariables} = props;
+
+  const addEnvVar = () => {
+    setEnvVariables([...envVariables, {name: '', value: ''}]);
+  };
+
+  const handleNameChange = (value: string, pos: number) => {
+    const newEnvVariables = [...envVariables];
+    while (newEnvVariables.length <= pos) {
+      newEnvVariables.push({name: '', value: ''});
+    }
+    newEnvVariables[pos] = {...newEnvVariables[pos], name: value};
+    setEnvVariables(newEnvVariables);
+  };
+
+  const handleValueChange = (value: string, pos: number) => {
+    const newEnvVariables = [...envVariables];
+    while (newEnvVariables.length <= pos) {
+      newEnvVariables.push({name: '', value: ''});
+    }
+    newEnvVariables[pos] = {...newEnvVariables[pos], value};
+    setEnvVariables(newEnvVariables);
+  };
+
+  const removeEnvVar = (pos: number) => {
+    const newEnvVariables = [...envVariables];
+    newEnvVariables.splice(pos, 1);
+    setEnvVariables(newEnvVariables);
+  };
+
+  return (
+    <div>
+      <PanelHeader>
+        {t('Environment Variables')}
+        <StyledAddButton
+          size="sm"
+          type="button"
+          icon={<IconAdd isCircled />}
+          aria-label={t('Add Environment Variable')}
+          onClick={addEnvVar}
+        />
+      </PanelHeader>
+      <StyledPanelBody>
+        <EnvironmentVariableWrapper>
+          <EnvHeader>{t('Name')}</EnvHeader>
+          <EnvHeaderRight>{t('Value')}</EnvHeaderRight>
+        </EnvironmentVariableWrapper>
+        {envVariables.map((envVariable, i) => {
+          return (
+            <EnvironmentVariableWrapper key={i}>
+              <InputField
+                name={`env-variable-name-${i}`}
+                type="text"
+                required={false}
+                inline={false}
+                defaultValue={envVariable.name}
+                value={envVariable.name}
+                stacked
+                onChange={e => handleNameChange(e, i)}
+              />
+              <InputField
+                name={`env-variable-value-${i}`}
+                type="text"
+                required={false}
+                inline={false}
+                defaultValue={envVariable.value}
+                value={envVariable.value}
+                stacked
+                onChange={e => handleValueChange(e, i)}
+              />
+              <ButtonHolder>
+                <StyledAddButton
+                  size="sm"
+                  icon={<IconDelete />}
+                  type="button"
+                  aria-label={tct('Remove Environment Variable [i]', {i})}
+                  onClick={() => removeEnvVar(i)}
+                />
+              </ButtonHolder>
+            </EnvironmentVariableWrapper>
+          );
+        })}
+      </StyledPanelBody>
+    </div>
+  );
+}
+
+export default SentryFunctionEnvironmentVariables;
+
+const EnvironmentVariableWrapper = styled('div')`
+  display: grid;
+  grid-template-columns: 1fr 1.5fr min-content;
+`;
+
+const StyledAddButton = styled(Button)`
+  float: right;
+`;
+
+const EnvHeader = styled('div')`
+  text-align: left;
+  margin-top: ${space(2)};
+  margin-bottom: ${space(1)};
+  color: ${p => p.theme.gray400};
+`;
+
+const EnvHeaderRight = styled(EnvHeader)`
+  margin-left: -${space(2)};
+`;
+
+const ButtonHolder = styled('div')`
+  align-items: center;
+  display: flex;
+  margin-bottom: ${space(2)};
+`;
+
+const StyledPanelBody = styled(PanelBody)`
+  padding: ${space(2)};
+`;