add the dumb strategy to add a new feature a day in the flow

This commit is contained in:
Julien Calixte
2023-07-22 18:34:30 +02:00
parent 05e89f6b03
commit 0d875fd1c2
6 changed files with 76 additions and 32 deletions

View File

@@ -14,13 +14,13 @@ ul {
border: 3px solid var(--background-color); border: 3px solid var(--background-color);
color: var(--background-color); color: var(--background-color);
background-color: white; background-color: white;
height: var(--feature-item-height); min-height: var(--feature-item-height);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 12pt; font-size: 12pt;
padding: 0 0.5rem; padding: 0 0.5rem 0.2rem;
text-align: center; text-align: center;
transition-property: border, color, background-color, font-size; transition-property: border, color, background-color, font-size;
transition-duration: 1s; transition-duration: 1s;

View File

@@ -7,16 +7,14 @@ const hasQualityIssues = computed(() => props.feature.qualityIssue > 0)
</script> </script>
<template> <template>
<div class="feature-item"> <div class="feature-item bin">
<div class="bin">
<div> <div>
{{ feature.name }} {{ feature.name }}
<span class="numeric">({{ feature.complexity }})</span> <span class="numeric">({{ feature.complexity }})</span>
</div> </div>
<div class="lead-time numeric">{{ feature.leadTime }} days</div> <div class="lead-time numeric">{{ feature.leadTime }} days</div>
<div v-if="hasQualityIssues" class="red-bin numeric"> <div v-if="hasQualityIssues" class="red-bin numeric">
{{ feature.qualityIssue }} issues {{ feature.qualityIssue }} 🔴
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -24,8 +22,8 @@ const hasQualityIssues = computed(() => props.feature.qualityIssue > 0)
<style scoped lang="scss"> <style scoped lang="scss">
.feature-item { .feature-item {
.red-bin { .red-bin {
background-color: #ff7979; border: 2px solid #ff7979;
padding: 0 0.5rem; padding: 0 0.5rem 0.1rem;
} }
} }
</style> </style>

View File

@@ -23,16 +23,13 @@ const hasFeaturesInProgress = computed(
() => featuresInProgress.value.length > 0 () => featuresInProgress.value.length > 0
) )
const hasFeaturesDone = computed(() => featuresDone.value.length > 0) const hasFeaturesDone = computed(() => featuresDone.value.length > 0)
const isLive = computed(
() => props.step.title.toLocaleLowerCase() === 'release'
)
</script> </script>
<template> <template>
<li class="feature-step"> <li class="feature-step">
<header>{{ step.title }}</header> <header>{{ step.title }}</header>
<section class="doing"> <section class="doing">
<h5>📝</h5> <h5>📝 ({{ featuresInProgress.length }})</h5>
<ul v-if="hasFeaturesInProgress"> <ul v-if="hasFeaturesInProgress">
<li v-for="feature in featuresInProgress" :key="feature.name"> <li v-for="feature in featuresInProgress" :key="feature.name">
<Starport <Starport
@@ -45,7 +42,7 @@ const isLive = computed(
</ul> </ul>
</section> </section>
<section class="done"> <section class="done">
<h5>📝</h5> <h5>📝 ({{ featuresDone.length }})</h5>
<div <div
v-for="blueBucket in remainingBlueBuckets" v-for="blueBucket in remainingBlueBuckets"
:key="blueBucket" :key="blueBucket"
@@ -57,7 +54,7 @@ const isLive = computed(
<li v-for="feature in featuresDone" :key="feature.name"> <li v-for="feature in featuresDone" :key="feature.name">
<Starport <Starport
:port="feature.name" :port="feature.name"
style="height: var(--feature-item-height)" style="height: calc(var(--feature-item-height) + 0.2rem)"
> >
<FeatureItem :feature="feature" /> <FeatureItem :feature="feature" />
</Starport> </Starport>
@@ -68,6 +65,22 @@ const isLive = computed(
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
@mixin hideScrollbar {
// https://blogs.msdn.microsoft.com/kurlak/2013/11/03/hiding-vertical-scrollbars-with-pure-css-in-chrome-ie-6-firefox-opera-and-safari/
// There is a CSS rule that can hide scrollbars in Webkit-based browsers (Chrome and Safari).
&::-webkit-scrollbar {
width: 0 !important;
}
// There is a CSS rule that can hide scrollbars in IE 10+.
-ms-overflow-style: none;
// Use -ms-autohiding-scrollbar if you wish to display on hover.
// -ms-overflow-style: -ms-autohiding-scrollbar;
// There used to be a CSS rule that could hide scrollbars in Firefox, but it has since been deprecated.
scrollbar-width: none;
}
.feature-step { .feature-step {
header { header {
padding: 0.5rem; padding: 0.5rem;
@@ -76,10 +89,13 @@ const isLive = computed(
} }
section { section {
@include hideScrollbar();
margin: 1rem 0; margin: 1rem 0;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto;
max-height: 40vh;
} }
h5 { h5 {
@@ -87,6 +103,7 @@ const isLive = computed(
background-color: var(--background-color); background-color: var(--background-color);
padding: 0.35rem; padding: 0.35rem;
text-align: center; text-align: center;
color: white;
} }
li { li {
@@ -95,7 +112,6 @@ const isLive = computed(
.done-list { .done-list {
flex: 1; flex: 1;
overflow-y: auto;
} }
} }
</style> </style>

View File

@@ -8,24 +8,35 @@ import { computed, onMounted, ref } from 'vue'
const featureBoard = createFeatureBoard() const featureBoard = createFeatureBoard()
const totalDays = ref(0)
const features = ref<Feature[]>([]) const features = ref<Feature[]>([])
const meanComplexity = computed( const meanComplexity = computed(
() => () =>
sumElements(features.value.map((feature) => feature.complexity)) / Math.round(
features.value.length 100 *
(sumElements(features.value.map((feature) => feature.complexity)) /
features.value.length)
) / 100
) )
const meanLeadTime = computed( const meanLeadTime = computed(
() => () =>
sumElements(features.value.map((feature) => feature.leadTime)) / Math.round(
features.value.length 100 *
(sumElements(features.value.map((feature) => feature.leadTime)) /
features.value.length)
) / 100
) )
onMounted(() => (features.value = featureBoard.initBoard(featureSteps))) onMounted(() => (features.value = featureBoard.initBoard(featureSteps)))
const nextDay = () => { const nextDay = () => {
features.value = featureBoard.nextDay(features.value) totalDays.value++
features.value = featureBoard.nextDay(
features.value,
featureSteps[0].stepIndex
)
} }
const featuresGroupedByStep = computed(() => { const featuresGroupedByStep = computed(() => {
@@ -51,6 +62,7 @@ const featuresGroupedByStep = computed(() => {
</div> </div>
<div> <div>
<button @click="nextDay">next day</button> <button @click="nextDay">next day</button>
Total days: {{ totalDays }}
</div> </div>
</div> </div>
<ul class="features-steps"> <ul class="features-steps">

View File

@@ -3,6 +3,8 @@ import { FeatureStep } from '@/modules/feature/feature-steps'
import { features } from '@/modules/feature/feature.fixture' import { features } from '@/modules/feature/feature.fixture'
import { pickRandomElement, popNElement, shuffleArray } from '@/utils' import { pickRandomElement, popNElement, shuffleArray } from '@/utils'
const MAX_FEATURES = 30
const hasQualityIssue = ( const hasQualityIssue = (
complexity: number, complexity: number,
tasksInParallel: number tasksInParallel: number
@@ -10,18 +12,24 @@ const hasQualityIssue = (
let probabilityOfQualityIssue = 0 let probabilityOfQualityIssue = 0
switch (complexity) { switch (complexity) {
case 0:
probabilityOfQualityIssue = 0.99
case 1: case 1:
probabilityOfQualityIssue = 0.8 probabilityOfQualityIssue = 0.93
break break
case 2: case 2:
probabilityOfQualityIssue = 0.7 probabilityOfQualityIssue = 0.9
break break
case 3: case 3:
probabilityOfQualityIssue = 0.6 probabilityOfQualityIssue = 0.85
break
case 4:
probabilityOfQualityIssue = 0.77
break break
default: default:
probabilityOfQualityIssue = 0.5 probabilityOfQualityIssue = 0.65
break break
} }
@@ -38,6 +46,8 @@ const hasQualityIssue = (
multiplicator = 1.1 multiplicator = 1.1
case 5: case 5:
multiplicator = 1.15 multiplicator = 1.15
default:
multiplicator = 1.25
} }
return Math.random() > probabilityOfQualityIssue / multiplicator return Math.random() > probabilityOfQualityIssue / multiplicator
@@ -58,7 +68,7 @@ export const createFeatureBoard = () => {
return initialFeatures return initialFeatures
} }
const nextDay = (features: Feature[]): Feature[] => { const nextDay = (features: Feature[], initialStep: number): Feature[] => {
features.forEach((feature) => { features.forEach((feature) => {
const isFeatureLive = feature.step === 0 && feature.status === 'done' const isFeatureLive = feature.step === 0 && feature.status === 'done'
if (isFeatureLive) { if (isFeatureLive) {
@@ -91,6 +101,14 @@ export const createFeatureBoard = () => {
} }
}) })
if (features.length < MAX_FEATURES) {
const [newFeature] = popNElement(boardFeatures, 1)
if (newFeature) {
features.push({ ...newFeature, step: initialStep })
}
}
return features return features
} }

View File

@@ -205,7 +205,7 @@ const featureNames = [
export const features: Feature[] = featureNames.map((name) => ({ export const features: Feature[] = featureNames.map((name) => ({
name, name,
complexity: Math.floor(Math.random() * 5), complexity: Math.floor(Math.random() * 6),
leadTime: 0, leadTime: 0,
status: 'doing', status: 'doing',
step: Infinity, step: Infinity,