{{ humanReadableSiteFieldName(field) }} |
-
-
- Description
+
+
+
+ Description
+
+
+
+
+
+
+
+
-
- {{ mostRecentInformation.description }}
+
+
-
- No description provided...
+
+
+ {{ mostRecentInformation.description }}
+
+
+ No description provided...
+
@@ -157,7 +202,7 @@
-
+
{{ humanReadableSiteFieldName(field) }} |
{{ humanReadableSiteFieldValue(field, information.node[field]) }}
@@ -203,7 +248,7 @@
-
+
{{ humanReadableSiteFieldName(field) }} |
{{ humanReadableSiteFieldValue(field, information.node[field]) }}
@@ -258,6 +303,39 @@ const SITE_MAINTAINERS_QUERY = gql`
}
`;
+const SITE_INFORMATION_QUERY = gql`
+ query($siteid: ID, $after: String) {
+ site(id: $siteid) {
+ id
+ information(after: $after, first: 100) {
+ edges {
+ node {
+ id
+ timestamp
+ description
+ processorVendor
+ processorVendorId
+ processorFamilyId
+ processorModelId
+ processorCacheSize
+ numberLogicalCpus
+ numberPhysicalCpus
+ totalVirtualMemory
+ totalPhysicalMemory
+ processorClockFrequency
+ }
+ }
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ }
+ }
+ }
+`;
+
export default {
components: {
LoadingIndicator,
@@ -276,39 +354,16 @@ export default {
},
},
+ data() {
+ return {
+ editingDescription: false,
+ currentDescription: '',
+ };
+ },
+
apollo: {
site: {
- query: gql`
- query($siteid: ID, $after: String) {
- site(id: $siteid) {
- id
- information(after: $after, first: 100) {
- edges {
- node {
- timestamp
- description
- processorVendor
- processorVendorId
- processorFamilyId
- processorModelId
- processorCacheSize
- numberLogicalCpus
- numberPhysicalCpus
- totalVirtualMemory
- totalPhysicalMemory
- processorClockFrequency
- }
- }
- pageInfo {
- hasNextPage
- hasPreviousPage
- startCursor
- endCursor
- }
- }
- }
- }
- `,
+ query: SITE_INFORMATION_QUERY,
variables() {
return {
siteid: this.siteId,
@@ -331,6 +386,7 @@ export default {
site(id: $siteid) {
id
mostRecentInformation {
+ id
description
processorVendor
processorVendorId
@@ -462,6 +518,8 @@ export default {
const information2_copy = JSON.parse(JSON.stringify(information2));
information1_copy.timestamp = '';
information2_copy.timestamp = '';
+ information1_copy.id = '';
+ information2_copy.id = '';
return JSON.stringify(information1_copy) === JSON.stringify(information2_copy);
},
@@ -593,6 +651,93 @@ export default {
console.error(error);
});
},
+
+ editDescription() {
+ this.editingDescription = true;
+ this.currentDescription = this.mostRecentInformation?.description;
+ },
+
+ cancelEditDescription() {
+ this.editingDescription = false;
+ },
+
+ saveDescription() {
+ this.editingDescription = false;
+
+ this.$apollo.mutate({
+ mutation: gql`mutation ($siteid: ID!, $description: String!) {
+ updateSiteDescription(input: {
+ siteId: $siteid
+ description: $description
+ }) {
+ site {
+ id
+ mostRecentInformation {
+ id
+ timestamp
+ description
+ processorVendor
+ processorVendorId
+ processorFamilyId
+ processorModelId
+ processorCacheSize
+ numberLogicalCpus
+ numberPhysicalCpus
+ totalVirtualMemory
+ totalPhysicalMemory
+ processorClockFrequency
+ }
+ }
+ }
+ }`,
+ variables: {
+ siteid: this.siteId,
+ description: this.currentDescription,
+ },
+ update: (cache, { data: { updateSiteDescription } }) => {
+ const data = JSON.parse(JSON.stringify(cache.readQuery({
+ query: SITE_INFORMATION_QUERY,
+ variables: {
+ siteid: this.siteId,
+ },
+ })));
+ data.site.information.edges.unshift({
+ node: {
+ ...updateSiteDescription.site.mostRecentInformation,
+ },
+ });
+ cache.writeQuery({ query: SITE_INFORMATION_QUERY, data: data });
+ },
+ optimisticResponse: {
+ __typename: 'Mutation',
+ updateSiteDescription: {
+ __typename: 'UnclaimSiteMutationPayload',
+ site: {
+ __typename: 'Site',
+ id: this.siteId,
+ mostRecentInformation: {
+ __typename: 'SiteInformation',
+ id: -1,
+ timestamp: DateTime.now().toISODate(),
+ description: this.currentDescription,
+ processorVendor: this.mostRecentInformation.processorVendor,
+ processorVendorId: this.mostRecentInformation.processorVendorId,
+ processorFamilyId: this.mostRecentInformation.processorFamilyId,
+ processorModelId: this.mostRecentInformation.processorModelId,
+ processorCacheSize: this.mostRecentInformation.processorCacheSize,
+ numberLogicalCpus: this.mostRecentInformation.numberLogicalCpus,
+ numberPhysicalCpus: this.mostRecentInformation.numberPhysicalCpus,
+ totalVirtualMemory: this.mostRecentInformation.totalVirtualMemory,
+ totalPhysicalMemory: this.mostRecentInformation.totalPhysicalMemory,
+ processorClockFrequency: this.mostRecentInformation.processorClockFrequency,
+ },
+ },
+ },
+ },
+ }).catch((error) => {
+ console.error(error);
+ });
+ },
},
};
diff --git a/resources/js/vue/components/UserHomepage.vue b/resources/js/vue/components/UserHomepage.vue
index 8e28f9d3ca..1043dfc47e 100644
--- a/resources/js/vue/components/UserHomepage.vue
+++ b/resources/js/vue/components/UserHomepage.vue
@@ -109,7 +109,7 @@
@@ -229,7 +229,7 @@
|
{{ site.name }}
diff --git a/routes/web.php b/routes/web.php
index 3434a2ec90..5e9068b1c2 100755
--- a/routes/web.php
+++ b/routes/web.php
@@ -238,9 +238,17 @@
Route::get('/manageCoverage.php', 'CoverageController@manageCoverage');
Route::post('/manageCoverage.php', 'CoverageController@manageCoverage');
- // TODO: (williamjallen) send the POST route to a different function
- Route::get('/editSite.php', 'SiteController@editSite');
- Route::post('/editSite.php', 'SiteController@editSite');
+ Route::match(['get', 'post'], '/editSite.php', function (Request $request) {
+ if ($request->has('siteid')) {
+ $siteid = $request->integer('siteid');
+ return redirect("/sites/$siteid", 301);
+ } elseif ($request->has('projectid')) {
+ $projectid = $request->integer('projectid');
+ return redirect("/projects/$projectid/sites", 301);
+ } else {
+ return redirect('/sites', 301);
+ }
+ });
Route::get('/ajax/buildnote.php', 'BuildController@ajaxBuildNote');
diff --git a/tests/Browser/Pages/SitesIdPageTest.php b/tests/Browser/Pages/SitesIdPageTest.php
index 54830d8bb4..967d7aa1ae 100644
--- a/tests/Browser/Pages/SitesIdPageTest.php
+++ b/tests/Browser/Pages/SitesIdPageTest.php
@@ -47,6 +47,11 @@ public function tearDown(): void
$site->delete();
}
$this->sites = [];
+
+ foreach ($this->users as $users) {
+ $users->delete();
+ }
+ $this->users = [];
}
public function testMostRecentSiteDetails(): void
@@ -290,4 +295,81 @@ public function testClaimSiteFunctionality(): void
->waitUntilMissingText("{$this->users['user']->firstname} {$this->users['user']->lastname} ({$this->users['user']->institution})");
});
}
+
+ public function testEditSiteDescription(): void
+ {
+ $this->sites['site1'] = $this->makeSite();
+ $this->sites['site1']->information()->create();
+ $this->users['user'] = $this->makeNormalUser();
+
+ $this->browse(function (Browser $browser) {
+ // We shouldn't see buttons to edit the description if we are logged out
+ $browser->visit("/sites/{$this->sites['site1']->id}")
+ ->whenAvailable('@site-description', function (Browser $browser) {
+ $browser->assertMissing('@cancel-edit-description-button');
+ $browser->assertMissing('@save-description-button');
+ $browser->assertMissing('@edit-description-button');
+ });
+
+ $description1 = Str::uuid()->toString();
+ $description2 = Str::uuid()->toString();
+
+ $browser->loginAs($this->users['user'])
+ ->visit("/sites/{$this->sites['site1']->id}")
+ ->waitFor('@site-description')
+ ->assertMissing('@cancel-edit-description-button')
+ ->assertMissing('@save-description-button')
+ ->assertVisible('@edit-description-button')
+ ->waitForTextIn('@site-description', 'No description provided')
+ ->click('@edit-description-button')
+ ->waitFor('@cancel-edit-description-button')
+ ->assertVisible('@cancel-edit-description-button')
+ ->assertVisible('@save-description-button')
+ ->assertMissing('@edit-description-button')
+ ->assertVisible('@edit-description-textarea')
+ ->assertValue('@edit-description-textarea', '')
+ ->type('@edit-description-textarea', $description1)
+ ->click('@cancel-edit-description-button')
+ ->waitForTextIn('@site-description', 'No description provided')
+ ->assertDontSee($description1)
+ ->assertMissing('@cancel-edit-description-button')
+ ->assertMissing('@save-description-button')
+ ->assertVisible('@edit-description-button')
+ ->click('@edit-description-button')
+ ->assertVisible('@cancel-edit-description-button')
+ ->assertVisible('@save-description-button')
+ ->assertMissing('@edit-description-button')
+ ->assertVisible('@edit-description-textarea')
+ ->assertValue('@edit-description-textarea', '')
+ ->type('@edit-description-textarea', $description1)
+ ->click('@save-description-button')
+ ->waitFor('@edit-description-button')
+ ->assertMissing('@cancel-edit-description-button')
+ ->assertMissing('@save-description-button')
+ ->assertVisible('@edit-description-button')
+ ->assertSeeIn('@site-description', $description1)
+ ->assertSeeIn('[data-test="site-history-item"]:nth-child(1)', $description1)
+ ->refresh()
+ ->waitFor('@site-description')
+ ->assertMissing('@cancel-edit-description-button')
+ ->assertMissing('@save-description-button')
+ ->assertVisible('@edit-description-button')
+ ->waitForTextIn('@site-description', $description1)
+ ->waitForTextIn('[data-test="site-history-item"]:nth-child(1)', $description1)
+ ->click('@edit-description-button')
+ ->assertValue('@edit-description-textarea', $description1)
+ ->clear('@edit-description-textarea')
+ ->type('@edit-description-textarea', $description2)
+ ->click('@save-description-button')
+ ->waitForTextIn('@site-description', $description2)
+ ->waitForTextIn('[data-test="site-history-item"]:nth-child(1)', $description2)
+ ->waitForTextIn('[data-test="site-history-item"]:nth-child(2)', $description1)
+ ->refresh()
+ ->waitFor('@site-description')
+ ->waitForTextIn('@site-description', $description2)
+ ->waitForTextIn('[data-test="site-history-item"]:nth-child(1)', $description2)
+ ->waitForTextIn('[data-test="site-history-item"]:nth-child(2)', $description1)
+ ;
+ });
+ }
}
diff --git a/tests/Feature/GraphQL/Mutations/UpdateSiteDescriptionTest.php b/tests/Feature/GraphQL/Mutations/UpdateSiteDescriptionTest.php
new file mode 100644
index 0000000000..fb55d956f5
--- /dev/null
+++ b/tests/Feature/GraphQL/Mutations/UpdateSiteDescriptionTest.php
@@ -0,0 +1,150 @@
+site = $this->makeSite();
+ $this->user = $this->makeNormalUser();
+ }
+
+ protected function tearDown(): void
+ {
+ $this->site->delete();
+ $this->user->delete();
+
+ parent::tearDown();
+ }
+
+ public function testMutationFailsWhenInvalidSiteId(): void
+ {
+ self::assertEmpty($this->site->information()->get());
+
+ $this->actingAs($this->user)->graphQL('
+ mutation ($siteid: ID!, $description: String!) {
+ updateSiteDescription(input: {
+ siteId: $siteid
+ description: $description
+ }) {
+ site {
+ id
+ }
+ message
+ }
+ }
+ ', [
+ 'siteid' => 123456789,
+ 'description' => Str::uuid()->toString(),
+ ])->assertJson([
+ 'data' => [
+ 'updateSiteDescription' => [
+ 'site' => null,
+ 'message' => 'Requested site not found.',
+ ],
+ ],
+ ], true);
+
+ self::assertEmpty($this->site->information()->get());
+ }
+
+ public function testMutationFailsWhenInvalidUser(): void
+ {
+ self::assertEmpty($this->site->information()->get());
+
+ $this->graphQL('
+ mutation ($siteid: ID!, $description: String!) {
+ updateSiteDescription(input: {
+ siteId: $siteid
+ description: $description
+ }) {
+ site {
+ id
+ }
+ message
+ }
+ }
+ ', [
+ 'siteid' => $this->site->id,
+ 'description' => Str::uuid()->toString(),
+ ])->assertJson([
+ 'data' => [
+ 'updateSiteDescription' => [
+ 'site' => null,
+ 'message' => 'Authentication required to edit site descriptions.',
+ ],
+ ],
+ ], true);
+
+ self::assertEmpty($this->site->information()->get());
+ }
+
+ public function testMutationPreservesPreviousInformation(): void
+ {
+ $this->site->information()->create([
+ 'timestamp' => Carbon::create(2020),
+ 'description' => Str::uuid()->toString(),
+ 'processorclockfrequency' => 1234,
+ ]);
+
+ $new_description = Str::uuid()->toString();
+
+ $this->actingAs($this->user)->graphQL('
+ mutation ($siteid: ID!, $description: String!) {
+ updateSiteDescription(input: {
+ siteId: $siteid
+ description: $description
+ }) {
+ site {
+ id
+ mostRecentInformation {
+ timestamp
+ description
+ processorClockFrequency
+ }
+ }
+ message
+ }
+ }
+ ', [
+ 'siteid' => $this->site->id,
+ 'description' => $new_description,
+ ])->assertJson([
+ 'data' => [
+ 'updateSiteDescription' => [
+ 'site' => [
+ 'id' => (string) $this->site->id,
+ 'mostRecentInformation' => [
+ 'timestamp' => $this->site->mostRecentInformation?->timestamp->toIso8601String(),
+ 'description' => $new_description,
+ 'processorClockFrequency' => 1234,
+ ],
+ ],
+ 'message' => null,
+ ],
+ ],
+ ], true);
+
+ $site_information = $this->site->information()->get();
+ self::assertCount(2, $site_information);
+ self::assertNotEquals($site_information[0]->timestamp ?? null, $site_information[1]->timestamp ?? null);
+ }
+}
| | |