UNCLASSIFIED

Commit b9bd74c6 authored by graham.smith's avatar graham.smith
Browse files

Merge branch 'BULL-1012' into 'master'

Bull 1012 | New Feature: My Projects Card View

See merge request !139
parents b3f8835a 66d8f55c
......@@ -25,7 +25,13 @@
and star the projects that you are interested in.</v-card-text
>
</v-card>
<v-expansion-panels accordion multiple hover flat v-else>
<v-expansion-panels
accordion
multiple
hover
flat
v-else-if="this.panelView === true"
>
<v-expansion-panel
v-for="(project, index) in filteredProjects"
:key="index"
......@@ -231,6 +237,93 @@
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<v-row class="justify-content-center" v-else-if="this.cardView === true">
<v-col
v-for="(project, index) in filteredProjects"
lg="4"
:key="index"
:project="project"
:cols="12"
>
<v-card class="card">
<div
v-bind:class="{
'project-card-header-failed':
project.latestPipeline.status === 'failed',
'project-card-header-passed':
project.latestPipeline.status === 'passed',
}"
>
<v-card-title class="justify-content-center project-card-title">
<div>
<p>
{{ project.name }}
</p>
<v-row class="justify-content-center">
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
:href="project.links.repo"
target="_blank"
v-bind="attrs"
v-on="on"
@click.native.stop
color="primary"
>
<v-icon small>$vuetify.icons.gitlab</v-icon>
</v-btn>
</template>
<span>GitLab repository</span>
</v-tooltip>
</v-row>
</div>
</v-card-title>
</div>
<v-card-actions>
<v-layout row justify-center class="project-card-detail">
<v-card-text v-if="project.latestPipeline">
<div class="project-card-pipeline-name">
Pipeline: {{ project.latestPipeline.name }}
</div>
</v-card-text>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<span v-bind="attrs" v-on="on">
<GitLabIcon
v-if="
project.latestPipeline.status === 'running' ||
project.latestPipeline.status === 'pending'
"
class="status-icon"
:status="'running'"
/>
<GitLabIcon
v-if="project.latestPipeline.status === 'failed'"
class="status-icon"
:status="'failed'"
/> </span
></template>
<span>
Latest Pipeline: {{ project.latestPipeline.status }}
</span>
</v-tooltip>
<router-link
class="project-title"
target="_blank"
v-for="(
job, jIndex
) in retrieveFailedProjectJobsFromFailedPipeline(project)"
:key="jIndex"
:to="job.link"
>
<a :href="job.link" target="_blank">{{ job.name }}; </a>
</router-link>
</v-layout>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-skeleton-loader>
</v-container>
</template>
......@@ -258,6 +351,8 @@ export default {
},
data: () => ({
loading: false,
panelView: false,
cardView: true,
emptyString: false,
error: false,
errorMessage: "",
......@@ -286,6 +381,7 @@ export default {
this.emptyString = false;
try {
const projects = await ProjectService.getProjectsSummary();
this.failedProjectJobs = this.projects;
if (projects) {
this.setProjects(projects);
}
......@@ -301,6 +397,21 @@ export default {
this.setProjectLoading(false);
}
},
setCardView() {
this.panelView = false;
this.cardView = true;
},
setPanelView() {
this.panelView = true;
this.cardView = false;
},
retrieveFailedProjectJobsFromFailedPipeline(project) {
if (project.latestPipeline.status === "failed") {
return (project.latestPipeline.jobs || []).filter(
(o) => o.status === "failed"
);
}
},
},
computed: {
filteredProjects() {
......@@ -330,6 +441,32 @@ export default {
.status-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}
.project-card-header-passed {
background-color: map-get($material-light, "background-accent-1");
color: map-get($material-light, "secondary-text-color");
fill: map-get($material-light, "secondary-text-color");
}
.project-card-header-failed {
background-color: #ff3838;
color: map-get($material-light, "secondary-text-color");
fill: map-get($material-light, "secondary-text-color");
}
.project-card-title {
padding-bottom: 20px;
}
.project-card-detail {
padding-top:30px;
padding-bottom: 45px;
padding-left:45px;
padding-right:45px;
}
.card {
min-height: 100%;
min-width: 100%;
max-width: 400px;
margin: auto;
}
}
</style>
......@@ -20,6 +20,7 @@
</template>
<span>Refresh</span>
</v-tooltip>
<slot name="header-bar-icons"> </slot>
<v-btn
v-if="!hideCollapse && !hideCollapseButton"
icon
......
......@@ -11,6 +11,38 @@
:loading="loadingProjectData"
:refreshClick="refreshProjectData"
>
<div
slot="header-bar-icons"
:cardViewProjects="cardViewProjects"
:panelViewProjects="panelViewProjects"
>
<v-tooltip top >
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
v-bind="attrs"
v-on="on"
@click="cardViewProjects"
>
<v-icon>mdi-view-grid</v-icon>
</v-btn>
</template>
<span>Card View</span>
</v-tooltip>
<v-tooltip top >
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
v-bind="attrs"
v-on="on"
@click="panelViewProjects"
>
<v-icon>mdi-reorder-horizontal</v-icon>
</v-btn>
</template>
<span>Panel View</span>
</v-tooltip>
</div>
<ProjectsSummary
slot="content"
ref="projectSummary"
......@@ -41,6 +73,12 @@ export default {
refreshProjectData() {
this.$refs.projectSummary.refreshProjects();
},
cardViewProjects() {
this.$refs.projectSummary.setCardView();
},
panelViewProjects() {
this.$refs.projectSummary.setPanelView();
},
setProjectLoading(loading) {
this.loadingProjectData = loading;
},
......
......@@ -59,6 +59,39 @@
:refreshClick="refreshProjectData"
headerTitle="My Project Summary"
>
<div
class="align-items-center d-flex "
slot="header-bar-icons"
:cardViewProjects="cardViewProjects"
:panelViewProjects="panelViewProjects"
>
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
v-bind="attrs"
v-on="on"
@click="cardViewProjects"
>
<v-icon>mdi-view-grid</v-icon>
</v-btn>
</template>
<span>Card View</span>
</v-tooltip>
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
v-bind="attrs"
v-on="on"
@click="panelViewProjects"
>
<v-icon>mdi-reorder-horizontal</v-icon>
</v-btn>
</template>
<span>Panel View</span>
</v-tooltip>
</div>
<span slot="userinfo">
<TutorialTooltip tooltipName="projects">
<span id="projectsTutorialTooltip"></span>
......@@ -135,6 +168,12 @@ export default {
refreshProjectData() {
this.$refs.projectSummary.refreshProjects();
},
cardViewProjects() {
this.$refs.projectSummary.setCardView();
},
panelViewProjects() {
this.$refs.projectSummary.setPanelView();
},
refreshCurriculumSchedule() {
this.$refs.curriculumSchedule.refreshCurriculumSchedule();
},
......
......@@ -35,6 +35,101 @@ describe("ProjectsSummary", () => {
expect(wrapper.find(".error").exists()).toBe(false);
});
it("should set panelView true and cardView false", () => {
ProjectService.getProjectsSummary = jest.fn();
// render the component
const wrapper = shallowMount(ProjectsSummary, {
mocks: {
$store: {
state: {
error: {},
projects: {
list: [],
},
},
commit: jest.fn(),
},
},
data() {
return {
cardView: true,
panelView: false,
};
},
localVue,
vuetify,
});
wrapper.vm.setPanelView();
expect(
wrapper.vm.panelView === true && wrapper.vm.cardView === false
).toBeTruthy();
});
it("should set panelView false and cardView true", () => {
ProjectService.getProjectsSummary = jest.fn();
// render the component
const wrapper = shallowMount(ProjectsSummary, {
mocks: {
$store: {
state: {
error: {},
projects: {
list: [],
},
},
commit: jest.fn(),
},
},
data() {
return {
cardView: true,
panelView: false,
};
},
localVue,
vuetify,
});
wrapper.vm.setCardView();
expect(
wrapper.vm.panelView === false && wrapper.vm.cardView === true
).toBeTruthy();
});
it("should return only failed jobs from a failed pipeline given a project", () => {
ProjectService.getProjectsSummary = jest.fn();
// render the component
const wrapper = shallowMount(ProjectsSummary, {
mocks: {
$store: {
state: {
error: {},
projects: {
list: [],
},
},
commit: jest.fn(),
},
},
localVue,
vuetify,
});
const failedProjectJobs =
wrapper.vm.retrieveFailedProjectJobsFromFailedPipeline({
id: "1",
links: {},
latestPipeline: {
name: "testPipeline",
status: "failed",
latestPipeline: "",
message: "test",
jobs: [
{ name: "e2e-test", status: "failed", link: "" },
{ name: "e2e-test2", status: "passed", link: "" },
],
},
favorite: true,
});
expect(failedProjectJobs.length === 1).toBeTruthy();
});
it("should filter by favorited projects", () => {
ProjectService.getProjectsSummary = jest.fn();
// render the component
......
......@@ -27,6 +27,18 @@ describe("Projects", () => {
wrapper.vm.refreshProjectData();
expect(wrapper.vm.$refs.projectSummary.refreshProjects).toHaveBeenCalled();
});
it("should call setCardView", async () => {
const wrapper = shallowMount(Projects, { store, localVue });
wrapper.vm.$refs.projectSummary.setCardView = jest.fn();
wrapper.vm.cardViewProjects();
expect(wrapper.vm.$refs.projectSummary.setCardView).toHaveBeenCalled();
});
it("should call setPanelView", async () => {
const wrapper = shallowMount(Projects, { store, localVue });
wrapper.vm.$refs.projectSummary.setPanelView = jest.fn();
wrapper.vm.panelViewProjects();
expect(wrapper.vm.$refs.projectSummary.setPanelView).toHaveBeenCalled();
});
it("should call setProjectLoading", async () => {
const wrapper = shallowMount(Projects, { store, localVue });
wrapper.vm.setProjectLoading(true);
......
......@@ -67,7 +67,58 @@ describe("LaunchboardUser", () => {
};
expect(wrapper.vm.showWelcomeMessage).toBe(true);
});
it("should call setCardView", async () => {
const store = new Vuex.Store({
state: {
user: {
user: { name: "mock user", permission: Permission.USER },
},
userPreferences: {
userPreference: {
darkMode: true,
welcomeMessage: true,
},
},
},
});
const wrapper = shallowMount(LaunchboardUser, {
mocks: {
$vuetify: { breakpoint: { xsOnly: true } },
},
vuetify,
store,
localVue,
});
wrapper.vm.$refs.projectSummary.setCardView = jest.fn();
wrapper.vm.cardViewProjects();
expect(wrapper.vm.$refs.projectSummary.setCardView).toHaveBeenCalled();
});
it("should call setPanelView", async () => {
const store = new Vuex.Store({
state: {
user: {
user: { name: "mock user", permission: Permission.USER },
},
userPreferences: {
userPreference: {
darkMode: true,
welcomeMessage: true,
},
},
},
});
const wrapper = shallowMount(LaunchboardUser, {
mocks: {
$vuetify: { breakpoint: { xsOnly: true } },
},
vuetify,
store,
localVue,
});
wrapper.vm.$refs.projectSummary.setPanelView = jest.fn();
wrapper.vm.panelViewProjects();
expect(wrapper.vm.$refs.projectSummary.setPanelView).toHaveBeenCalled();
});
it("should refresh project data", async () => {
const store = new Vuex.Store({
state: {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment