Skip to content

Commit

Permalink
Manage external apps in editor (#1332)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleF83 authored Sep 8, 2020
1 parent 3c0cbde commit 480a959
Show file tree
Hide file tree
Showing 12 changed files with 701 additions and 9 deletions.
12 changes: 10 additions & 2 deletions services/authoring/src/routes/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const allowedPermissions = R.without(<any>PERMISSIONS.ADMIN, R.values(PERMISSION

const hasValidPermissions = R.all(<any>R.contains((<any>R).__, allowedPermissions));

const getPublicProps = (s: AppSecretKey) => ({ id: s.id, creationDate: s.creationDate });

export type AppCreationRequestModel = {
name: string;
permissions: Array<string>;
Expand All @@ -34,7 +36,13 @@ export type AppCreationResponseModel = {
appSecret: string;
};

export type App = AppManifest & { id: string };
export type App = {
id: string;
version: string;
name: string;
secretKeys: Pick<AppSecretKey, 'id' | 'creationDate'>[];
permissions?: string[];
};

export type AppSecretKeyCreationResponseModel = {
appId: string;
Expand All @@ -56,7 +64,7 @@ export class AppsController {
@GET
async getApps(): Promise<App[]> {
const apps = await this.appsRepository.getApps();
return Object.entries(apps).map(([id, app]) => ({ id, ...app }));
return Object.entries(apps).map(([id, app]) => ({ id, ...app, secretKeys: app.secretKeys.map(getPublicProps) }));
}

@Authorize({ permission: PERMISSIONS.ADMIN })
Expand Down
4 changes: 2 additions & 2 deletions services/editor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tweek-editor",
"version": "1.0.0-rc16",
"version": "1.0.0-rc17",
"main": "dist/index.js",
"repository": "Soluto/tweek",
"author": "Soluto",
Expand Down Expand Up @@ -74,7 +74,7 @@
"rxjs": "^6.4.0",
"rxjs-compat": "^6.4.0",
"title-case": "3.0.2",
"tweek-client": "^2.0.1",
"tweek-client": "^2.2.0",
"tweek-local-cache": "^0.6.2",
"velocity-react": "^1.3.3"
},
Expand Down
4 changes: 4 additions & 0 deletions services/editor/src/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { signOut } from './services/auth-service';
import PoliciesPage from './pages/settings/components/PoliciesPage/PoliciesPage';
import HooksPage from './pages/settings/components/HooksPage/HooksPage';
import EditHookPage from './pages/settings/components/HooksPage/EditHookPage';
import ExternalAppsPage from './pages/settings/components/ExternalAppsPage/ExternalAppsPage';
import EditExternalAppsPage from './pages/settings/components/ExternalAppsPage/EditExternalAppPage';

const SelectKeyMessage = () => <div className={'select-key-message'}>Select key...</div>;

Expand All @@ -45,6 +47,8 @@ const renderSettingsRoutes = ({ match }) => (
<PrivateRoute path={`${match.path}/policies`} component={PoliciesPage} />
<PrivateRoute exact path={`${match.path}/hooks`} component={HooksPage} />
<PrivateRoute path={`${match.path}/hooks/edit`} component={EditHookPage} />
<PrivateRoute exact path={`${match.path}/externalApps`} component={ExternalAppsPage} />
<PrivateRoute path={`${match.path}/externalApps/edit`} component={EditExternalAppsPage} />
</SettingsPage>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { buttons } from '../../../../store/ducks';
import './CreateExternalAppSecret.css';

const Component = ({ appId, appSecret }) => (
<div>
<div className="note">
Write out the application id and the secret before closing this window.
</div>
<div className="field-wrapper">
<label className="field-label">App Id:</label>
<div>{appId}</div>
</div>
<div className="field-wrapper">
<label className="field-label">Secret:</label>
<div className="long-text">{appSecret}</div>
</div>
</div>
);

export default (appId, appSecret) => ({
title: 'Important!',
component: () => <Component appId={appId} appSecret={appSecret} />,
buttons: [buttons.OK],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@import "../../../../styles/core/core.less";

@small-font: 16px;

.label-text() {
all: unset;
font-size: @small-font;
margin: 0px 5px 0 0;
color: gray;
display: flex;
white-space: nowrap;
}

.field-wrapper {
display: flex;
flex-grow: 1;
flex-direction: row;
margin-top: 5px;

.field-label {
.label-text();
align-items: center;
padding-bottom: 1px;
}

.long-text {
overflow-wrap: anywhere;
}
}

.note {
overflow-wrap: break-word;
font-style: italic;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { useState, useMemo, useContext, useCallback } from 'react';
import { WithContext as ReactTags } from 'react-tag-input';
import { tweekManagementClient, useErrorNotifier } from '../../../../utils';
import { SaveButton } from '../../../../components/common';
import { ReduxContext } from '../../../../store';
import { showSuccess, showCustomAlert } from '../../../../store/ducks';
import createAlert from './CreateExternalAppSecret';
import './EditExternalAppPage.css';

export default ({ location, history }) => {
const { dispatch } = useContext(ReduxContext);
const initialExternalAppData = (location.state && location.state.externalApp) || {};
const id = initialExternalAppData.id;
const isEditPage = Boolean(id);

const [name, setName] = useState(initialExternalAppData.name || '');
const [permissions, setPermissions] = useState(initialExternalAppData.permissions || []);
const [saveError, setSaveError] = useState(null);
const [isSaving, setIsSaving] = useState(false);

const isValid = useMemo(() => validateInput({ name, permissions }), [name, permissions]);
const hasChanges = useMemo(() => checkForChanges({ initialExternalAppData, name, permissions }), [
name,
permissions,
]);

useErrorNotifier(saveError, 'Failed to save external app');

return (
<div className="edit-external-app-page">
<h3>{isEditPage ? 'Edit External App' : 'Add New External App'}</h3>

<TextField label="Name:" value={name} setter={setName} />
<PermissionsPicker permissions={permissions} setPermissions={setPermissions} />

<SaveButton
{...{ isSaving, isValid, hasChanges }}
onClick={useSaveExternalAppCallback({
id,
name,
permissions,
setIsSaving,
history,
setSaveError,
dispatch,
})}
/>
</div>
);
};

const TextField = ({ label, value, setter, placeholder }) => (
<div className="field-input-wrapper">
<label className="field-label">{label}</label>
<input type="text" onChange={(e) => setter(e.target.value)} {...{ value, placeholder }} />
</div>
);

const PermissionsPicker = ({ permissions, setPermissions }) => (
<div className="permissions-picker">
<label className="field-label">Permissions:</label>
<ReactTags
tags={permissions.map((permission) => ({ id: permission, text: permission }))}
handleDelete={(index) => setPermissions(permissions.filter((_, i) => i !== index))}
handleAddition={(permission) => setPermissions([...permissions, permission.text])}
placeholder="New permission"
autofocus={false}
allowDeleteFromEmptyInput
allowDragDrop={false}
minQueryLength={1}
classNames={{
tags: 'tags-container',
tagInput: 'tag-input',
tag: 'tag',
remove: 'tag-delete-button',
suggestions: 'tags-suggestion',
}}
/>
</div>
);

const useSaveExternalAppCallback = ({
id,
name,
permissions,
setIsSaving,
history,
setSaveError,
dispatch,
}) =>
useCallback(async () => {
try {
setIsSaving(true);

if (id) await tweekManagementClient.updateExternalApp(id, { name, permissions });
else {
const { appId, appSecret } = await tweekManagementClient.createExternalApp({
name,
permissions,
});
await dispatch(showCustomAlert(createAlert(appId, appSecret)));
}

setIsSaving(false);
dispatch(showSuccess({ title: 'External App Saved' }));
history.goBack();
} catch (err) {
setIsSaving(false);
setSaveError(err);
}
}, [id, name, permissions]);

const validateInput = ({ name, permissions }) =>
Boolean(name && permissions && Array.isArray(permissions));

const checkForChanges = ({ initialExternalAppData, name, permissions }) =>
name !== initialExternalAppData.name || permissions !== initialExternalAppData.permissions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
@import "../../../../styles/core/core.less";
@import "../../../../styles/core/comboBox.less";
@import (reference) "../../../../styles/core/tags.less";

@text-padding: 10px;
@panel-padding: 35px;
@small-font: 16px;
@smaller-font: 15px;

@button-width: 100px;
@button-height: 25px;

.edit-external-app-page {
.panel;
padding: @panel-padding;
border-width: 0;
box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.2);
margin-top: 18px * 2 + 36px;
margin-bottom: auto;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
flex-grow: 0.5;

h3 {
font-size: 22px;
padding-left: @text-padding;
margin: 0px 0px 58px 0px;
font-weight: 400;
}

.field-input-wrapper {
display: flex;
flex-direction: row;
flex-grow: 1;
align-content: space-between;
padding-left: @text-padding;
padding-right: @button-width + @panel-padding;
margin-bottom: 41px;

> input {
.metro-textbox;
font-size: @small-font;
width: 550px;
}
}

.field-label {
.label-text();
align-items: center;
padding-bottom: 1px;
}

.permissions-picker {
.field-input-wrapper;
.metro-tags;
}
}

.label-text() {
all: unset;
font-size: @small-font;
margin: 0px 5px 0 0;
color: gray;
display: flex;
white-space: nowrap;
}
Loading

0 comments on commit 480a959

Please sign in to comment.