add the dumb strategy to add a new feature a day in the flow
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user