├── .gitignore ├── README.md ├── golang ├── .idea │ └── .gitignore ├── go.mod ├── internal │ ├── handler │ │ └── handler.go │ ├── model │ │ ├── bug.go │ │ ├── feature.go │ │ ├── sprint.go │ │ ├── story.go │ │ ├── subTrack.go │ │ └── task.go │ └── service │ │ ├── bug_factory.go │ │ ├── feature_factory.go │ │ ├── story_factory.go │ │ ├── task_factory.go │ │ └── task_planner_service.go ├── main.go └── pkg │ └── constants.go └── python ├── classes ├── Bug.py ├── Feature.py ├── Sprint.py ├── Story.py ├── Subtrack.py └── Task.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # machine-coding-basic-jira 2 | Create a basic backend for JIRA. Details mentioned as per mentioned in [Doc](https://docs.google.com/document/d/1FrUU7ROZTNCgQKeel6Eh9bLhZtRX43r9IE8xoGEYOhY/edit) 3 | -------------------------------------------------------------------------------- /golang/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /golang/go.mod: -------------------------------------------------------------------------------- 1 | module machine-coding-basic-jira 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /golang/internal/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "machine-coding-basic-jira/internal/model" 5 | "machine-coding-basic-jira/internal/service" 6 | "time" 7 | ) 8 | 9 | type TaskHandler struct { 10 | taskPlannerService service.TaskPlannerService 11 | } 12 | 13 | func GetTaskHandler() *TaskHandler { 14 | return &TaskHandler{ 15 | taskPlannerService: service.GetTaskPlannerService(), 16 | } 17 | } 18 | 19 | func (t *TaskHandler) CreateTask(title string, assignee string, dueDate time.Time, taskType model.TaskType) (model.ITask, error) { 20 | return t.taskPlannerService.CreateTask(title, assignee, dueDate, taskType) 21 | } 22 | -------------------------------------------------------------------------------- /golang/internal/model/bug.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Severity int 4 | 5 | const ( 6 | SEVERITY_P0 Severity = iota 7 | SEVERITY_P1 8 | SEVERITY_P2 9 | ) 10 | 11 | type Bug struct { 12 | Task 13 | Severity Severity 14 | Status TaskStatus 15 | } 16 | 17 | func (b Bug) CreateTask(task *Task, taskType TaskType) (*Task, error) { 18 | //TODO implement me 19 | panic("implement me") 20 | } 21 | 22 | func (b Bug) UpdateTaskStatus(task *Task, status TaskStatus) (*Task, error) { 23 | //TODO implement me 24 | panic("implement me") 25 | } 26 | 27 | func (b Bug) UpdateTaskAssignee(task *Task, assignee string) (*Task, error) { 28 | //TODO implement me 29 | panic("implement me") 30 | } 31 | 32 | func (b Bug) TasksAssignedToUser(assignee string) ([]*Task, error) { 33 | //TODO implement me 34 | panic("implement me") 35 | } 36 | -------------------------------------------------------------------------------- /golang/internal/model/feature.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Impact int 4 | 5 | const ( 6 | IMPACT_LOW Impact = iota 7 | IMPACT_MODERATE 8 | IMPACT_HIGH 9 | ) 10 | 11 | type Feature struct { 12 | Task // Embedding Task struct 13 | FeatureSummary string 14 | Impact Impact 15 | Status TaskStatus 16 | } 17 | -------------------------------------------------------------------------------- /golang/internal/model/sprint.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type AllowedStatusSprint int 6 | 7 | const ( 8 | ALLOWED_STATUS_SPRINT_OPEN AllowedStatusSprint = iota 9 | ALLOWED_STATUS_SPRINT_IN_PROGRESS 10 | ALLOWED_STATUS_SPRINT_COMPLETED 11 | ) 12 | 13 | type Sprint struct { 14 | Tasks []*Task 15 | Name string 16 | StartDate time.Time 17 | EndDate time.Time 18 | AllowedStatus AllowedStatusSprint 19 | } 20 | -------------------------------------------------------------------------------- /golang/internal/model/story.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Story struct { 4 | Task 5 | StorySummary string 6 | Status TaskStatus 7 | SubTracks []*SubTrack 8 | } 9 | -------------------------------------------------------------------------------- /golang/internal/model/subTrack.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type AllowedStatusSubTrack int 4 | 5 | const ( 6 | ALLOWED_STATUS_SUBTRACK_OPEN AllowedStatusSubTrack = iota 7 | ALLOWED_STATUS_SUBTRACK_IN_PROGRESS 8 | ALLOWED_STATUS_SUBTRACK_COMPLETED 9 | ) 10 | 11 | type SubTrack struct { 12 | Title string 13 | AllowedStatus AllowedStatusSubTrack 14 | ParentTask *Task 15 | } 16 | -------------------------------------------------------------------------------- /golang/internal/model/task.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type TaskType int 6 | 7 | const ( 8 | TASK_TYPE_FEATURE TaskType = iota 9 | TASK_TYPE_BUG 10 | TASK_TYPE_STORY 11 | ) 12 | 13 | type TaskStatus int 14 | 15 | const ( 16 | TASK_STATUS_OPEN TaskStatus = iota 17 | TASK_STATUS_IN_PROGRESS 18 | TASK_STATUS_TESTING 19 | TASK_STATUS_DEPLOYED 20 | TASK_STATUS_FIXED 21 | TASK_STATUS_COMPLETED 22 | ) 23 | 24 | type Task struct { 25 | Title string 26 | Assignee string 27 | Type TaskType 28 | DueDate time.Time 29 | } 30 | 31 | type ITask interface { 32 | GetTitle() string 33 | GetAssignee() string 34 | GetType() TaskType 35 | GetDueDate() time.Time 36 | SetAssignee(string) 37 | SetTitle(title string) 38 | SetType(taskType TaskType) 39 | SetDueDate(dueDate time.Time) 40 | } 41 | 42 | func (t *Task) GetTitle() string { 43 | return t.Title 44 | } 45 | func (t *Task) GetAssignee() string { 46 | return t.Assignee 47 | } 48 | func (t *Task) GetType() TaskType { 49 | return t.Type 50 | } 51 | func (t *Task) GetDueDate() time.Time { 52 | return t.DueDate 53 | } 54 | func (t *Task) SetAssignee(assignee string) { 55 | t.Assignee = assignee 56 | } 57 | func (t *Task) SetTitle(title string) { 58 | t.Title = title 59 | } 60 | 61 | func (t *Task) SetType(taskType TaskType) { 62 | t.Type = taskType 63 | } 64 | 65 | func (t *Task) SetDueDate(dueDate time.Time) { 66 | t.DueDate = dueDate 67 | } 68 | -------------------------------------------------------------------------------- /golang/internal/service/bug_factory.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "machine-coding-basic-jira/internal/model" 6 | "time" 7 | ) 8 | 9 | type BugTaskFactory struct{} 10 | 11 | func (b *BugTaskFactory) CreateTask(title string, assignee string, dueDate time.Time) (model.ITask, error) { 12 | // can have validators return error 13 | fmt.Println("Enter Severity: (0 for P0, 1 for P1, 2 for P2") 14 | var severityInput int 15 | _, err := fmt.Scanln(&severityInput) 16 | 17 | if err != nil || severityInput < 0 || severityInput > 2 { 18 | fmt.Println("Invalid input for severityInput value.") 19 | return nil, err 20 | } 21 | return &model.Bug{ 22 | Task: model.Task{ 23 | Title: title, 24 | Assignee: assignee, 25 | DueDate: dueDate, 26 | Type: model.TASK_TYPE_BUG, 27 | }, 28 | Severity: model.Severity(severityInput), 29 | Status: model.TASK_STATUS_OPEN, 30 | }, nil 31 | } 32 | 33 | func (b *BugTaskFactory) UpdateTaskAssignee(task model.ITask, assignee string) (model.ITask, error) { 34 | task.SetAssignee(assignee) 35 | return task, nil 36 | } 37 | -------------------------------------------------------------------------------- /golang/internal/service/feature_factory.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "machine-coding-basic-jira/internal/model" 6 | "time" 7 | ) 8 | 9 | type FeatureTaskFactory struct{} 10 | 11 | func (f *FeatureTaskFactory) CreateTask(title string, assignee string, dueDate time.Time) (model.ITask, error) { 12 | // can have validators return error 13 | var summary string 14 | var impactInput int 15 | fmt.Println("Enter Feature Summary") 16 | fmt.Scanln(&summary) 17 | fmt.Println("Enter impact (0 for Low, 1 for Moderate, 2 for High):") 18 | _, err := fmt.Scanln(&impactInput) 19 | 20 | if err != nil || impactInput < 0 || impactInput > 2 { 21 | fmt.Println("Invalid input or impact value.") 22 | return nil, err 23 | } 24 | return &model.Feature{ 25 | Task: model.Task{ 26 | Title: title, 27 | Assignee: assignee, 28 | DueDate: dueDate, 29 | Type: model.TASK_TYPE_FEATURE, 30 | }, 31 | FeatureSummary: summary, 32 | Impact: model.Impact(impactInput), 33 | Status: model.TASK_STATUS_OPEN, 34 | }, nil 35 | } 36 | 37 | func (f *FeatureTaskFactory) UpdateTaskAssignee(task model.ITask, assignee string) (model.ITask, error) { 38 | task.SetAssignee(assignee) 39 | return task, nil 40 | } 41 | -------------------------------------------------------------------------------- /golang/internal/service/story_factory.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "machine-coding-basic-jira/internal/model" 6 | "time" 7 | ) 8 | 9 | type StoryTaskFactory struct{} 10 | 11 | func (s *StoryTaskFactory) CreateTask(title string, assignee string, dueDate time.Time) (model.ITask, error) { 12 | var summary string 13 | fmt.Println("Enter Story Summary") 14 | fmt.Scanln(&summary) 15 | 16 | return &model.Story{ 17 | Task: model.Task{ 18 | Title: title, 19 | Assignee: assignee, 20 | DueDate: dueDate, 21 | Type: model.TASK_TYPE_STORY, 22 | }, 23 | StorySummary: summary, 24 | Status: model.TASK_STATUS_OPEN, 25 | }, nil 26 | } 27 | 28 | func (s *StoryTaskFactory) UpdateTaskAssignee(task model.ITask, assignee string) (model.ITask, error) { 29 | task.SetAssignee(assignee) 30 | return task, nil 31 | } 32 | -------------------------------------------------------------------------------- /golang/internal/service/task_factory.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "machine-coding-basic-jira/internal/model" 5 | "time" 6 | ) 7 | 8 | type TaskFactory interface { 9 | CreateTask(title string, assignee string, dueDate time.Time) (model.ITask, error) 10 | UpdateTaskAssignee(task model.ITask, assignee string) (model.ITask, error) 11 | // UpdateTaskStatus(task model.ITask, status model.TaskStatus) (model.ITask, error) 12 | //TasksAssignedToUser(assignee string) ([]*model.Task, error) 13 | } 14 | -------------------------------------------------------------------------------- /golang/internal/service/task_planner_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "machine-coding-basic-jira/internal/model" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | var ( 12 | allowedStatus = map[model.TaskType][]model.TaskStatus{ 13 | model.TASK_TYPE_FEATURE: {model.TASK_STATUS_OPEN, model.TASK_STATUS_IN_PROGRESS, model.TASK_STATUS_TESTING, model.TASK_STATUS_DEPLOYED}, 14 | model.TASK_TYPE_BUG: {model.TASK_STATUS_OPEN, model.TASK_STATUS_IN_PROGRESS, model.TASK_STATUS_FIXED}, 15 | model.TASK_TYPE_STORY: {model.TASK_STATUS_OPEN, model.TASK_STATUS_IN_PROGRESS, model.TASK_STATUS_COMPLETED}, 16 | // to be used in respective task status change methods of the respective task factories 17 | } 18 | ) 19 | 20 | type TaskPlannerServiceImpl struct { 21 | Tasks []*model.ITask 22 | TasksGroupedByUsers map[string][]*model.ITask 23 | FeatureTaskFactory // embedding factory structs 24 | StoryTaskFactory 25 | BugTaskFactory 26 | } 27 | 28 | type TaskPlannerService interface { 29 | GetTaskFactoryFromTaskType(model.TaskType) TaskFactory 30 | CreateTask(title string, assignee string, dueDate time.Time, taskType model.TaskType) (model.ITask, error) 31 | UpdateAssignee(task model.ITask) (model.ITask, error) 32 | DisplayTasksAssignedToUser(assignee string) 33 | } 34 | 35 | func (t *TaskPlannerServiceImpl) GetTaskFactoryFromTaskType(taskType model.TaskType) TaskFactory { 36 | // returns specific task factory as per task type 37 | // returning address to the structs since methods of TaskFactory interface are implemented by pointers to these structs 38 | // ie, check method signatures: 39 | // func (f *FeatureTaskFactory) CreateTask(title string, assignee string, dueDate time.Time) (model.ITask, error) 40 | switch taskType { 41 | case model.TASK_TYPE_FEATURE: 42 | return &t.FeatureTaskFactory 43 | case model.TASK_TYPE_BUG: 44 | return &t.BugTaskFactory 45 | case model.TASK_TYPE_STORY: 46 | return &t.StoryTaskFactory 47 | } 48 | return nil 49 | } 50 | 51 | func (t *TaskPlannerServiceImpl) CreateTask(title string, assignee string, dueDate time.Time, taskType model.TaskType) (model.ITask, error) { 52 | taskFactory := t.GetTaskFactoryFromTaskType(taskType) 53 | task, err := taskFactory.CreateTask(title, assignee, dueDate) 54 | if err != nil { 55 | return nil, err 56 | } 57 | t.Tasks = append(t.Tasks, &task) 58 | if taskGroup, ok := t.TasksGroupedByUsers[task.GetAssignee()]; ok { 59 | // Key exists, append to values 60 | t.TasksGroupedByUsers[task.GetAssignee()] = append(taskGroup, &task) 61 | } else { 62 | // Key does not exist, create new key-value pair 63 | t.TasksGroupedByUsers[task.GetAssignee()] = []*model.ITask{&task} 64 | } 65 | 66 | return task, nil 67 | } 68 | 69 | func (t *TaskPlannerServiceImpl) UpdateAssignee(task model.ITask) (model.ITask, error) { 70 | fmt.Println("Enter new assignee") 71 | var assignee string 72 | fmt.Scanln(&assignee) 73 | taskFactory := t.GetTaskFactoryFromTaskType(task.GetType()) 74 | task, err := taskFactory.UpdateTaskAssignee(task, assignee) 75 | // handle error properly 76 | if err != nil { 77 | return nil, err 78 | } 79 | if taskGroup, ok := t.TasksGroupedByUsers[task.GetAssignee()]; ok { 80 | // Key exists, append to values 81 | t.TasksGroupedByUsers[task.GetAssignee()] = append(taskGroup, &task) 82 | } else { 83 | // Key does not exist, create new key-value pair 84 | t.TasksGroupedByUsers[task.GetAssignee()] = []*model.ITask{&task} 85 | } 86 | return task, nil 87 | } 88 | 89 | func (t *TaskPlannerServiceImpl) DisplayTasksAssignedToUser(assignee string) { 90 | if taskGroup, ok := t.TasksGroupedByUsers[assignee]; ok { 91 | for _, task := range taskGroup { 92 | // Ideally, should have DisplayTask as part of ITask interface and other task factories should implement it 93 | log.Println("task: ", task) 94 | } 95 | } else { 96 | // Key does not exist 97 | log.Println("No tasks assigned to given user: ", assignee) 98 | } 99 | } 100 | 101 | var taskPlannerServiceInstance TaskPlannerService 102 | var taskPlannerOnce sync.Once 103 | 104 | func GetTaskPlannerService() TaskPlannerService { 105 | log.Println("initialising GetTaskPlannerService") 106 | taskPlannerOnce.Do(func() { 107 | taskPlannerServiceInstance = &TaskPlannerServiceImpl{ 108 | Tasks: []*model.ITask{}, 109 | TasksGroupedByUsers: map[string][]*model.ITask{}, 110 | FeatureTaskFactory: FeatureTaskFactory{}, 111 | StoryTaskFactory: StoryTaskFactory{}, 112 | BugTaskFactory: BugTaskFactory{}, 113 | } 114 | }) 115 | return taskPlannerServiceInstance 116 | } 117 | -------------------------------------------------------------------------------- /golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "machine-coding-basic-jira/internal/handler" 7 | "machine-coding-basic-jira/internal/model" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | log.Println("\nin main: ") 13 | taskHandler := handler.GetTaskHandler() 14 | 15 | feature, _ := taskHandler.CreateTask("abc", "s", time.Now(), model.TASK_TYPE_FEATURE) 16 | fmt.Println(feature) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /golang/pkg/constants.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /python/classes/Bug.py: -------------------------------------------------------------------------------- 1 | from classes.Task import Task 2 | 3 | 4 | class Bug(Task): 5 | 6 | allowed_status = ['open', 'in_progress', 'fixed'] 7 | 8 | def __init__(self, title, assignee, task_type, due_date, severity, status): 9 | Task.__init__(title, assignee, task_type, due_date) 10 | self.severity = severity 11 | self.status = status if status in self.allowed_status else None 12 | 13 | def change_task_assignee(self, new_assignee): 14 | return Task.change_task_assignee(self, new_assignee) 15 | 16 | def change_status(self, new_status): 17 | if new_status not in self.allowed_status: 18 | print('invalid bug status') 19 | return 20 | if self.allowed_status.index(new_status) < self.allowed_status.index(self.status): 21 | print('new status cannot be behind prevous status') 22 | return 23 | 24 | print('\nchanging status of bug from', 25 | self.status, 'to ', new_status) 26 | self.status = new_status 27 | return 28 | 29 | def display_details(self): 30 | self.display_task() 31 | print('\nbug severity: ', self.severity, '\nbug status', self.status) 32 | -------------------------------------------------------------------------------- /python/classes/Feature.py: -------------------------------------------------------------------------------- 1 | from classes.Task import Task 2 | 3 | 4 | class Feature(Task): 5 | 6 | allowed_status = ['open', 'in_progress', 'testing', 'deployed'] 7 | 8 | def __init__(self, title, assignee, task_type, due_date, feature_summary, impact, status): 9 | Task.__init__(self, title, assignee, task_type, due_date) 10 | self.feature_summary = feature_summary 11 | self.impact = impact 12 | self.status = status if status in self.allowed_status else None 13 | 14 | def change_task_assignee(self, new_assignee): 15 | return Task.change_task_assignee(self, new_assignee) 16 | 17 | def change_status(self, new_status): 18 | if new_status not in self.allowed_status: 19 | print('invalid feature status') 20 | return 21 | if self.allowed_status.index(new_status) < self.allowed_status.index(self.status): 22 | print('new status cannot be behind prevous status') 23 | return 24 | 25 | print('\nchanging status of feature from', 26 | self.status, 'to ', new_status) 27 | self.status = new_status 28 | return 29 | 30 | def display_details(self): 31 | self.display_task() 32 | print('\nfeature summary: ', self.feature_summary, 33 | '\nimpact: ', self.impact, '\nstatus', self.status) 34 | -------------------------------------------------------------------------------- /python/classes/Sprint.py: -------------------------------------------------------------------------------- 1 | class Sprint(): 2 | allowed_status = ['open', 'in_progress', 'completed'] 3 | 4 | def __init__(self, name, start_date, end_date, status, tasks=[]) -> None: 5 | self.name = name 6 | self.start_date = start_date 7 | self.end_date = end_date 8 | self.status = status if status in self.allowed_status else None 9 | self.tasks = tasks 10 | 11 | def add_task_to_sprint(self, task): 12 | if self.status != 'completed': 13 | self.tasks.append(task) 14 | else: 15 | print('cant add to sprint, sprint ended already') 16 | 17 | def remove_task_from_sprint(self, task_title): 18 | if (len(self.tasks) == 0): 19 | print('\ncannot remove task from empty sprint') 20 | return 21 | for index, task in enumerate(self.tasks): 22 | if task.title == task_title: 23 | del self.tasks[index] 24 | print('\ntask removed from sprint') 25 | 26 | def change_status(self, new_status): 27 | if new_status not in self.allowed_status: 28 | print('invalid bug status') 29 | return 30 | if self.allowed_status.index(new_status) < self.allowed_status.index(self.status): 31 | print('new status cannot be behind prevous status') 32 | return 33 | 34 | print('\nchanging status of sprint from', 35 | self.status, 'to ', new_status) 36 | self.status = new_status 37 | return 38 | 39 | def start_sprint(self): 40 | self.change_status('open') 41 | 42 | def end_sprint(self): 43 | self.change_status('completed') 44 | 45 | def display_sprint_details(self): 46 | print('\ndisplaying sprint details') 47 | print('\nsprint name', self.name, '\nsprint start_date: ', self.start_date, '\nend_date: ', self.end_date, 48 | '\nstatus: ', self.status) 49 | if (len(self.tasks) == 0): 50 | print('\nsprint has no tasks') 51 | return 52 | for task in self.tasks: 53 | task.display_details() 54 | -------------------------------------------------------------------------------- /python/classes/Story.py: -------------------------------------------------------------------------------- 1 | from classes.Subtrack import Subtrack 2 | from classes.Task import Task 3 | 4 | 5 | class Story(Task): 6 | 7 | allowed_status = ['open', 'in_progress', 'completed'] 8 | 9 | def __init__(self, title, assignee, task_type, due_date, story_summary, status, subtracks=[]): 10 | Task.__init__(self, title=title, assignee=assignee, 11 | task_type=task_type, due_date=due_date) 12 | self.story_summary = story_summary 13 | self.status = status if status in self.allowed_status else None 14 | self.subtracks = subtracks 15 | 16 | def change_task_assignee(self, new_assignee): 17 | return Task.change_task_assignee(self, new_assignee) 18 | 19 | def change_status(self, new_status): 20 | if new_status not in self.allowed_status: 21 | print('invalid story status') 22 | return 23 | if self.allowed_status.index(new_status) < self.allowed_status.index(self.status): 24 | print('new status cannot be behind prevous status') 25 | return 26 | 27 | if new_status != 'completed': 28 | self.status = new_status 29 | else: 30 | if len(self.subtracks) == 0: 31 | self.status = new_status 32 | print('\nchanging status of story from', 33 | self.status, 'to ', new_status) 34 | return 35 | else: 36 | count_completed_subtracks = 0 37 | for subtrack in self.subtracks: 38 | if subtrack.status == 'completed': 39 | count_completed_subtracks += 1 40 | if len(self.subtracks) == count_completed_subtracks: 41 | self.status = new_status 42 | print('\nchanging status of story from', 43 | self.status, 'to ', new_status) 44 | else: 45 | print('cant complete story, subtracks still left') 46 | 47 | return 48 | 49 | def add_subtrack_to_current_story(self): 50 | if self.status == 'completed': 51 | print('\nstory is completed. cannot add subtrack') 52 | else: 53 | subtrack_title = input('subtrack_title?') 54 | subtrack_status = input('subtrack status?') 55 | subtrack = Subtrack( 56 | subtrack_title, status=subtrack_status, parent_task_title=self.title) 57 | self.subtracks.append(subtrack) 58 | 59 | def display_details(self): 60 | self.display_task() 61 | print('\nstory summary: ', self.story_summary, '\nstatus', self.status) 62 | if (len(self.subtracks) == 0): 63 | print('\ncurrent story has no sub_tracks') 64 | else: 65 | print('\ndisplaying subtracks for story') 66 | for subtrack in self.subtracks: 67 | subtrack.display_details() 68 | -------------------------------------------------------------------------------- /python/classes/Subtrack.py: -------------------------------------------------------------------------------- 1 | class Subtrack(): 2 | 3 | allowed_status = ['open', 'in_progress', 'completed'] 4 | 5 | def __init__(self, subtrack_title, status, parent_task_title) -> None: 6 | self.subtrack_title = subtrack_title 7 | self.status = status if status in self.allowed_status else None 8 | self.parent_task_title = parent_task_title 9 | 10 | def display_details(self): 11 | print('\nsubtrack title: ', self.subtrack_title, '\nsubtrack status', 12 | self.status, '\nparent task title: ', self.parent_task_title) 13 | 14 | def change_status(self, new_status): 15 | if new_status not in self.allowed_status: 16 | print('invalid subtrack status') 17 | return 18 | if self.allowed_status.index(new_status) < self.allowed_status.index(self.status): 19 | print('new status cannot be behind prevous status') 20 | return 21 | 22 | self.status = new_status 23 | return 24 | -------------------------------------------------------------------------------- /python/classes/Task.py: -------------------------------------------------------------------------------- 1 | class Task(): 2 | def __init__(self, title, assignee, task_type, due_date): 3 | self.title = title 4 | self.assignee = assignee 5 | self.task_type = task_type 6 | self.due_date = due_date 7 | 8 | def change_task_assignee(self, new_assignee): 9 | self.assignee = new_assignee 10 | 11 | def display_task(self): 12 | print('\ntask title: ', self.title, '\ntask type: ', 13 | self.task_type, '\ndue date: ', self.due_date) 14 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.6.16 2 | chardet==3.0.4 3 | Click==7.0 4 | Flask==1.1.1 5 | idna==2.8 6 | itsdangerous==1.1.0 7 | Jinja2==2.10.1 8 | MarkupSafe==1.1.1 9 | requests==2.22.0 10 | urllib3==1.25.3 11 | Werkzeug==0.15.6 12 | Flask-Cors==3.0.9 13 | python-dateutil==2.8.1 --------------------------------------------------------------------------------