Skip to content

Commit

Permalink
Add button to upload notes
Browse files Browse the repository at this point in the history
  • Loading branch information
maorleger committed Dec 19, 2020
1 parent 0942c1d commit d2e22e7
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 40 deletions.
2 changes: 1 addition & 1 deletion samples/frameworks/react/ts/arm-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"corsRules": [
{
"allowedOrigins": ["[concat('http://localhost:', parameters('appPort'))]"],
"allowedMethods": ["GET", "OPTIONS"],
"allowedMethods": ["GET", "OPTIONS", "PUT", "POST"],
"maxAgeInSeconds": 0,
"exposedHeaders": ["*"],
"allowedHeaders": ["*"]
Expand Down
4 changes: 0 additions & 4 deletions samples/frameworks/react/ts/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
# made available. See the documentation for adding custom environment variables
# at the following link: https://create-react-app.dev/docs/adding-custom-environment-variables/)

# fully qualified namespace for ServiceBus, typically: <namespace>.servicebus.windows.net
REACT_APP_SERVICE_BUS_NAMESPACE=""
# name of the service bus queue that has been set up
REACT_APP_SERVICE_BUS_QUEUE=""
# <fully qualified namespace for event hubs, typically: <namespace>.servicebus.windows.net>
REACT_APP_EVENT_HUBS_NAMESPACE=""
# The name of the Event Hubs hub
Expand Down
10 changes: 7 additions & 3 deletions samples/frameworks/react/ts/src/components/TodoItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const TodoItem: React.FC<{
todo: Todo;
onToggleComplete: (event: React.ChangeEvent<HTMLInputElement>) => void;
onNotesClick: (event: React.MouseEvent<HTMLElement>) => void;
}> = ({ todo, onToggleComplete, onNotesClick }) => {
onNoteUploadClick: (event: React.MouseEvent<HTMLElement>) => void;
}> = ({ todo, onToggleComplete, onNotesClick, onNoteUploadClick }) => {
return (
<li className="list-group-item d-flex justify-content-between align-items-center">
<span>
Expand All @@ -26,10 +27,13 @@ const TodoItem: React.FC<{
</span>
<span>
{todo.noteFileName && (
<button className="btn btn-primary" type="button" onClick={onNotesClick}>
Fetch the note!
<button className="btn btn-primary mr-1" type="button" onClick={onNotesClick}>
Fetch the note
</button>
)}
<button className="btn btn-primary" type="button" onClick={onNoteUploadClick}>
Upload a note
</button>
</span>
</li>
);
Expand Down
24 changes: 15 additions & 9 deletions samples/frameworks/react/ts/src/components/TodoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@ import React, { useState } from "react";
import { v4 as guid } from "uuid";
import TodoItem from "./TodoItem";
import { useTodos, Todo } from "../hooks/useTodos";
import { useBlobs } from "../hooks/useBlobs";

export default function TodoList(): JSX.Element {
// Initialize the hooks needed for integrating with our Azure services.
const [todos, addTodo, updateTodo] = useTodos();
const [todos, addTodo, updateTodo, getNote, uploadNote] = useTodos();
const [note, setNote] = useState<string | undefined>();
const getBlob = useBlobs();

const [newTodoLabel, setNewTodoLabel] = useState("");

// Handle data binding for the new todo label
// Handle data binding for the new todo label.
const onNewLabelChange = (el: React.ChangeEvent<HTMLInputElement>) => {
setNewTodoLabel(el.target.value);
};

// Handle the Enter key press which will add a new Todo
// Handle the Enter key press which will add a new Todo.
const onKeyPress = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
Expand All @@ -37,23 +35,30 @@ export default function TodoList(): JSX.Element {
}
};

// Handle completion of a Todo item
// Handle completion of a Todo item.
const onToggleComplete = (todo: Todo) => async () => {
const updatedTodo: Todo = { ...todo, done: !todo.done };
await updateTodo(updatedTodo);
};

// Handle fetching a Todo's note and displaying its text
// Handle fetching a Todo's note and displaying its text.
const onNotesClick = (todo: Todo) => async (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
let text: string | undefined = "";
if (todo.noteFileName) {
const blob = await getBlob(todo.noteFileName);
text = await blob?.text();
text = await getNote(todo);
}
setNote(text);
};

// Handle uploading the current timestamp as a note.
const onNoteUploadClick = (todo: Todo) => async (
e: React.MouseEvent<HTMLElement, MouseEvent>
) => {
e.preventDefault();
await uploadNote(todo, `Note created on ${new Date()}`);
};

return (
<React.Fragment>
<div className="row">
Expand All @@ -78,6 +83,7 @@ export default function TodoList(): JSX.Element {
todo={todo}
key={todo.id}
onToggleComplete={onToggleComplete(todo)}
onNoteUploadClick={onNoteUploadClick(todo)}
onNotesClick={onNotesClick(todo)}
/>
))}
Expand Down
25 changes: 20 additions & 5 deletions samples/frameworks/react/ts/src/hooks/useBlobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { useEffect, useRef } from "react";
import { BlobServiceClient, ContainerClient } from "@azure/storage-blob";
import { credential, getEnvironmentVariable } from "../utils";

type Hook = () => (blobName: string) => Promise<Blob | undefined>;
type GetBlob = (blobName: string) => Promise<Blob | undefined>;
type UploadBlob = (blobName: string, content: string) => Promise<void>;
type Hook = () => [GetBlob, UploadBlob];

/**
* The Azure Blob hook exposes a single method that allows you
* to fetch an Azure Blob given a name.
* The Azure Blob hook exposes a methods to interact
* With Azure Blob Storage.
*/
const useBlobs: Hook = () => {
// Keep a reference to a client for a Blob Container
Expand All @@ -28,7 +30,7 @@ const useBlobs: Hook = () => {

/**
* Fetch a Blob from Azure Blob Storage, returning its body if the blob exists.
* @param blobName The name of the blob within the container
* @param blobName The name of the blob within the container.
*/
const getBlob = async (blobName: string): Promise<Blob | undefined> => {
if (!instance.current) {
Expand All @@ -39,6 +41,19 @@ const useBlobs: Hook = () => {
return response.blobBody;
};

/**
* Upload a string to Azure blob Stroage, overwriting it if it already exists.
* @param blobName The name of the blob within the container.
* @param content
*/
const uploadBlob = async (blobName: string, content: string): Promise<void> => {
if (!instance.current) {
throw new Error("[useBLobs]: Instance never initialized.");
}
const blockBlobClient = instance.current.getBlockBlobClient(blobName);
await blockBlobClient.upload(content, content.length);
};

useEffect(() => {
if (!instance.current) {
const uri = getEnvironmentVariable("REACT_APP_BLOB_URI");
Expand All @@ -54,7 +69,7 @@ const useBlobs: Hook = () => {
}
}, []);

return getBlob;
return [getBlob, uploadBlob];
};

export { useBlobs };
62 changes: 44 additions & 18 deletions samples/frameworks/react/ts/src/hooks/useTodos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

import { EventData } from "@azure/event-hubs";
import { useState } from "react";
import { useBlobs } from "./useBlobs";
import { useEventHubs } from "./useEventHubs";
import { v4 as uuid } from "uuid";

export interface Todo {
done: boolean;
Expand All @@ -22,30 +22,26 @@ export interface Todo {
type Todos = Todo[];
type AddTodo = (todo: Todo) => Promise<void>;
type UpdateTodo = (todo: Todo) => Promise<void>;
type Hook = [Todos, AddTodo, UpdateTodo];
type GetNote = (todo: Todo) => Promise<string | undefined>;
type UploadNote = (todo: Todo, content: string) => Promise<void>;
type Hook = [Todos, AddTodo, UpdateTodo, GetNote, UploadNote];

/**
* The useTodos hook is the main entrypoint for most of the todo
* lifecycle management. It exposes the current todos state, a method
* to add a new todo, and a method to update an existing todo.
* to add a new todo, a method to update an existing todo, and methods
* to interact with notes.
*/
export const useTodos: () => Hook = () => {
// Set up a hardcoded list of Todos for this example.
// Start with an empty list of Todos for this example.
// In a production application you might fetch these from
// a database like Cosmos DB on render.
const [todos, setTodos] = useState<Todo[]>([
{
id: uuid(),
done: false,
label: "Unfinished with note",
noteFileName: "todo.txt"
},
{
id: uuid(),
done: true,
label: "Finished"
}
]);
// If there was any activity within the last EventHubs retention period
// it'll be reflected and read in and the state updated.
const [todos, setTodos] = useState<Todo[]>([]);

// Add functionality to integrate with Azure Storage Blobs.
const [getBlob, uploadBlob] = useBlobs();

// An example of using EventHubs to process events which will
// be published to every consumer. For simplicity we just override
Expand All @@ -60,18 +56,48 @@ export const useTodos: () => Hook = () => {
};
const publishToEventHubs = useEventHubs(onEventHubMessage);

/**
* Adds a new todo to the collection.
* @param todo The todo item to add.
*/
const addTodo = async (todo: Todo): Promise<void> => onChange([todo, ...todos]);

/**
* Updates an existing todo by replacing it with the argument.
* @param todo The updated todo data.
*/
const updateTodo = async (todo: Todo): Promise<void> => {
const newTodos = todos.map((t) => (t.id === todo.id ? todo : t));
onChange(newTodos);
};

/**
* Uploads a note to Azure Blob Storage and attaches it to an existing todo.
* @param todo The todo to associate this note with.
* @param content The note's contents.
*/
const uploadNote = async (todo: Todo, content: string): Promise<void> => {
todo.noteFileName = `${todo.id}.txt`;
await uploadBlob(todo.noteFileName, content);
updateTodo(todo);
};

/**
* Fetches a note from Azure Blob Storage and returns its contents.
* @param todo The todo item that owns this note.
*/
const getNote = async (todo: Todo): Promise<string | undefined> => {
if (todo.noteFileName) {
const blob = await getBlob(todo.noteFileName);
return await blob?.text();
}
};

// onChange will publish the new set of todos to all the clients.
const onChange = async (newTodos: Todo[]): Promise<void> => {
setTodos(newTodos);
publishToEventHubs({ body: newTodos });
};

return [todos, addTodo, updateTodo];
return [todos, addTodo, updateTodo, getNote, uploadNote];
};

0 comments on commit d2e22e7

Please sign in to comment.