Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List of projects with a specific TaskType #4745

Merged
merged 14 commits into from
Aug 12, 2020
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Added
- Added the possibility to delete datasets on disk from webKnossos. Use with care. [#4696](https://github.com/scalableminds/webknossos/pull/4696)
- Added a list of all projects containing tasks of a specific task type. It's accessible from the task types list view. [#4420](https://github.com/scalableminds/webknossos/pull/4745)

### Changed
- When d/f switching is turned off and a slice is copied with the shortcut `v`, the previous slice used as the source will always be slice - 1 and `shift + v` will always take slice + 1 as the slice to copy from. [#4728](https://github.com/scalableminds/webknossos/pull/4728)
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/ProjectController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ class ProjectController @Inject()(projectService: ProjectService,
Ok(Json.toJson(js))
}
}

def projectsForTaskType(taskTypeId: String) = sil.SecuredAction.async { implicit request =>
for {
projects <- projectDAO.findAllWithTaskType(taskTypeId) ?~> "project.list.failed"
js <- Fox.serialCombined(projects)(p => projectService.publicWrites(p))
} yield {
Ok(Json.toJson(js))
}
}

def read(projectName: String) = sil.SecuredAction.async { implicit request =>
for {
Expand Down
14 changes: 14 additions & 0 deletions app/models/project/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ class ProjectDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext)
parsed <- Fox.combined(r.toList.map(parse))
} yield parsed

def findAllWithTaskType(taskTypeId: String)(implicit ctx: DBAccessContext): Fox[List[Project]] =
for{
accessQuery <- readAccessQuery
grittaweisheit marked this conversation as resolved.
Show resolved Hide resolved
r <- run(
sql"""select distinct p._id, p._team, p._owner, p.name, p.priority, p.paused, p.expectedTime, p.isBlacklistedFromReport, p.created, p.isDeleted
from webknossos.projects_ p
join webknossos.tasks_ t on t._project = p._id
join webknossos.taskTypes_ tt on t._taskType = tt._id
where tt._id = '#${taskTypeId}'"""
.as[ProjectsRow]
fm3 marked this conversation as resolved.
Show resolved Hide resolved
)
parsed <- Fox.combined(r.toList.map(parse))
} yield parsed

def findOneByName(name: String)(implicit ctx: DBAccessContext): Fox[Project] =
for {
accessQuery <- readAccessQuery
Expand Down
1 change: 1 addition & 0 deletions conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ GET /taskTypes
POST /taskTypes controllers.TaskTypeController.create
DELETE /taskTypes/:id controllers.TaskTypeController.delete(id: String)
GET /taskTypes/:id/tasks controllers.TaskController.listTasksForType(id: String)
GET /taskTypes/:id/projects controllers.ProjectController.projectsForTaskType(id: String)
GET /taskTypes/:id controllers.TaskTypeController.get(id: String)
PUT /taskTypes/:id controllers.TaskTypeController.update(id: String)

Expand Down
7 changes: 7 additions & 0 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ export async function getProjectsWithOpenAssignments(): Promise<Array<APIProject
return responses.map(transformProject);
}

export async function getProjectsForTaskType(taskTypeId: string): Promise<Array<APIProject>> {
const responses = await Request.receiveJSON(`/api/taskTypes/${taskTypeId}/projects`);
assertResponseLimit(responses);

return responses.map(transformProject);
}

export async function getProject(projectName: string): Promise<APIProject> {
const project = await Request.receiveJSON(`/api/projects/${projectName}`);
return transformProject(project);
Expand Down
33 changes: 25 additions & 8 deletions frontend/javascripts/admin/project/project_list_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import type { OxalisState } from "oxalis/store";
import { enforceActiveUser } from "oxalis/model/accessors/user_accessor";
import {
getProjectsWithOpenAssignments,
getProjectsForTaskType,
increaseProjectTaskInstances,
deleteProject,
pauseProject,
resumeProject,
downloadNml,
getTasks,
getTaskType,
} from "admin/admin_rest_api";
import Toast from "libs/toast";
import { handleGenericError } from "libs/error_handling";
Expand All @@ -32,6 +34,7 @@ const { Search } = Input;

type OwnProps = {|
initialSearchValue?: string,
taskTypeId?: string,
|};
type StateProps = {|
activeUser: APIUser,
Expand All @@ -48,6 +51,7 @@ type State = {
searchQuery: string,
isTransferTasksVisible: boolean,
selectedProject: ?APIProjectWithAssignments,
taskTypeName: string,
philippotto marked this conversation as resolved.
Show resolved Hide resolved
};

const persistence: Persistence<State> = new Persistence(
Expand Down Expand Up @@ -85,11 +89,18 @@ class ProjectListView extends React.PureComponent<PropsWithRouter, State> {
}

async fetchData(): Promise<void> {
const projects = await getProjectsWithOpenAssignments();

let projects;
let taskTypeName = null;
if (this.props.taskTypeId) {
projects = await getProjectsForTaskType(this.props.taskTypeId);
taskTypeName = await getTaskType(this.props.taskTypeId);
philippotto marked this conversation as resolved.
Show resolved Hide resolved
} else {
projects = await getProjectsWithOpenAssignments();
}
this.setState({
isLoading: false,
projects: projects.filter(p => p.owner != null),
taskTypeName,
});
}

Expand Down Expand Up @@ -180,19 +191,25 @@ class ProjectListView extends React.PureComponent<PropsWithRouter, State> {
<div className="container TestProjectListView">
<div>
<div className="pull-right">
<Link to="/projects/create">
<Button icon="plus" style={marginRight} type="primary">
Add Project
</Button>
</Link>
{this.props.taskTypeId ? null : (
<Link to="/projects/create">
<Button icon="plus" style={marginRight} type="primary">
Add Project
</Button>
</Link>
)}
<Search
style={{ width: 200 }}
onPressEnter={this.handleSearch}
onChange={this.handleSearch}
value={this.state.searchQuery}
/>
</div>
<h3>Projects</h3>
<h3>
{this.props.taskTypeId
? `Projects for task type ${this.state.taskTypeName}`
: "Projects"}
</h3>
<div className="clearfix" style={{ margin: "20px 0px" }} />
<Spin spinning={this.state.isLoading} size="large">
<Table
Expand Down
5 changes: 5 additions & 0 deletions frontend/javascripts/admin/tasktype/task_type_list_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ class TaskTypeListView extends React.PureComponent<Props, State> {
Tasks
</Link>
<br />
<Link to={`/taskTypes/${taskType.id}/projects`} title="View Projects">
<Icon type="eye-o" />
Projects
</Link>
<br />
<AsyncLink
href="#"
onClick={() => {
Expand Down
7 changes: 7 additions & 0 deletions frontend/javascripts/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,13 @@ class ReactRouter extends React.Component<Props> {
/>
)}
/>
<SecuredRoute
isAuthenticated={isAuthenticated}
path="/taskTypes/:taskTypeId/projects"
render={({ match }: ContextRouter) => (
<ProjectListView taskTypeId={match.params.taskTypeId} />
)}
/>
<SecuredRoute
isAuthenticated={isAuthenticated}
path="/scripts/create"
Expand Down