From 5b5149151f5bf5030999ccd1bc7bc8d62bd73122 Mon Sep 17 00:00:00 2001 From: Christopher Hirt Date: Thu, 24 Oct 2019 12:01:33 +0700 Subject: [PATCH 1/5] Fix CSV Insights and add command-line wrapper (#787) * try and fix project insights given live data * add command-line wrapper for CSV insights tool This adds a light-weight command-line script to output CSV insights directly on the server without a web interface. --- scripts/tools/csvInsights.php | 13 ++++++ .../Model/Shared/Dto/ProjectInsightsDto.php | 46 ++++++++++++++----- 2 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 scripts/tools/csvInsights.php diff --git a/scripts/tools/csvInsights.php b/scripts/tools/csvInsights.php new file mode 100644 index 0000000000..6d6731d02b --- /dev/null +++ b/scripts/tools/csvInsights.php @@ -0,0 +1,13 @@ +#!/usr/bin/php -q + +url = "/app/{$project->appName}/$project->id/"; // owner data - $owner = UserCommands::readUser($project->ownerRef->asString()); + try { + $owner = UserCommands::readUser($project->ownerRef->asString()); + } catch (\Exception $e) { + # there appears to be a dangling owner ref in our data + $owner = [ + 'username' => 'unknown', + 'email' => 'unknown', + 'name' => 'unknown', + 'role' => 'unknown', + ]; + } $projectData->ownerUserName = $owner['username']; $projectData->ownerEmail = $owner['email']; $projectData->ownerName = $owner['name']; @@ -72,11 +82,13 @@ public static function singleProjectInsights($id, $website) { $recentUsers = []; $lastActivityDate = null; foreach ($projectActivity->entries as $event) { - $userId = (string) $event['userRef']; - $users[$userId] = array_key_exists($userId, $users) ? $users[$userId] + 1 : 1; - if (date_create($event['date']) > date_create()->modify('-180 days')) { - $recentUsers[$userId] = true; - }; + if (array_key_exists('userRef', $event)) { + $userId = (string) $event['userRef']; + $users[$userId] = array_key_exists($userId, $users) ? $users[$userId] + 1 : 1; + if (date_create($event['date']) > date_create()->modify('-180 days')) { + $recentUsers[$userId] = true; + }; + } $lastActivityDate = $lastActivityDate === null ? $event['date']->toDateTime() : max($event['date']->toDateTime(), $lastActivityDate); } $projectData->activeUsers = 0; @@ -165,6 +177,22 @@ public static function allProjectInsights($website) } public static function csvInsights($website) { + $filePointer = fopen('php://memory', 'r+'); + self::writeInsightsToCsvFilePointer($website, $filePointer); + rewind($filePointer); + $csv = stream_get_contents($filePointer); + fclose($filePointer); + return $csv; + } + + public static function csvInsightsToFile($website, $filename) { + $filePointer = fopen($filename, 'w'); + $count = self::writeInsightsToCsvFilePointer($website, $filePointer); + fclose($filePointer); + print "Wrote $count insights to CSV file $filename\n"; + } + + private static function writeInsightsToCsvFilePointer($website, $filePointer) { $insights = ProjectInsightsDto::allProjectInsights($website); // convert camelCase properties to sentence case for table headings @@ -175,15 +203,11 @@ public static function csvInsights($website) { } // in order to get automatic escaping of CSV we have to write to a "file" - $filePointer = fopen('php://memory', 'r+'); fputcsv($filePointer, $headings); foreach ($insights->projectList as $row) { fputcsv($filePointer, array_values((array) $row)); } - rewind($filePointer); - $csv = stream_get_contents($filePointer); - fclose($filePointer); - return $csv; + return count($insights->projectList); } private static function appName($website) { From 8486ba569a3732df4b8b726ffd9f95866dff9ac9 Mon Sep 17 00:00:00 2001 From: Christopher Hirt Date: Thu, 24 Oct 2019 12:02:04 +0700 Subject: [PATCH 2/5] changesitename script (#786) * add command line script to change site name This script is intended to be run on the command line in the following way: php changeSiteName.php [run|test] [qa|local] where the first parameter is the run mode (test mode does not change the database) and the second parameter is the target site name, either qa or local, which is for developers * changesitename.php: no updated timestamp --- scripts/tools/changeSiteName.php | 127 ++++++++++++++++++++ src/Api/Model/Shared/Mapper/MapperModel.php | 6 +- 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 scripts/tools/changeSiteName.php diff --git a/scripts/tools/changeSiteName.php b/scripts/tools/changeSiteName.php new file mode 100644 index 0000000000..c6ebfe7875 --- /dev/null +++ b/scripts/tools/changeSiteName.php @@ -0,0 +1,127 @@ +#!/usr/bin/php -q + +read(); + + print "Parsing " . $projectList->count . " projects.\n"; + foreach ($projectList->entries as $projectParams) { // foreach existing project + $projectId = $projectParams['id']; + $project = new ProjectModel($projectId); + $siteName = $project->siteName; + $newSiteName = self::getNewSiteName($target, $siteName); + if ($newSiteName) { + $project->siteName = $newSiteName; + if (!$testMode) { + $project->write(); + } + if (array_key_exists($siteName, $siteNameCount)) { + $siteNameCount[$siteName]++; + } else { + $siteNameCount[$siteName] = 1; + } + } + } + foreach(array_keys($siteNameCount) as $from) { + $count = $siteNameCount[$from]; + print "$count $from projects changed site to " . self::getNewSiteName($target, $from) . "\n"; + } + print "\n"; + + $siteNameCount = array(); + + // loop over every user + $userList = new UserListModel(); + $userList->read(); + $userChangeCount = 0; + print "Parsing " . $userList->count . " users.\n"; + foreach ($userList->entries as $userParams) { + $siteNamesToRemove = array(); + $userId = $userParams['id']; + $user = new UserModel($userId); + foreach ($user->siteRole->getArrayCopy() as $siteName => $role) { + $newSiteName = self::getNewSiteName($target, $siteName); + if ($newSiteName) { + $user->siteRole[$newSiteName] = $role; + $siteNamesToRemove[] = $siteName; + if (array_key_exists($siteName, $siteNameCount)) { + $siteNameCount[$siteName]++; + } else { + $siteNameCount[$siteName] = 1; + } + $userChangeCount++; + } + } + foreach ($siteNamesToRemove as $siteName) { + unset($user->siteRole[$siteName]); + } + if (!$testMode) { + $user->write(); + } + } + print "$userChangeCount users changed\n\n"; + foreach(array_keys($siteNameCount) as $from) { + $count = $siteNameCount[$from]; + print "$count users of $from projects changed site to " . self::getNewSiteName($target, $from) . "\n"; + } + print "\n"; + } +} +if (count($argv) != 3) { + print "Usage:\n" . "php changeSiteName.php [run|test] [qa|local]\n\n" . + "examples:\n\n" . "php changeSiteName.php test local\n - test changes but do not make actual changes. " . + "Change site names to the localhost site available on developer machines\n\n" . + "php changeSiteName.php run qa\n - change the database. change site names to the QA site\n"; + exit; +} + +$mode = $argv[1]; +if ($mode != 'test' && $mode != 'run') { + print "Error: first argument must be either 'test' or 'run' which determines script run mode\n"; + exit; +} + +$target = $argv[2]; +if ($target != 'qa' && $target != 'local') { + print "Error: second argument must be either 'qa' or 'local' which determines the target site\n"; + exit; +} + +ChangeSiteName::run($mode, $target); diff --git a/src/Api/Model/Shared/Mapper/MapperModel.php b/src/Api/Model/Shared/Mapper/MapperModel.php index b2ff87838f..09861ebc83 100644 --- a/src/Api/Model/Shared/Mapper/MapperModel.php +++ b/src/Api/Model/Shared/Mapper/MapperModel.php @@ -90,8 +90,10 @@ public function readByPropertyArrayContains($property, $value) public function write() { CodeGuard::checkTypeAndThrow($this->id, 'Api\Model\Shared\Mapper\Id'); - $now = UniversalTimestamp::now(); - $this->dateModified = $now; + if (! defined('MAPPERMODEL_NO_TIMESTAMP_UPDATE')) { + $now = UniversalTimestamp::now(); + $this->dateModified = $now; + } if (Id::isEmpty($this->id)) { $this->dateCreated = $now; } From 4c3dff04efdae8574ba6eb88cc2fb9daa58581bd Mon Sep 17 00:00:00 2001 From: Christopher Hirt Date: Fri, 25 Oct 2019 12:47:03 +0700 Subject: [PATCH 3/5] fix if statement in MapperModel (#788) * fix if statement in MapperModel Ira spotted a bug after the last PR had already been merged. Moved the $now outside of the if statement since it could cause the variable to be undefined --- src/Api/Model/Shared/Mapper/MapperModel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Api/Model/Shared/Mapper/MapperModel.php b/src/Api/Model/Shared/Mapper/MapperModel.php index 09861ebc83..2cf100efce 100644 --- a/src/Api/Model/Shared/Mapper/MapperModel.php +++ b/src/Api/Model/Shared/Mapper/MapperModel.php @@ -90,8 +90,8 @@ public function readByPropertyArrayContains($property, $value) public function write() { CodeGuard::checkTypeAndThrow($this->id, 'Api\Model\Shared\Mapper\Id'); + $now = UniversalTimestamp::now(); if (! defined('MAPPERMODEL_NO_TIMESTAMP_UPDATE')) { - $now = UniversalTimestamp::now(); $this->dateModified = $now; } if (Id::isEmpty($this->id)) { From 220063e52ecba177e22283a01dcc1ba4c6d9b454 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 3 Mar 2020 15:37:16 +0700 Subject: [PATCH 4/5] User Share: don't fetch invalid avatar URLs See https://docs.angularjs.org/api/ng/directive/ngSrc for why the ng-src attribute is needed here instead of src. --- .../shared/share-with-others/user-management.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/angular-app/languageforge/lexicon/shared/share-with-others/user-management.component.html b/src/angular-app/languageforge/lexicon/shared/share-with-others/user-management.component.html index 2f87d43d95..ae171353fe 100644 --- a/src/angular-app/languageforge/lexicon/shared/share-with-others/user-management.component.html +++ b/src/angular-app/languageforge/lexicon/shared/share-with-others/user-management.component.html @@ -39,7 +39,7 @@
  • - +
    {{user.name}} (you)
    From 4a27902a5a421923e7d35e1705e45eb34ccacaed Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 3 Mar 2020 16:15:35 +0700 Subject: [PATCH 5/5] Fix roles dropdown in user management dialog Now the roles dropdown will no longer be blank and will actually do something when a new role is picked. --- .../share-with-others/role-dropdown.component.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/angular-app/languageforge/lexicon/shared/share-with-others/role-dropdown.component.ts b/src/angular-app/languageforge/lexicon/shared/share-with-others/role-dropdown.component.ts index d49a4ced10..497fc9faef 100644 --- a/src/angular-app/languageforge/lexicon/shared/share-with-others/role-dropdown.component.ts +++ b/src/angular-app/languageforge/lexicon/shared/share-with-others/role-dropdown.component.ts @@ -32,8 +32,15 @@ export class RoleDropdownController implements angular.IController { if (changes.roles) this.buildRoleDetails(); if (changes.selectedRole) { - if (!this.selectedRole) this.selectedRole = this.roles[this.roles.length - 1]; - this.selectedRoleDetail = this.roleDetails.find(p => p.role.key === this.selectedRole.key); + const selectedRole = changes.selectedRole.currentValue || changes.selectedRole; + const selectedRoleDetail = this.roleDetails.find(p => p.role.key === (selectedRole.key || selectedRole)); + if (selectedRoleDetail) { + this.selectedRole = selectedRoleDetail.role; + this.selectedRoleDetail = selectedRoleDetail; + } else { + this.selectedRole = this.roles[this.roles.length - 1]; + this.selectedRoleDetail = this.roleDetails.find(p => p.role.key === this.selectedRole.key); + } } }