
Ben je gedurende de gehele periode bezig geweest met de ontwikkeling van producten. Toon aan dat je hierin Agile hebt gewerkt. Hoe is de keuze voor het gebruikte ontwikkelplatform en de gekozen taal tot stand? Hoe heb je jullie prototype en onderdelen getest? Toon op basis van testresultaten de doorontwikkeling van jullie oplossing aan.
Inleiding
In dit hoofdstuk wordt toegelicht hoe het ontwikkelen van de Immersive Storyboard Prototype is verlopen. Mijn persoonlijke inbreng, perspectief en reflectie.
We hebben onze prototype ontwikkeld met behulp van GitHub. Tijdens het ontwikkelen heb ik meegeholpen met het ontwikkelen van nieuwe features en met waarborgen van de kwaliteit van de code. Hier voor heb ik samen met Stefan De gier pull request reviews gedaan en was ik verantwoordelijk voor het mergen van feature branches.
Development sprints
Wij hebben ongeveer twee weken gehad om te ontwikkelen. Voordat wij als team aan de slag konden gaan met het ontwikkelen. Is het belangrijk om te plannen en het product te defineren door middel van essentiële features.
Nu hebben wij een concept bedacht en moeten wij de belangrijkste features in kaart brengen. Dit heb ik gedaan door middel van user-stories. Deze userstories zijn features die ontwikkeld moeten worden. Aangezien wij als team gekozen hebben om te werken in een scrum omgeving, moeten deze taken ook in de backlog verwerkt worden.
Backlog
Ik heb de userstories samen met de acceptatie criteria geschreven en in de backlog geplaatst. Het was hierbij belangrijk dat in de ticket duidelijk de acceptatie criteria te vinden is. Om de voortgang per feature (user story) inzichtelijk te maken, heb ik alle userstories verdeelt onder sub-stories. Dit is bewust als een child issue in Jira geplaatst. Op deze manier kan je de voortgang in een oogopslag zien.

Onze back-log bestaat uit user-stories die wij hebben gecategoriseerd door middel van epics.
Op deze manier hebben wij onze prototype verdeeld in aantal onderdelen, zo konden wij de voortgang van het prototype inzichtelijk houden. Dit heb ik eerder gezien op stage en om die redenen voorgesteld aan de scrum leader om verschillende epics te maken.

Reflectie
Deze voorstellen en inzichten hebben mij erg geholpen met het overzicht houden van de voortgang en vooral tijdens het mergen van de branches. De Jira integratie was heel fijn om branches aan te maken, alleen pakte deze de volledige userstory titel en hadden branches hele lange namen die bijna niet over te typen waren. Dit zou ik in de toekomst wel gebruiken, maar kijken of dit beter kan.
Pull requests
Wij hebben gebruik gemaakt Github om samen te werken aan de code. Onderling is er afgesproken om te werken in branch van je ticket nummer. Na het ontwikkelen van de nieuwe functionaliteit, maakt de developer een pull request aan om deze ticket te mergen naar de epic branch. Zodra alle features klaar zijn en ge-merged zijn in de epic branch. Word deze op de staging omgeving gezet om te testen.
De verschillende features moeten samengevoegd worden en in zijn geheel getest worden. Als de code nog niet aan de acceptatie criteria voldoet en of er nog feedback is over de kwaliteit van de code. Moet dat worden verwerkt worden voor dat dit word meegenomen naar de staging omgeving.
Stefan en ik zijn verantwoordelijk voor het samenvoegen van de branches naar de staging omgeving. Zodra iemand een pull request heeft gestuurd naar mij. Heb ik als eerst gekeken naar de userstorie om te kijken of de code voldoet aan de acceptatie criteria. Door de koppeling met Netlify wordt er automatisch een build gemaakt van de pull request en een preview link gedeeld. Door deze te openen en te testen weet ik of de functionaliteit goed werkt. Als er feedback is of er nog iets niet klopt, heb ik comments geplaatst en de review terug gestuurd.
Uiteindelijk hebben wij alle userstories uit de backlog ontwikkeld en samen kunnen voegen tot de eerste release versie van onze prototype. In het overzicht is te zien hoeveel pull request er zijn geweest.
Deze manier van samenwerken was heel fijn en heeft ervoor gezorgd dat wij afzonderlijk van elkaar aan de slag konden gaan met de stories. Het was niet voor iedereen even makkelijk om op deze manier te werken, dus hebben wij vaak hulp aan elkaar geboden om dit voor elkaar te krijgen. Door dat niet iedereen basis ervaring had met werken met Git. Heeft dit gezorgd voor verkeerd gebruik van Git en hebben wij vaker merge conflicten gehad door dat er niet altijd goed ge-pulled werd of vanaf de verkeerde branch een nieuwe word gemaakt. Gelukkig hebben we dit kunnen oplossen en konden we gauw weer aan de slag.
Nu hebben wij de werking van Git en onze werkwijze beschreven in de samenwerkingsovereenkomst. Dit werd niet altijd geraadpleegd. Het is wellicht een goed idee om dit apart toe te lichten in de README.md.
Pull request ervaringen
Het gebruik deploy previews van Netlify heeft mij erg geholpen met beoordelen van pull requests, omdat ik direct live kan testen of de code goed werkt. In de toekomst zal ik vaker gebruik maken van deze features. Ik heb geleerd dat deze workflow niet altijd voor iedereen even goed werkt en dat het team goed op één lijn moet zitten om efficient gebruik te maken van deze tools. Dit zal ik in de toekomst goed overwegen bij het gebruik van nieuwe tools, omdat het team zich er ook in moet verdiepen om op een juiste manier gebruik te maken van deze diensten.
Deployment
Om iteratief te werken hebben wij van tevoren afgesproken om te werken met feature branches. De werking hiervan staat beschreven in onze samenwerkingsovereenkomst. Wij hebben gebruik gemaakt van Netlify om onze codebase automatisch online te zetten. Netlify heeft een fijne integratie met GitHub, door onze repository te koppelen met de service van Netlify heb ik een online prototype kunnen hosten. Dit prototype is een reflectie van onze "Main" branch.
Een feature die mij aansprak om gebruik van te maken is "deploy previews". Bij het aanmaken van een nieuwe pull request, bouwt netlify een nieuwe versie en word deze gehost op een unieke link. Deze link word automatisch als een pipeline comment geplaatst in de pull request. Zo kon je zien hoe de main branch er uit zou zien na het mergen en helpt dit ons om features beter te reviewen.

Persoonlijke uitdaging
In het prototype is een tijdlijn waarbij storyboard panelen met een afbeelding in volgorde staat. Ik de verantwoordelijkheid gekregen om uit te zoeken hoe wij panels kunnen herordenen door middel van drag-and-drop gestures.
Dit was een nieuwe uitdaging voor mij aangezien ik dit niet eerder had gedaan. Ik heb online gekeken naar verschillende libraries en frameworks. Door de modulariteit en voorbeelden die er beschikbaar waren heb ik gekozen voor de library DND-kit. Dit is een library voor react, die het mogelijk maakt om componenten 'draggable' en 'droppable' te maken.
Concept en learning curve
Het was in het begin vrij moeilijk om hier mee te werken omdat deze library niet inhaakt op te Drag and Drop API. Deze library heeft abstractie laag ontwikkeld om drag and drop functionaliteiten te bieden aan ontwikkelaars zonder de limieten van de Drag and Drop API. Om aan de slag te gaan met deze library moet ik mij verdiepen in de concepten die beschreven zijn in de documentatie. Dit is het uiteindelijke resultaat geworden.
Timeline
Code
import {FC, useState} from "react";
import {
closestCenter,
DndContext,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from "@dnd-kit/core";
import {Panel, TimelinePanel} from "@/components/timeline/panel";
import {LockOpenIcon, LockClosedIcon, PlusIcon} from '@heroicons/react/24/outline'
import {
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
arrayMove,
} from "@dnd-kit/sortable";
import VideoEditingTimeline from 'video-editing-timeline-react';
import {useGlobalContext} from "@/providers/GlobalContextProvider";
interface StoryBoardTimelineProps {
panels: Array<Panel>
current: number
}
export const StoryBoardTimeline: FC<StoryBoardTimelineProps> = ({panels, current}) => {
const {setOpenSceneBuilder} = useGlobalContext()
const timeLineConfig = {
canvasWidth: (200 * panels.length) + 100,
canvasHeight: 40,
minimumScale: 10,
minimumScaleTime: 18,
}
const [isLocked, setLocked] = useState(false)
const [items, setItems] = useState(panels.map(p => p.id))
const [activeId, setActiveId] = useState(null);
// Sensors are an abstraction to detect different input methods in order to initiate drag operations, respond to movement and end or cancel the operation.
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
/**
* Set panel active
* @param event
*/
function handleDragStart(event: any) {
const {active} = event;
setActiveId(active.id);
}
/**
* Updates panel list index
* @param event
*/
function handleDragEnd(event: any) {
const {active, over} = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
setActiveId(null);
}
return (
<DndContext
collisionDetection={closestCenter}
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<SortableContext
items={items}
disabled={isLocked}
strategy={verticalListSortingStrategy}
>
<section
className="w-full bg-[#1E1E1E] bg-main rounded-lg flex flex-row overflow-hidden overflow-x-scroll scroll-pl-8 items-end items-end relative">
<div className=" h-full flex flex-col bg-[#1E1E1E] w-24 rounded-l-lg absolute">
<div className="h-11/12 w-full inner-wrapper flex p-2 items-center justify-center w-full ">
<h1 className="text-white font-semibold">Time</h1>
</div>
<div className="h-2/3 w-full flex justify-center py-8 items-center flex flex-col">
{!isLocked ?
<LockOpenIcon className="w-6 h-6 text-[#23A6F0]" onClick={() => setLocked(true)}/> :
<LockClosedIcon className="w-6 h-6 text-[#23A6F0]" onClick={() => setLocked(false)}/>}
</div>
<div className="h-1/3 w-full flex items-center justify-center flex-col pb-3 ">
<button
className="bg-[#23A6F0] text-white font-bold rounded-full h-10 w-10 flex items-center justify-center "
onClick={()=>setOpenSceneBuilder(true)}>
<PlusIcon className="w-4 h-4"/>
</button>
<p className="text-gray-400 text-xs">New panel</p>
</div>
</div>
<div className="w-full pl-24 flex flex-col items-start justify-start">
<div className="inner-wrapper flex w-full">
{
<VideoEditingTimeline config={timeLineConfig}/>
}
</div>
<div className="panels-wrapper w-full flex flex-row space-x-2 bg-black text-white p-4 pb-0">
<div className=" overflow-hidden overflow-x-scroll flex h-2/3">
{
items.map((item, index) => <TimelinePanel key={index} current={current === index}
panel={panels.find(p => p.id === item) ?? panels[index]}/>)
}
</div>
</div>
</div>
</section>
</SortableContext>
</DndContext>
)
}
export default StoryBoardTimeline