608 lines
25 KiB
HTML
608 lines
25 KiB
HTML
<!--
|
|
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
~ Copyright (c) 2016 Codethink Ltd.
|
|
~ Copyright (c) 2017 Adam Coldrick
|
|
~
|
|
~ Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
~ not use this file except in compliance with the License. You may obtain
|
|
~ a copy of the License at
|
|
~
|
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
~
|
|
~ Unless required by applicable law or agreed to in writing, software
|
|
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
~ License for the specific language governing permissions and limitations
|
|
~ under the License.
|
|
-->
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
<div class="alert alert-danger" ng-show="story.private">
|
|
<i class="fa fa-eye-slash"></i>
|
|
<strong>This story is private</strong>.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
<div ng-include
|
|
src="'/inline/story_detail_form.html'">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
<hr/>
|
|
<div ng-include src="'/inline/task_list.html'"></div>
|
|
<hr/>
|
|
<div ng-include src="'/inline/tags.html'"></div>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<div class="row">
|
|
<div class="clearfix">
|
|
<div class="pull-right">
|
|
<div class="btn" ng-show="isUpdating">
|
|
<i class="fa fa-spinner fa-lg fa-spin"></i>
|
|
</div>
|
|
<button type="button"
|
|
class="btn btn-primary"
|
|
ng-click="createStory()"
|
|
ng-disabled="!isValid()">
|
|
Create Story
|
|
</button>
|
|
</div>
|
|
<button type="button"
|
|
class="btn btn-primary"
|
|
ng-click="preview = !preview">
|
|
Toggle Preview
|
|
</button>
|
|
<a class="btn btn-link" target="_blank"
|
|
href="http://daringfireball.net/projects/markdown/basics">
|
|
Markdown formatting is supported.
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Template for the header and description -->
|
|
<script type="text/ng-template" id="/inline/story_detail_form.html">
|
|
<hr ng-hide="story.private">
|
|
<form name="storyForm" role="form" class="form-horizontal">
|
|
<div class="form-group has-feedback"
|
|
ng-class="{'has-error': storyForm.title.$invalid,
|
|
'has-success': !storyForm.title.$invalid}">
|
|
<label for="title" class="col-sm-2 control-label">
|
|
Title
|
|
</label>
|
|
<div class="col-sm-10">
|
|
<input id="title"
|
|
name="title"
|
|
type="text"
|
|
class="form-control"
|
|
ng-model="story.title"
|
|
required
|
|
focus
|
|
maxlength="100"
|
|
placeholder="Story title"
|
|
ng-disabled="isUpdating">
|
|
<span class="form-control-feedback"
|
|
ng-show="storyForm.title.$invalid">
|
|
<i class="fa fa-times fa-lg"></i>
|
|
</span>
|
|
<span class="form-control-feedback"
|
|
ng-show="!storyForm.title.$invalid">
|
|
<i class="fa fa-check fa-lg"></i>
|
|
</span>
|
|
|
|
<div class="help-block text-danger"
|
|
ng-show="storyForm.title.$invalid">
|
|
<span ng-show="storyForm.title.$error.required">
|
|
A story title is required.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr ng-show="preview">
|
|
<div class="form-group" ng-show="preview">
|
|
<div class="col-sm-offset-1 col-sm-10">
|
|
<insert-markdown content="story.description">
|
|
</insert-markdown>
|
|
</div>
|
|
</div>
|
|
<hr ng-show="preview">
|
|
<div class="form-group">
|
|
<label for="description"
|
|
class="col-sm-2 control-label">
|
|
Description
|
|
</label>
|
|
<div class="col-sm-10">
|
|
<textarea id="description"
|
|
class="form-control"
|
|
ng-model="story.description"
|
|
msd-elastic
|
|
placeholder="Enter a story description here."
|
|
ng-disabled="isUpdating">
|
|
</textarea>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-if="!forcePrivate">
|
|
<label for="private" class="col-sm-2 control-label">
|
|
Private or Security Vulnerability
|
|
</label>
|
|
<div class="col-sm-10 checkbox">
|
|
<input id="private"
|
|
type="checkbox"
|
|
class="modal-checkbox"
|
|
ng-model="story.private"
|
|
ng-disabled="isUpdating"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 col-md-offset-3"
|
|
ng-show="story.private">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Teams and Users that can see this story. If it is a security vulnerability,
|
|
add the associated teams, e.g. vmt, $project coresec, etc.</th>
|
|
<th class="text-right">
|
|
<small>
|
|
<a href
|
|
ng-click="showAddUser = !showAddUser">
|
|
<i class="fa fa-plus" ng-if="!showAddUser"></i>
|
|
<i class="fa fa-minus" ng-if="showAddUser"></i>
|
|
Add Team or User
|
|
</a>
|
|
</small>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="team in story.teams">
|
|
<td colspan="2">
|
|
<i class="fa fa-sb-team"></i>
|
|
{{ team.name }}
|
|
<a class="close"
|
|
ng-click="removeTeam(team)"
|
|
ng-show="story.teams.length > 1 || story.users.length > 0">
|
|
×
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr ng-repeat="user in story.users">
|
|
<td colspan="2">
|
|
<i class="fa fa-sb-user"></i>
|
|
{{user.full_name}}
|
|
<a class="close"
|
|
ng-click="removeUser(user)"
|
|
ng-show="story.users.length > 1 || story.teams.length > 0">
|
|
×
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr ng-show="showAddUser">
|
|
<td colspan="2">
|
|
<input id="user"
|
|
type="text"
|
|
autocomplete="off"
|
|
placeholder="Click to add a team or user"
|
|
ng-model="asyncUser"
|
|
typeahead-wait-ms="200"
|
|
typeahead-editable="false"
|
|
typeahead="actor as actor.name for actor in
|
|
searchActors($viewValue, story.users, story.teams)"
|
|
typeahead-loading="loadingUsers"
|
|
typeahead-on-select="addActor($model)"
|
|
typeahead-template-url="/inline/actor-typeahead-item.html"
|
|
class="form-control input-sm"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</script>
|
|
|
|
|
|
<!-- Template for the Team/User typeahead items -->
|
|
<script type="text/ng-template" id="/inline/actor-typeahead-item.html">
|
|
<a tabindex="-1">
|
|
<i ng-class="'fa fa-sb-' + match.model.type"></i>
|
|
<span ng-bind-html="match.model.name | typeaheadHighlight:query"></span>
|
|
</a>
|
|
</script>
|
|
|
|
|
|
<!-- Template for the tags list and controls -->
|
|
<script type="text/ng-template" id="/inline/tags.html">
|
|
<h3>Tags</h3>
|
|
<ul class="list-inline">
|
|
<li ng-repeat="tag in tags">
|
|
<h4>
|
|
<span class="label label-warning">
|
|
{{tag}}
|
|
<i class="fa fa-remove" ng-show="isLoggedIn"
|
|
ng-click="removeTag(tag)"></i>
|
|
</span>
|
|
</h4>
|
|
</li>
|
|
</ul>
|
|
<span class="input-group" ng-show="showAddTag">
|
|
<input type="text" class="form-control" ng-model="newTag.name"
|
|
typeahead="tag as tag.name for tag in searchTags($viewValue)"
|
|
typeahead-on-select="updateViewValue($model.name);"/>
|
|
|
|
<span class="input-group-btn">
|
|
<button class="btn btn-primary" type="button"
|
|
ng-click="addTag(newTag.name)">Add</button>
|
|
<button class="btn btn-default" type="button"
|
|
ng-click="toggleAddTag()">Cancel</button>
|
|
</span>
|
|
</span>
|
|
<button class="btn btn-default" ng-click="toggleAddTag()"
|
|
ng-show="!showAddTag && isLoggedIn">
|
|
<i class="fa fa-plus-circle"></i>
|
|
Add
|
|
</button>
|
|
</script>
|
|
|
|
<!-- Template for the task list -->
|
|
<script type="text/ng-template" id="/inline/task_list.html">
|
|
<h3>Tasks</h3>
|
|
<div ng-repeat="name in projectNames | orderBy">
|
|
<h4>
|
|
<i class="fa fa-sb-project text-muted"></i>
|
|
<a href="#!/project/{{ projects[name].id }}">{{ name }}</a>
|
|
</h4>
|
|
<div ng-repeat="branchName in projects[name].branchNames">
|
|
<div class="row" ng-if="branchName != 'master'">
|
|
<div class="col-xs-3">
|
|
<strong>
|
|
  {{ branchName }}
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
<div class="row" ng-if="branchName == 'master'">
|
|
<div class="col-xs-12" ng-include src="'/inline/task_table.html'"></div>
|
|
</div>
|
|
<div class="row" ng-if="branchName != 'master'">
|
|
<div class="col-xs-11 col-xs-offset-1"
|
|
ng-include src="'/inline/task_table.html'">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div ng-show="newTask.show" ng-include src="'/inline/new_task.html'"></div>
|
|
<button class="btn btn-default"
|
|
ng-show="isLoggedIn && !newTask.show"
|
|
ng-click="newTask.show = !newTask.show">
|
|
<i class="fa fa-plus-circle"></i>
|
|
Add a project
|
|
</button>
|
|
</script>
|
|
|
|
|
|
<!-- Template for a table of tasks -->
|
|
<script type="text/ng-template" id="/inline/task_table.html">
|
|
<table class="table table-striped table-supercondensed">
|
|
<tr ng-repeat="task in projects[name].branches[branchName].tasks | orderBy: 'id'"
|
|
ng-include src="'/inline/task_list_item.html'">
|
|
</tr>
|
|
<tr ng-show="isLoggedIn && !projects[name].branches[branchName].showAddTask">
|
|
<td class="text-center">
|
|
<a href
|
|
ng-click="projects[name].branches[branchName].showAddTask =
|
|
!projects[name].branches[branchName].showAddTask">
|
|
<i class="fa fa-plus-circle"></i>
|
|
Add task affecting this project
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr ng-show="projects[name].branches[branchName].showAddTask"
|
|
ng-include src="'/inline/task_edit_form.html'">
|
|
</tr>
|
|
</table>
|
|
</script>
|
|
|
|
<!-- Template for an item in the task list -->
|
|
<script type="text/ng-template" id="/inline/task_list_item.html">
|
|
<td>
|
|
<div class="row">
|
|
<div class="col-xs-4 task-title">
|
|
<div>
|
|
<a href ng-click="showDetail = !showDetail">
|
|
<i class="fa fa-caret-down" ng-show="showDetail"></i>
|
|
<i class="fa fa-caret-right" ng-show="!showDetail && (task.link || isLoggedIn)"></i>
|
|
|
|
</a>
|
|
<strong>{{ task.id }}</strong>
|
|
|
|
</div>
|
|
<div class="title">
|
|
<input-inline
|
|
ng-model="task.title"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTask(task, 'title', task.title)"
|
|
empty-prompt="Click to set title."
|
|
empty-disabled-prompt="No title."
|
|
maxlength="255"
|
|
as-inline="true"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="col-xs-3">
|
|
<user-typeahead
|
|
ng-model="task.assignee_id"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTask(task, 'assignee_id', task.assignee_id)"
|
|
empty-prompt="Click to assign."
|
|
empty-disabled-prompt="Not assigned."
|
|
as-inline="true"
|
|
/>
|
|
</div>
|
|
<div class="col-xs-2">
|
|
<task-status-dropdown
|
|
editable="{{isLoggedIn}}"
|
|
on-change="updateTask(task, 'status', status)"
|
|
status="{{ task.status }}"
|
|
/>
|
|
</div>
|
|
<!-- Review link should go here once implemented in the API -->
|
|
</div>
|
|
<div class="padded-row" ng-show="showDetail">
|
|
<div class="col-xs-10 col-xs-offset-1">
|
|
<project-typeahead
|
|
auto-focus="false"
|
|
ng-model="task.project_id"
|
|
ng-show="changingProject"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTask(task, 'project_id', task.project_id, name, branchName);
|
|
changingProject = false"
|
|
empty-prompt="Click to set a project"
|
|
empty-disabled-prompt="No project set."
|
|
as-inline="false"
|
|
placeholder="Enter project name">
|
|
</project-typeahead>
|
|
<button class="btn btn-xs btn-default"
|
|
ng-show="isLoggedIn"
|
|
ng-click="changingProject = !changingProject">
|
|
{{ changingProject ? 'Cancel' : 'Change project' }}
|
|
</button>
|
|
<button class="btn btn-xs btn-default"
|
|
ng-click="removeTask(task, name, branchName)">
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="padded-row" ng-show="showDetail">
|
|
<div class="col-xs-10 col-xs-offset-1 task-notes"
|
|
ng-show="isLoggedIn">
|
|
<button class="btn btn-primary btn-xs pull-right"
|
|
ng-show="!task.editing && task.link && isLoggedIn"
|
|
ng-click="editNotes(task)">
|
|
<i class="fa fa-pencil"></i> Edit
|
|
</button>
|
|
<insert-markdown content="task.link" ng-show="!task.editing"></insert-markdown>
|
|
<insert-markdown content="task.tempNotes" ng-show="task.editing && notesPreview"></insert-markdown>
|
|
<form name="taskNotesForm" ng-show="task.editing">
|
|
<div class="form-group">
|
|
<textarea placeholder="Enter task notes here"
|
|
class="form-control context-edit"
|
|
msd-elastic
|
|
rows="3"
|
|
ng-disabled="isUpdating"
|
|
ng-model="task.tempNotes">
|
|
</textarea>
|
|
</div>
|
|
|
|
<div class="clearfix">
|
|
<button type="button"
|
|
class="btn btn-default" ng-click="notesPreview = !notesPreview">
|
|
Toggle Preview
|
|
</button>
|
|
<div class="pull-right">
|
|
<div class="btn" ng-show="isUpdating">
|
|
<i class="fa fa-spinner fa-lg fa-spin"></i>
|
|
</div>
|
|
<button type="button"
|
|
class="btn btn-primary"
|
|
ng-click="updateTask(task, 'link', task.tempNotes);
|
|
cancelEditNotes(task)"
|
|
ng-disabled="!taskNotesForm.$valid">
|
|
Save
|
|
</button>
|
|
<button type="button" ng-show="isLoggedIn"
|
|
class="btn btn-default"
|
|
ng-click="cancelEditNotes(task)">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<div class="padded-row pull-right" ng-show="!task.editing && !task.link">
|
|
<button class="btn btn-primary btn-xs"
|
|
ng-click="editNotes(task)">
|
|
<i class="fa fa-pencil"></i> Add notes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-xs-10 col-xs-offset-1 task-notes"
|
|
ng-show="!isLoggedIn && task.link">
|
|
<insert-markdown content="task.link" ng-show="!task.editing"></insert-markdown>
|
|
<insert-markdown content="task.tempNotes" ng-show="task.editing && notesPreview"></insert-markdown>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</script>
|
|
|
|
<!-- Template for the task add form. -->
|
|
<script type="text/ng-template" id="/inline/task_edit_form.html">
|
|
<td>
|
|
<div class="row">
|
|
<div class="col-xs-4 task-title">
|
|
<div>
|
|
<a href ng-click="showDetail = !showDetail">
|
|
<i class="fa fa-caret-down" ng-show="showDetail"></i>
|
|
<i class="fa fa-caret-right" ng-show="!showDetail"></i>
|
|
</a>
|
|
|
|
</div>
|
|
<input-inline
|
|
auto-focus="true"
|
|
ng-model="projects[name].branches[branchName].newTask.title"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTaskInline()"
|
|
empty-prompt="Click to assign."
|
|
empty-disabled-prompt="Not assigned."
|
|
maxlength="255"
|
|
as-inline="false"
|
|
placeholder="Enter task name"
|
|
/>
|
|
</div>
|
|
<div class="col-xs-3">
|
|
<user-typeahead
|
|
auto-focus="false"
|
|
ng-model="projects[name].branches[branchName].newTask.assignee_id"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTaskInline()"
|
|
empty-prompt="Click to assign."
|
|
empty-disabled-prompt="Not assigned."
|
|
as-inline="false"
|
|
placeholder="Assign user to task"
|
|
/>
|
|
</div>
|
|
<div class="col-xs-2">
|
|
<task-status-dropdown
|
|
editable="{{isLoggedIn}}"
|
|
on-change="updateTask(projects[name].branches[branchName].newTask, 'status', status)"
|
|
status="{{projects[name].branches[branchName].newTask.status}}"
|
|
/>
|
|
</div>
|
|
<!-- Review link should go here once implemented in the API -->
|
|
<div class="col-xs-2 text-right col-xs-offset-1">
|
|
<button ng-click="createTask(projects[name].branches[branchName].newTask,
|
|
projects[name].branches[branchName])"
|
|
class="btn btn-primary">
|
|
Save
|
|
</button>
|
|
<button ng-click="projects[name].branches[branchName].showAddTask =
|
|
!projects[name].branches[branchName].showAddTask"
|
|
class="btn btn-default">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="row" ng-show="showDetail">
|
|
<div class="col-xs-10 col-xs-offset-1">
|
|
<textarea ng-model="projects[name].branches[branchName].newTask.link"
|
|
class="form-control context-edit"
|
|
msd-elastic
|
|
rows="3"
|
|
ng-show="isLoggedIn"
|
|
placeholder="Task notes">
|
|
</textarea>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</script>
|
|
|
|
<!-- Template for creating a task in an as-yet unrelated project -->
|
|
<script type="text/ng-template" id="/inline/new_task.html">
|
|
<div>
|
|
<h4>
|
|
<i class="fa fa-sb-project text-muted"></i>
|
|
<project-typeahead
|
|
auto-focus="false"
|
|
ng-model="newTask.project_id"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTaskInline()"
|
|
empty-prompt="Click to set a project"
|
|
empty-disabled-prompt="No project set."
|
|
as-inline="false"
|
|
placeholder="Enter project name">
|
|
</project-typeahead>
|
|
</h4>
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
<table class="table table-supercondensed">
|
|
<tr>
|
|
<td>
|
|
<div class="row">
|
|
<div class="col-xs-4 task-title">
|
|
<div>
|
|
<a href ng-click="showDetail = !showDetail">
|
|
<i class="fa fa-caret-down" ng-show="showDetail"></i>
|
|
<i class="fa fa-caret-right" ng-show="!showDetail"></i>
|
|
</a>
|
|
|
|
</div>
|
|
<input-inline
|
|
auto-focus="true"
|
|
ng-model="newTask.title"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTaskInline()"
|
|
empty-prompt="Click to assign."
|
|
empty-disabled-prompt="Not assigned."
|
|
maxlength="255"
|
|
as-inline="false"
|
|
placeholder="Enter task name"
|
|
/>
|
|
</div>
|
|
<div class="col-xs-3">
|
|
<user-typeahead
|
|
auto-focus="false"
|
|
ng-model="newTask.assignee_id"
|
|
enabled="isLoggedIn"
|
|
on-change="updateTaskInline()"
|
|
empty-prompt="Click to assign."
|
|
empty-disabled-prompt="Not assigned."
|
|
as-inline="false"
|
|
placeholder="Assign user to task"
|
|
/>
|
|
</div>
|
|
<div class="col-xs-2">
|
|
<task-status-dropdown
|
|
editable="{{isLoggedIn}}"
|
|
on-change="updateTask(newTask, 'status', status)"
|
|
status="{{newTask.status}}"
|
|
/>
|
|
</div>
|
|
<!-- Review link should go here once implemented in the API -->
|
|
<div class="col-xs-2 text-right col-xs-offset-1">
|
|
<button ng-click="createTask(newTask)"
|
|
class="btn btn-primary">
|
|
Add
|
|
</button>
|
|
<button ng-click="newTask.show = !newTask.show"
|
|
ng-if="tasks.length > 0"
|
|
class="btn btn-default">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="row" ng-show="showDetail">
|
|
<div class="col-xs-10 col-xs-offset-1">
|
|
<textarea ng-model="newTask.link"
|
|
class="form-control context-edit"
|
|
msd-elastic
|
|
rows="3"
|
|
ng-show="isLoggedIn"
|
|
placeholder="Task notes">
|
|
</textarea>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</script>
|