re:Invent2020で発表されたAmplify Admin UI
を試してみます。
確認ポイントとしては、以下で、特に1.
については可能性を探ってみたいポイントです。
- 今後、管理画面の構築が不要になる!?
- HeadlessCMSとの違い
Amplify Admin UI
とは
以下の2つの機能を提供してくれるツール(管理画面)です。
- (アプリ管理者 ※非開発者向け機能)アプリケーションのユーザーおよびコンテンツ管理など
- (システム管理者 ※開発者向け機能)データベースのモデリング、認証・認可機能の追加、管理画面のユーザー管理(システム管理者・アプリ管理者)など
作成したサンプルアプリ
Demo Page
https://thirosue.github.io/amplify-admin-ui-sample/
ソースコードの全量はGitHubにあります。
https://github.com/Thirosue/amplify-admin-ui-sample
前提
-
node
がインストールされていること -
yarn
がインストールされていること
大まかな流れ
- 初期アプリセットアップ
- Amplifyセットアップ
-
Admin UI
有効化 -
Admin UI
ユーザ払い出し - データモデル定義
- データ登録
- アプリ修正
初期アプリセットアップ
$ git clone git@github.com:Thirosue/amplify-admin-ui-sample.git
$ cd amplify-admin-ui-sample
$ yarn
$ yarn start
ここまで実行のあと、以下URLにアクセスすると、簡単な「ブログ一覧」、「ブログページ」が表示されるはずです。
Compiled successfully!
You can now view amplify-admin-ui-sample in the browser.
Local: http://localhost:3000/amplify-admin-ui-sample
On Your Network: http://192.168.11.11:3000/amplify-admin-ui-sample
Note that the development build is not optimized.
To create a production build, use yarn build.
Amplify セットアップ
Amplifyはこちらを参考にセットアップします。
AWS Amplify CLIの使い方〜インストールから初期セットアップまで〜
Amplify Admin UI有効化
AWSマネジメントコンソールで以下に移動します。
「AWS Amplify」 - 「作成したアプリ」 - 「Set up Admin UI」 - 「on」 - 「Invite users」でユーザ招待 - メールを確認してログイン(※初期PWセットアップとかとか)
最初は、データモデル定義する(For 開発者)ため、「admin」ユーザを招待します。
- 「AWS Amplify」 遷移のあと
- 「作成したアプリ」 クリックのあと
- 「Set up Admin UI」 クリックのあと
- 「on」のあと
- 「Invite user(s)」でユーザ招待
- 招待メール
- ログイン後トップ画面
コンテンツ(Blog)のモデリング
Admin UI
で以下を行う。
- 「Data」へ移動
- 「Add」 - 「Add model」
- データモデル定義
ブログに必要な最低限のデータ定義をします。
- 作成中...(裏でCfnが走っている)
コンテンツ管理者ユーザ作成
AWSマネジメントコンソールに戻って、次は非開発者向けの管理者用のユーザ(「Manage only」ユーザ)を作成します。
ここでブログを投稿していきたいところですが、2020/12/12現在では、以下のようになってしまいます。
コンテンツ投稿
仕方ないので、Adminユーザからブログを投稿していきます。
- 1投目
- 2投目
- 3投目
- 最終的な一覧はこんな感じ
Dynamoに登録されているはずだから見にいくと...
2020/12/12現在ではDynamoDBは空っぽ...
公式ブログ では、
この画面から、レストランのオーナーはメニューに項目を追加、削除、編集をいつでも行うことが出来ます。また、ここで変更した項目は保存後すぐにウェブサイトに反映されます。
こう書いてあるので、Betaが外れる(Bugfixされる)と、DynamoDB
に登録されるはずです。
- DynamoDB
- Cfn(Dynamoの物理名)も念のため、確認
- この状態で
amplify pull
してみると、API(AppSync)が作成されている (=amplify add api
でGraphQLのAPIを作成した状態)
% amplify pull
Pre-pull status:
Current Environment: dev
| Category | Resource name | Operation | Provider plugin |
| -------- | -------------------- | --------- | ----------------- |
| Api | amplifyadminuisample | No Change | awscloudformation |
✔ Successfully pulled backend environment dev from the cloud.
Post-pull status:
Current Environment: dev
| Category | Resource name | Operation | Provider plugin |
| -------- | -------------------- | --------- | ----------------- |
| Api | amplifyadminuisample | No Change | awscloudformation |
-
Admin UI
で作成すると、スタックが作成済みの状態からpullすることになるため、クライアントのコードの自動生成もスキップされてしまう...(amplify cli
の以下フロー)
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
結論、Beta版の現状(2020/12/12時点)では、amplify cli
で作成した方が無難...(当たり前か...)
まとめ
1. 管理画面の構築が不要になる!?
- 非開発者向けの管理画面は、
Admin UI
自体をカスタマイズ可能(Lambdaのボイラーテンプレート出力みたいに)になれば、かなり嬉しいかも - 開発者向け機能は、当分
cli
で (アップデートまち)
2. HeadlessCMSとの違い
- ContentFulなどとほぼ同様の使用感
- コンテンツ以外の管理(ユーザ管理など)もできるので、今後アップデートが進めば一元管理できるかも
参考リンク
- Amplify関連
以下、蛇足 (仕方ないのでamplify cli
で、Amplifyを利用したブログを完成させる)
一旦、Admin UI
で作成したAppSyncは削除
AppSync 追加
こんな感じで、Admin UI
で作成したものと同様のModelを生成し、amplify push
する。
% amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: amplifyadminuisample
? Choose the default authorization type for the API API key
? Enter a description for the API key: 1df1c9bb-1bd8-1fde-b2d6-cea1ee234285
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
- Model定義
type Blog @model {
id: ID!
title: String!
description: String!
body: String!
imagePath: String!
}
コンテンツ追加
管理コンソールから手で登録
→bodyはMD形式です
ソースコード修正
- GraphQL クエリ
export const listBlogs = /* GraphQL */ `
query ListBlogs(
$filter: ModelBlogFilterInput
$limit: Int
$nextToken: String
) {
listBlogs(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
title
description
body
imagePath
}
nextToken
}
}
`;
- Amplify Import
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Amplify from 'aws-amplify'; // <---- 追加
import awsconfig from './aws-exports'; // <---- 追加
Amplify.configure(awsconfig); // <---- 追加
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
- サービスクラス作成
import { API, graphqlOperation } from 'aws-amplify';
import { listBlogs } from '../graphql/queries';
async function list() {
const results = await API.graphql(graphqlOperation(listBlogs));
return results.data.listBlogs.items
}
export default list;
- カスタムフック作成
import { useState, useEffect } from 'react';
import getBlogs from '../service/blog';
// eslint-disable-next-line
export default () => {
const [blogs, setBlogs] = useState([]);
// このカスタムフックを利用しているコンポーネントがマウントされたら ブログ一覧 を取得する。
useEffect(() => {
const fetchAll = async () => {
const results = await getBlogs();
setBlogs(results)
}
fetchAll()
}, []);
return { blogs };
};
- ページコンポーネント修正
リストレンダリングするように修正
function App() {
const classes = useStyles();
const [detail, setDetail] = React.useState(false);
const { blogs } = useBlog(); // <------ カスタムフックを利用する
const [id, setId] = React.useState(1); // <------ 選択したブログID用
React.useEffect(() => {
console.log(blogs)
}, [blogs]);
const handleClick = async id => {
console.log('select blog id : %s', id);
setId(id - 1) // ブログIDをセットする
setDetail(true)
}
return (
<DialogContent>
{!detail && (
<>
<ThemeProvider theme={theme}>
<Typography variant="h4">Sample Blog</Typography>
</ThemeProvider>
<Box mb="1.5rem" />
<Grid container spacing={3}>
{/* リストレンダリング */}
{0 < blogs.length && blogs.sort((a, b) => a.id - b.id).map(blog =>
<Grid key={blog.id} item xs={12} sm={4} md={3}>
{/* ブログ選択時はブログIDを渡す */}
<Card onClick={() => handleClick(blog.id)}>
<CardMedia
className={classes.media}
image={"/amplify-admin-ui-sample/" + blog.imagePath}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{blog.title}
</Typography>
<Typography variant="body2" color="textSecondary" component="div">
{blog.description}
</Typography>
</CardContent>
</Card>
</Grid>
)}
</Grid>
</>
)}
{!!detail && (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12} sm={12} md={12}>
{/* 選択したブログを表示する */}
<Card>
<CardMedia
className={classes.detail}
image={"/amplify-admin-ui-sample/" + blogs[id].imagePath}
/>
<CardContent>
<Typography className={classes.title} gutterBottom variant="h4" component="h2">
{blogs[id].title}
</Typography>
<Typography className={classes.content} variant="subtitle1" color="textSecondary" component="div">
<ReactMarkdown>{blogs[id].body}</ReactMarkdown>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
<Box mb="1rem" />
<Link
component="button"
variant="subtitle1"
onClick={() => setDetail(false)}
>
Back
</Link>
</div>
)}
</DialogContent>
);
}
- 修正差分
https://github.com/Thirosue/amplify-admin-ui-sample/commit/e4abc73bb77347000bc0e7627d295b332a2308f9
※ amplifyフォルダはコミット対象外にしています。
- 対象ブランチ