Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Http/Controllers/ViewProjectsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function viewProjects(): View|RedirectResponse
}

return $this->vue('projects-page', 'Projects', [
'is-logged-in' => Auth::check(),
'can-create-projects' => Gate::allows('create', Project::class),
]);
}
Expand Down
125 changes: 93 additions & 32 deletions resources/js/vue/components/ProjectsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
role="tablist"
class="tw-tabs tw-tabs-bordered"
>
<a
v-if="isLoggedIn"
role="tab"
class="tw-tab"
:class="{'tw-tab-active': currentTab === 'MEMBER', 'tw-font-bold': currentTab === 'MEMBER' }"
data-test="member-tab"
@click="currentTab = 'MEMBER'"
>
Member
</a>
<a
role="tab"
class="tw-tab"
Expand Down Expand Up @@ -41,18 +51,11 @@
</div>
<loading-indicator :is-loading="projects === null">
<div
v-if="projects.length === 0 && currentTab === 'ACTIVE'"
class="tw-italic tw-font-medium tw-text-neutral-500"
data-test="no-active-projects-message"
>
No projects with builds in the last 24 hours...
</div>
<div
v-else-if="projects.length === 0 && currentTab === 'ALL'"
v-if="noProjectsMessage !== null"
class="tw-italic tw-font-medium tw-text-neutral-500"
data-test="no-projects-message"
>
No projects to display...
{{ noProjectsMessage }}
</div>
<table
v-else
Expand Down Expand Up @@ -122,9 +125,39 @@ import {
} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';

const PROJECT_LIST_QUERY = `
projects {
edges {
node {
id
name
description
logoUrl
visibility
buildCount(filters: {
gt: {
submissionTime: $countBuildsSince
}
})
mostRecentBuild {
id
startTime
submissionTime
}
}
}
}
`;

export default {
components: {FontAwesomeIcon, ProjectLogo, LoadingIndicator},

props: {
isLoggedIn: {
type: Boolean,
required: true,
},

canCreateProjects: {
type: Boolean,
required: true,
Expand All @@ -133,40 +166,37 @@ export default {

data() {
return {
currentTab: 'ACTIVE', // Options: 'ACTIVE', 'ALL'
currentTab: this.isLoggedIn ? 'MEMBER' : 'ACTIVE', // Options: 'MEMBER', 'ACTIVE', 'ALL'
};
},

apollo: {
allVisibleProjects: {
query: gql`
query allVisibleProjects($countBuildsSince: DateTimeTz!) {
allVisibleProjects: projects {
edges {
node {
id
name
description
logoUrl
visibility
buildCount(filters: {
gt: {
submissionTime: $countBuildsSince
}
})
mostRecentBuild {
id
startTime
submissionTime
}
}
}
allVisibleProjects: ${PROJECT_LIST_QUERY}
}
`,
variables() {
return {
countBuildsSince: this.oneDayAgo,
};
},
},

myProjects: {
query: gql`
query myProjects($countBuildsSince: DateTimeTz!) {
me {
id
${PROJECT_LIST_QUERY}
}
}
`,
update: data => data?.me?.projects,
variables() {
return {
countBuildsSince: DateTime.now().minus({days: 1}).startOf('second').toISO({suppressMilliseconds: true}),
countBuildsSince: this.oneDayAgo,
};
},
},
Expand All @@ -187,14 +217,45 @@ export default {
},

projects() {
const edges = this.allVisibleProjects?.edges.filter(({node: project}) => this.currentTab === 'ALL' || project.buildCount > 0);
let edges;
if (this.currentTab === 'MEMBER') {
edges = this.myProjects?.edges.map(x => x);
}
else if (this.currentTab === 'ACTIVE') {
edges = this.allVisibleProjects?.edges.filter(({node: project}) => project.buildCount > 0);
}
else {
edges = this.allVisibleProjects?.edges.map(x => x);
}

if (edges === null || edges === undefined) {
return null;
}

edges.sort((a, b) => b.node.buildCount - a.node.buildCount);
return edges;
},

noProjectsMessage() {
if (this.projects.length > 0) {
return null;
}

switch (this.currentTab) {
case 'MEMBER':
return 'You are not a member of any projects yet...';
case 'ACTIVE':
return 'No projects with builds in the last 24 hours...';
case 'ALL':
return 'No projects to display...';
default:
return null;
}
},

oneDayAgo() {
return DateTime.now().minus({days: 1}).startOf('second').toISO({suppressMilliseconds: true});
},
},

methods: {
Expand Down
60 changes: 57 additions & 3 deletions tests/Browser/Pages/ProjectsPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public function testShowsMessageWhenNoProjects(): void
$browser->click('@all-tab')
->waitFor('@no-projects-message')
->assertVisible('@no-projects-message')
->assertSee('No projects to display')
->assertMissing('@projects-table')
;
});
Expand All @@ -109,6 +110,7 @@ public function testShowsMessageWhenNoProjects(): void
$browser->click('@all-tab')
->waitFor('@projects-table')
->assertMissing('@no-projects-message')
->assertDontSee('No projects to display')
->assertSeeIn('@projects-table', $this->projects['project1']->name)
;
});
Expand All @@ -129,8 +131,8 @@ public function testShowsMessageWhenNoActiveProjects(): void
$browser->visit('/projects')
->whenAvailable('@projects-page', function (Browser $browser): void {
$browser->click('@active-tab')
->waitFor('@no-active-projects-message')
->assertVisible('@no-active-projects-message')
->waitFor('@no-projects-message')
->assertSee('No projects with builds in the last 24 hours')
->assertMissing('@projects-table')
;
});
Expand All @@ -142,13 +144,65 @@ public function testShowsMessageWhenNoActiveProjects(): void
->whenAvailable('@projects-page', function (Browser $browser): void {
$browser->click('@active-tab')
->waitFor('@projects-table')
->assertMissing('@no-active-projects-message')
->assertMissing('@no-projects-message')
->assertDontSee('No projects with builds in the last 24 hours')
->assertSeeIn('@projects-table', $this->projects['project1']->name)
;
});
});
}

public function testShowsMessageWhenNoMemberProjects(): void
{
$this->users['admin'] = $this->makeAdminUser();
$this->projects['project1'] = $this->makePublicProject();

$this->browse(function (Browser $browser): void {
$browser->loginAs($this->users['admin'])
->visit('/projects')
->whenAvailable('@projects-page', function (Browser $browser): void {
$browser->click('@member-tab')
->waitFor('@no-projects-message')
->assertVisible('@no-projects-message')
->assertSee('You are not a member of any projects yet')
->assertMissing('@projects-table')
;
});

$this->projects['project1']->users()->attach($this->users['admin'], ['role' => Project::PROJECT_USER]);

$browser->loginAs($this->users['admin'])
->visit('/projects')
->whenAvailable('@projects-page', function (Browser $browser): void {
$browser->click('@member-tab')
->waitFor('@projects-table')
->assertMissing('@no-projects-message')
->assertDontSee('You are not a member of any projects yet')
->assertSeeIn('@projects-table', $this->projects['project1']->name)
;
});
});
}

public function testOnlyShowsMemberTabWhenLoggedIn(): void
{
$this->users['admin'] = $this->makeAdminUser();
$this->projects['project1'] = $this->makePublicProject();

$this->browse(function (Browser $browser): void {
$browser->visit('/projects')
->whenAvailable('@projects-page', function (Browser $browser): void {
$browser->assertMissing('@member-tab');
});

$browser->loginAs($this->users['admin'])
->visit('/projects')
->whenAvailable('@projects-page', function (Browser $browser): void {
$browser->assertVisible('@member-tab');
});
});
}

public function testSortsProjectsByNumberOfBuildsInLastDay(): void
{
$this->projects['project1'] = $this->makePublicProject();
Expand Down