diff --git a/CHANGELOG.md b/CHANGELOG.md index 3483f1d892..c8c2ab5b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + ## Unreleased ### Added +- enrich commit mentions in markdown viewer by internal links ([#1210](https://github.com/scm-manager/scm-manager/pull/1210)) + +### Changed +- Checkboxes can now be 'indeterminate' ([#1215](https://github.com/scm-manager/scm-manager/pull/1215)) + +### Fixed +- Fixed installation of debian packages on distros without preinstalled `at` ([#1216](https://github.com/scm-manager/scm-manager/issues/1216) and [#1217](https://github.com/scm-manager/scm-manager/pull/1217)) +- Fixed broken migration with empty security.xml ([#1219](https://github.com/scm-manager/scm-manager/issues/1219) and [#1221](https://github.com/scm-manager/scm-manager/pull/1221)) +- Mercurial on Python 3 + +## [2.1.1] - 2020-06-23 +### Fixed +- Wait until recommended java installation is available for deb packages ([#1209](https://github.com/scm-manager/scm-manager/pull/1209)) +- Do not force java home of recommended java dependency for rpm and deb packages ([#1195](https://github.com/scm-manager/scm-manager/issues/1195) and [#1208](https://github.com/scm-manager/scm-manager/pull/1208)) +- Migration of non-bare repositories ([#1213](https://github.com/scm-manager/scm-manager/pull/1213)) + +## [2.1.0] - 2020-06-18 +### Added +- Option to configure jvm parameter of docker container with env JAVA_OPTS or with arguments ([#1175](https://github.com/scm-manager/scm-manager/pull/1175)) +- Added links in diff views to expand the gaps between "hunks" ([#1178](https://github.com/scm-manager/scm-manager/pull/1178)) +- Show commit contributors in table on changeset details view ([#1169](https://github.com/scm-manager/scm-manager/pull/1169)) +- Show changeset parents on changeset details view ([#1189](https://github.com/scm-manager/scm-manager/pull/1189)) +- Annotate view to display commit metadata for each line of a file ([#1196](https://github.com/scm-manager/scm-manager/pull/1196)) + +### Fixed +- Avoid caching of detected browser language ([#1176](https://github.com/scm-manager/scm-manager/pull/1176)) +- Fixes configuration of jetty listener address with system property `jetty.host` ([#1173](https://github.com/scm-manager/scm-manager/pull/1173), [#1174](https://github.com/scm-manager/scm-manager/pull/1174)) +- Fixes loading plugin bundles with context path `/` ([#1182](https://github.com/scm-manager/scm-manager/pull/1182/files), [#1181](https://github.com/scm-manager/scm-manager/issues/1181)) +- Sets the new plugin center URL once ([#1184](https://github.com/scm-manager/scm-manager/pull/1184)) +- Diffs with CR characters are parsed correctly ([#1185](https://github.com/scm-manager/scm-manager/pull/1185)) +- Close file lists in migration ([#1191](https://github.com/scm-manager/scm-manager/pull/1191)) +- Use command in javahg.py from registrar (Upgrade to newer javahg version) ([#1192](https://github.com/scm-manager/scm-manager/pull/1192)) +- Fixed wrong e-tag format ([sdorra/web-resource #1](https://github.com/sdorra/web-resources/pull/1)) +- Fixed refetching loop for non existing changesets ([#1203](https://github.com/scm-manager/scm-manager/pull/1203)) +- Fixed active state of sub navigation items, which are using activeWhenMatch ([#1199](https://github.com/scm-manager/scm-manager/pull/1199)) +- Handles repositories in custom directories correctly in migration from 1.x ([#1201](https://github.com/scm-manager/scm-manager/pull/1201)) +- Usage of short git commit ids in changeset urls ([#1200](https://github.com/scm-manager/scm-manager/pull/1200)) +- Fixes linebreaks in multiline tooltip ([#1207](https://github.com/scm-manager/scm-manager/pull/1207)) + +## [2.0.0] - 2020-06-04 +### Added - Detect renamed files in git and hg diffs ([#1157](https://github.com/scm-manager/scm-manager/pull/1157)) - ClassLoader and Adapter parameters to typed store apis ([#1111](https://github.com/scm-manager/scm-manager/pull/1111)) - Native packaging for Debian, Red Hat, Windows, Unix, Docker and Kubernetes ([#1165](https://github.com/scm-manager/scm-manager/pull/1165)) @@ -15,7 +57,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Correctly resolve Links in markdown files ([#1152](https://github.com/scm-manager/scm-manager/pull/1152)) - Missing copy on write in the data store ([#1155](https://github.com/scm-manager/scm-manager/pull/1155)) - Resolved conflicting dependencies for scm-webapp ([#1159](https://github.com/scm-manager/scm-manager/pull/1159)) -- Mercurial on Python 3 ## [2.0.0-rc8] - 2020-05-08 ### Added @@ -169,3 +210,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.0-rc5]: https://github.com/scm-manager/scm-manager/releases/tag/2.0.0-rc5 [2.0.0-rc6]: https://github.com/scm-manager/scm-manager/releases/tag/2.0.0-rc6 [2.0.0-rc7]: https://github.com/scm-manager/scm-manager/releases/tag/2.0.0-rc7 +[2.0.0-rc8]: https://github.com/scm-manager/scm-manager/releases/tag/2.0.0-rc8 +[2.0.0]: https://github.com/scm-manager/scm-manager/releases/tag/2.0.0 diff --git a/Jenkinsfile b/Jenkinsfile index e424e62f95..688930d227 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -83,10 +83,6 @@ node('docker') { stage('SonarQube') { def sonarQube = new SonarCloud(this, [sonarQubeEnv: 'sonarcloud.io-scm', sonarOrganization: 'scm-manager', integrationBranch: 'develop']) sonarQube.analyzeWith(mvn) - - if (!waitForQualityGateWebhookToBeCalled()) { - currentBuild.result = 'UNSTABLE' - } } if (isBuildSuccessful() && (isMainBranch() || isReleaseBranch())) { @@ -224,18 +220,6 @@ boolean isMainBranch() { return mainBranch.equals(env.BRANCH_NAME) } -boolean waitForQualityGateWebhookToBeCalled() { - boolean isQualityGateSucceeded = true - timeout(time: 10, unit: 'MINUTES') { // Needed when there is no webhook for example - def qGate = waitForQualityGate() - echo "SonarQube Quality Gate status: ${qGate.status}" - if (qGate.status != 'OK') { - isQualityGateSucceeded = false - } - } - return isQualityGateSucceeded -} - void withGPGEnvironment(def closure) { withCredentials([ file(credentialsId: 'oss-gpg-secring', variable: 'GPG_KEYRING'), diff --git a/README.md b/README.md index 543956310a..0a7fd405e6 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ repositories over http. - Very easy installation - No need to hack configuration files, SCM-Manager is completely - configureable from its Web-Interface -- No Apache and no database installation is required + configurable from its Web-Interface +- No Apache and no database installation required - Central user, group and permission management - Out of the box support for Git, Mercurial and Subversion - Full RESTFul Web Service API (JSON and XML) @@ -19,34 +19,32 @@ repositories over http. - Useful plugins available - Licensed under the MIT-License -This branch (default) is for the development of SCM-Manager 2.x. If you are interested in the development of version 1.x, please checkout the 1.x branch. +This branch (`develop`) is for the development of SCM-Manager 2.x. If you are interested in the development of version +1.x, please checkout the branch `support/1.x`. ## News -- **2018-09-25** - [SCM-Manager 2 gets a boost by Cloudogu - GmbH](https://www.scm-manager.org/scm-manager-2/scm-manager-2-gets-a-boost-by-cloudogu-gmbh/) -- **2018-05-04** - SCM-Manager 1.60 released - ([download](http://www.scm-manager.org/download/) \| - [release notes](release-notes.md)) -- **2018-04-11** - SCM-Manager 1.59 released - -[All news](http://www.scm-manager.org/news/) +All news regarding SCM-Manager will be published in our [blog](https://www.scm-manager.org/blog/). ## Mailing List - - [archive](http://groups.google.com/group/scmmanager) \| - [subscribe](mailto:scmmanager+subscribe@googlegroups.com) - \| + [subscribe](mailto:scmmanager+subscribe@googlegroups.com) \| [unsubscribe](mailto:scmmanager+unsubscribe@googlegroups.com) ## Documentation -You can find the complete documentation in the [docs/](docs/Home.md) directory. +You can find the complete documentation on our [homepage](https://www.scm-manager.org/docs/). ## Need help? -Looking for more guidance? Full documentation lives [in the SCM-Manager repository](https://github.com/scm-manager/scm-manager/blob/develop/docs/Home.md). Do you have further ideas or need support? +Looking for more guidance? Full documentation lives on our [homepage](https://www.scm-manager.org/docs/) or the +dedicated pages for our [plugins](https://www.scm-manager.org/plugins/). Do you have further ideas or need support? -- **Community Support** - Contact the SCM-Manager support team for questions about SCM-Manager, to report bugs or to request features through the official channels. [Find more about this here](https://www.scm-manager.org/support/). +- **Community Support** - Contact the SCM-Manager support team for questions about SCM-Manager, to report bugs or to + request features through the official channels. [Find more about this here](https://www.scm-manager.org/support/). -- **Enterprise Support** - Do you require support with the integration of SCM-Manager into your processes, with the customization of the tool or simply a service level agreement (SLA)? **Contact our development partner Cloudogu! Their team is looking forward to discussing your individual requirements with you and will be more than happy to give you a quote.** [Request Enterprise Support](https://cloudogu.com/en/scm-manager-enterprise/). +- **Enterprise Support** - Do you require support with the integration of SCM-Manager into your processes, with the + customization of the tool or simply a service level agreement (SLA)? **Contact our development partner Cloudogu! + Their team is looking forward to discussing your individual requirements with you and will be more than happy to + give you a quote.** [Request Enterprise Support](https://cloudogu.com/en/scm-manager-enterprise/). diff --git a/docs/de/index.md b/docs/de/index.md new file mode 100644 index 0000000000..4f56403336 --- /dev/null +++ b/docs/de/index.md @@ -0,0 +1,14 @@ +--- +title: Dokumentation +subtitle: SCM-Manager Dokumentation +--- +Diese Dokumentation beschreibt die Verwaltung und Verwendung vom SCM-Manager. Sie steht in verschiedenen Sprachen und Versionen zur Verfügung, die im Menü rechts ausgewählt werden können. + +Der SCM-Manager 2 ist ein extrem leichtgewichtiges Source Code Management-Tool und kann über eine Vielzahl von Plugins individuell erweitert werden. +Die Dokumentation zum SCM-Manager ist in fünf verschiedene Kategorien unterteilt: + +- Der Abschnitt "Erste Schritte" beschreibt die Installation und die Konfiguration des SCM-Managers und beantwortet häufig gestellte Fragen. +- Der Bereich "Administration" handelt von Themen, die beim Betreiben einer Instanz von Belangen sind. +- Unter der Rubrik "Entwicklung" werden alle Informationen zusammengefasst, die für die Weiterentwicklung des SCM-Managers relevant sind. Hierbei wird beschrieben, wie der SCM-Manager gebaut werden kann und was es dabei zu Beachten gilt. +- Der Bereich "Pluginentwicklung" legt den Fokus auf den Prozess eben dieser. +- Unter dem Abschnitt "Anwenderorientiert" werden aus Anwendersicht die Funktionen des SCM-Managers in der Basis-Variante mit dem Mindest-Set an Plugins beschrieben. Die Features der optionalen Plugins werden in separaten Dokumentationen vorgestellt. diff --git a/docs/de/navigation.yml b/docs/de/navigation.yml new file mode 100644 index 0000000000..bf2e185da2 --- /dev/null +++ b/docs/de/navigation.yml @@ -0,0 +1,6 @@ +- section: Anwenderorientiert + entries: + - /user/repo/ + - /user/user/ + - /user/group/ + - /user/admin/ diff --git a/docs/de/user/admin/assets/administration-information.png b/docs/de/user/admin/assets/administration-information.png new file mode 100644 index 0000000000..1050025588 Binary files /dev/null and b/docs/de/user/admin/assets/administration-information.png differ diff --git a/docs/de/user/admin/assets/administration-permissionRoles.png b/docs/de/user/admin/assets/administration-permissionRoles.png new file mode 100644 index 0000000000..410a033f53 Binary files /dev/null and b/docs/de/user/admin/assets/administration-permissionRoles.png differ diff --git a/docs/de/user/admin/assets/administration-plugins-available.png b/docs/de/user/admin/assets/administration-plugins-available.png new file mode 100644 index 0000000000..d34547cc46 Binary files /dev/null and b/docs/de/user/admin/assets/administration-plugins-available.png differ diff --git a/docs/de/user/admin/assets/administration-plugins-installed.png b/docs/de/user/admin/assets/administration-plugins-installed.png new file mode 100644 index 0000000000..180d5f1c9c Binary files /dev/null and b/docs/de/user/admin/assets/administration-plugins-installed.png differ diff --git a/docs/de/user/admin/assets/administration-settings-general.png b/docs/de/user/admin/assets/administration-settings-general.png new file mode 100644 index 0000000000..3be3e6b715 Binary files /dev/null and b/docs/de/user/admin/assets/administration-settings-general.png differ diff --git a/docs/de/user/admin/index.md b/docs/de/user/admin/index.md new file mode 100644 index 0000000000..0bbe66fe9d --- /dev/null +++ b/docs/de/user/admin/index.md @@ -0,0 +1,14 @@ +--- +title: Administration +partiallyActive: true +--- +Im Bereich Administration kann die SCM-Manager Instanz administriert werden. Von hier können Plugins verwaltet, Berechtigungsrollen festgelegt und angepasst, sowie Einstellungen vorgenommen werden. + +* [Plugins](plugins/) +* [Berechtigungsrollen](roles/) +* [Einstellungen](settings/) + +### Information +Auf der Informationsseite in der Administration findet man die aktuelle Version der SCM-Manager Instanz und hilfreiche Links zur Kontaktaufnahme mit dem SCM-Manager Support-Team. + +![Administration-Information](assets/administration-information.png) diff --git a/docs/de/user/admin/plugins.md b/docs/de/user/admin/plugins.md new file mode 100644 index 0000000000..10714fafb6 --- /dev/null +++ b/docs/de/user/admin/plugins.md @@ -0,0 +1,19 @@ +--- +title: Administration +subtitle: Plugins +--- +Unter dem Eintrag "Plugins" können mithilfe des externen Plugin-Centers Plugins für den SCM Manager verwaltet werden. Die Plugins werden nach installierten und verfügbaren Plugins unterschieden und nach Funktionsschwerpunkt wie bspw. Workflow oder Authentifizierung gruppiert. + +Die Plugins können über Funktions-Icons auf den Kacheln verwaltet werden. Systemrelevante Plugins, die der SCM-Manager selbst liefert, können weder deinstalliert noch aktualisiert werden. + +Damit Änderungen der Plugins wirksam werden, muss der SCM-Manager-Server neugestartet werden. Das kann nach jeder einzelnen Aktion erfolgen. Es ist aber auch möglich viele unterschiedliche Aktionen wie Installieren, Aktualisieren und Löschen in eine Warteschlange einzureihen und alle Aktionen mit einem einzigen Neustart auszuführen. Wird eine Aktion (Installieren, Deinstallieren, Aktualisieren) für ein Plugin ausgewählt, erscheinen die Schaltflächen "Änderungen ausführen" und "Änderungen abbrechen". Über "Änderungen ausführen" öffnet sich ein Pop-Up Fenster, indem die aktuelle Warteschlange (alle ausgeführten Aktionen ohne Neustart) angezeigt werden. Der Anwender hat nun die Möglichkeit zu entscheiden, ob die Änderungen durch einen Neustart ausgeführt werden sollen. Falls Aktionen, die sich bereits in der Warteschlange befinden nicht mehr erwünscht sind, kann die gesamte Warteschlange über den Button "Änderungen abbrechen" verworfen werden. + +### Installiert +Auf der Übersicht für installierte Plugins werden alle auf der SCM-Manager Instanz installierten Plugins angezeigt. Optionale Plugins können hier deinstalliert und aktualisiert werden. + +![Administration-Plugins-Installed](assets/administration-plugins-installed.png) + +### Verfügbar +Auf der Übersicht der verfügbaren Plugins werden alle kompatiblen Plugins, die über das SCM-Plugin-Center zur Verfügung stehen, aufgeführt. Die Plugins können über den Download-Icon heruntergeladen und mit einem Neustart des SCM-Manager-Servers installiert werden. + +![Administration-Plugins-Available](assets/administration-plugins-available.png) diff --git a/docs/de/user/admin/roles.md b/docs/de/user/admin/roles.md new file mode 100644 index 0000000000..54c78f0c02 --- /dev/null +++ b/docs/de/user/admin/roles.md @@ -0,0 +1,11 @@ +--- +title: Administration +subtitle: Berechtigungsrollen +--- +Auf der Seite "Berechtigungsrollen" können unterschiedliche Profile mit Berechtigungen erstellt werden. Einer Rolle werden ein Name und ein Satz an Berechtigungen zugewiesen. Diese Rolle kann dann Nutzern und Gruppen für einzelne Repositories zugewiesen werden. + +Es gibt vordefinierte System-Rollen, die nicht verändert oder gelöscht werden können. + +Durch einen Klick auf eine Rolle sieht man die Informationen zur Rolle und welche Berechtigungen dieser Rolle zugewiesen sind. Benutzerdefinierte Rollen können nachträglich editiert und gelöscht werden. + +![Administration-PermissionRoles](assets/administration-permissionRoles.png) diff --git a/docs/de/user/admin/settings.md b/docs/de/user/admin/settings.md new file mode 100644 index 0000000000..d26d8af3a2 --- /dev/null +++ b/docs/de/user/admin/settings.md @@ -0,0 +1,59 @@ +--- +title: Administration +subtitle: Einstellungen +displayToc: true +--- +In den Einstellungen findet die globale Konfiguration der SCM-Manager Instanz statt. Viele SCM-Manager-Plugins, die sich konfigurieren lassen, erhalten nach ihrer Installation einen Eintrag unter Einstellungen. Dadurch ist er nach Bedarf maximal konfigurierbar. + +### Generell +#### Realm Beschreibung +Im Feld "Realm Beschreibung" kann definiert werden, welcher Authentication Realm für die Anmeldung mit Basic Authentication verwendet wird. + +#### Namespace Strategien +Im SCM-Manager werden die Repositories nach Namespaces gruppiert. Diese Gruppierung kann nach unterschiedlichen Strategien stattfinden: + +* Benutzername: Der Benutzername des angemeldeten Benutzers wird verwendet +* Aktuelles Jahr: Das aktuelle Jahr wird verwendet +* Repository Typ: Der Typ (git, hg oder svn) wird verwendet +* Benutzerdefiniert: Der Namespace kann beim Erstellen des Repositories frei gewählt werden + +#### Login Info URL +Auf der Login-Seite des SCM-Managers werden hilfreiche Plugins und Features vorgestellt. Falls man eigene Informationen auf die Login-Seite abbilden möchte, kann diese URL ausgetauscht werden. + +#### XSRF Protection aktivieren +Um Angriffe auf den SCM-Manager mit Cross Site Scripting (XSS / XSRF) zu erschweren. Dieses Feature ist noch experimentell. + +#### Plugin-Center-URL +Der SCM-Manager kann ein Plugin-Center anbinden, um schnell und bequem Plugins verwalten zu können. Um ein anderes SCM-Plugin-Center als das vorkonfigurierte zu verwenden, reicht es aus diese URL zu ändern. Läuft der SCM-Manager im Cloudogu EcoSystem kann die Plugin Center URL über einen Eintrag im etcd gesetzt werden. + +#### Anonyme Zugriff erlauben +Der SCM-Manager 2 hat das Konzept für anonyme Zugriffe über einen "_anonymous"-Benutzer realisiert. Beim Aktivieren des anonymen Zugriffs wird ein neuer Benutzer erstellt mit dem Namen "_anonymous". Dieser Nutzer kann wie ein gewöhnlicher Benutzer für unterschiedliche Aktionen berechtigt werden. Bei einem Zugriff auf den SCM-Manager ohne Zugangsdaten (gilt nicht für die Web-Oberflächen) wird dieser anonyme Benutzer verwendet. + +Beispiel: Falls der anonyme Zugriff aktiviert ist und der "_anonymous"-Benutzer volle Zugriffsrechte auf ein bestimmtes Git-Repository hat, kann jeder über eine Kommandozeile mit den klassischen Git-Befehlen ohne Zugangsdaten auf dieses Repository zugreifen. Zugriffe über SSH werden aktuell nicht unterstützt. + +#### Anmeldeversuche +Es lässt sich konfigurieren wie häufig sich ein Benutzer falsch anmelden darf, bevor dessen Benutzerkonto gesperrt wird. Der Zähler für fehlerhafte Anmeldeversuche wird nach einem erfolgreichen Login zurückgesetzt. Man kann dieses Feature abschalten, indem man "-1" in die Konfiguration einträgt. + +#### Timeout bei fehlgeschlagenen Anmeldeversuchen +Falls konfiguriert werden Benutzerkonten bei zu vielen fehlgeschlagenen Anmeldeversuche gesperrt. Über einen Timeout kann gesteuert werden, wie lange diese Konten deaktiviert werden. + +#### Base URL +Bei der Base URL handelt es sich um die URL, unter der die SCM-Manager Instanz aufgerufen werden kann. Über eine Checkbox kann konfiguriert werden, ob diese Base URL erzwungen werden soll. Durch das Erzwingen werden Zugriff auf diese Applikation über andere URLs auf die Base URL umgeleitet. + +Beispiel: Die Base URL lautet www.scm-manager.com/scm, es gibt aber noch eine zweite URL www.scm-manager.io/scm. Beide zeigen auf dieselbe SCM-Manager Instanz. Wenn die Base URL erzwungen wird, wird der Anwender bei einem Zugriff über www.scm-manager.io/scm direkt auf die Base URL www.scm-manager.com/scm weitergeleitet. + +**Achtung:** Wenn die Base URL auf einen falschen Wert gesetzt wird und der Haken bei "Base URL erzwingen" gesetzt wurde, ist die SCM-Manager Instanz nicht mehr erreichbar. + +#### Proxy Einstellungen +Falls die SCM-Manager Instanz hinter einem Proxy-Server liegt, kann hier die Verbindung konfiguriert werden. + +![Administration-Configuration](assets/administration-settings-general.png) + +### Mercurial +Hier können globale Einstellungen zum Arbeiten mit dem Source Control Management Tool "Mercurial", kurz hg, konfiguriert werden. + +### Git +Hier können globale Einstellungen zum Arbeiten mit dem Source Control Management Tool "Git" konfiguriert werden. + +### Subversion +Hier können globale Einstellungen zum Arbeiten mit dem Source Control Management Tool "Subversion", kurz svn, konfiguriert werden. diff --git a/docs/de/user/group/assets/create-group.png b/docs/de/user/group/assets/create-group.png new file mode 100644 index 0000000000..14a9089263 Binary files /dev/null and b/docs/de/user/group/assets/create-group.png differ diff --git a/docs/de/user/group/assets/external-cas-group.png b/docs/de/user/group/assets/external-cas-group.png new file mode 100644 index 0000000000..2abe10b959 Binary files /dev/null and b/docs/de/user/group/assets/external-cas-group.png differ diff --git a/docs/de/user/group/assets/groups-information.png b/docs/de/user/group/assets/groups-information.png new file mode 100644 index 0000000000..811b9af08b Binary files /dev/null and b/docs/de/user/group/assets/groups-information.png differ diff --git a/docs/de/user/group/assets/groups-overview.png b/docs/de/user/group/assets/groups-overview.png new file mode 100644 index 0000000000..443bb40fa5 Binary files /dev/null and b/docs/de/user/group/assets/groups-overview.png differ diff --git a/docs/de/user/group/assets/groups-settings-general.png b/docs/de/user/group/assets/groups-settings-general.png new file mode 100644 index 0000000000..c73f9a177a Binary files /dev/null and b/docs/de/user/group/assets/groups-settings-general.png differ diff --git a/docs/de/user/group/assets/groups-settings-permissions.png b/docs/de/user/group/assets/groups-settings-permissions.png new file mode 100644 index 0000000000..bc2e56dc89 Binary files /dev/null and b/docs/de/user/group/assets/groups-settings-permissions.png differ diff --git a/docs/de/user/group/external.md b/docs/de/user/group/external.md new file mode 100644 index 0000000000..b1ea5ff36f --- /dev/null +++ b/docs/de/user/group/external.md @@ -0,0 +1,9 @@ +--- +title: Gruppen +subtitle: Externe Gruppen (CAS) +--- +Benutzer, die sich über eine CAS-Instanz (Central Authentication Service) am SCM-Manager anmelden, erhalten zusätzliche Gruppen aus dem CAS beigefügt. Externe Gruppen können ohne ein manuelles Anlegen im SCM-Manager genutzt werden. Das Hinzufügen, der als "extern" gekennzeichneten Gruppen, bringt jedoch die Vorteile mich sich, dass die angelegten Gruppen bei der Autovervollständigung im SCM-Manager berücksichtigt werden und es können Berechtigungen direkt an eine externe Gruppe zugewiesen werden. Welche externen Gruppen einem Benutzer zugewiesen sind, lässt sich über die Profil-Seite eines angemeldeten Benutzers auslesen. + +Siehe Beispiel: CasGroup + +![External-CAS-Group](assets/external-cas-group.png) diff --git a/docs/de/user/group/index.md b/docs/de/user/group/index.md new file mode 100644 index 0000000000..28a2eb1136 --- /dev/null +++ b/docs/de/user/group/index.md @@ -0,0 +1,25 @@ +--- +title: Gruppen +partiallyActive: true +--- +Der Bereich Gruppen umfasst alles, was auf einen Zusammenschluss mehrerer Anwender und deren Berechtigungen herunterzubrechen ist. + +* [Externe Gruppen (CAS)](external/) +* [Einstellungen](settings/) + +Um nicht jeden Benutzer einzeln berechtigen zu müssen, gibt es im SCM-Manager die Möglichkeit Gruppen anzulegen. Diese Gruppen können mit Berechtigungen ausgestattet werden und können beliebig viele Benutzer enthalten. + +### Übersicht +Die Gruppenübersicht zeigt eine Liste der existierenden Gruppen an. Die Gruppen werden dabei nach internen Gruppen und externen Gruppen unterschieden. Interne Gruppen werden in der Übersicht durch das Haus-Symbol kenntlich gemacht und sind Gruppen, die im SCM-Manager erstellt wurden. Externe Gruppen wiederum haben das Planeten-Symbol und sind Gruppen, dessen Mitglieder von externen Quellen wie beispielsweise einer LDAP-Instanz verwaltet werden. + +![Gruppen Übersicht](assets/groups-overview.png) + +### Gruppe erstellen +Für das Erstellen einer Gruppe wird lediglich ein Name benötigt. Es können aber auch direkt beim Erstellen die Beschreibung und die Mitglieder hinzugefügt werden. Einzig die Berechtigungen der Gruppe müssen nachträglich konfiguriert werden. + +![Gruppe erstellen](assets/create-group.png) + +### Gruppen Informationen +Die Informationsseite einer Gruppe zeigt neben den Metadaten auch eine Liste der Gruppenmitglieder an. + +![Gruppen Informationen](assets/groups-information.png) diff --git a/docs/de/user/group/settings.md b/docs/de/user/group/settings.md new file mode 100644 index 0000000000..b5fe590cba --- /dev/null +++ b/docs/de/user/group/settings.md @@ -0,0 +1,13 @@ +--- +title: Gruppen +subtitle: Einstellungen +--- +### Generell +In den allgemeinen Einstellungen einer Gruppe können die Beschreibung und die Mitglieder-Liste editiert werden. Ebenso kann die Gruppe hier gelöscht werden. Dieser Vorgang kann nicht rückgängig gemacht werden. + +![Generelle Gruppeneinstellungen](assets/groups-settings-general.png) + +### Berechtigungen +Eine Gruppe kann globale Berechtigung bekommen. Diese Berechtigungen gelten für alle Mitglieder der Gruppe. + +![Gruppen Berechtigungen](assets/groups-settings-permissions.png) diff --git a/docs/de/user/repo/assets/create-repository.png b/docs/de/user/repo/assets/create-repository.png new file mode 100644 index 0000000000..b73d8b5648 Binary files /dev/null and b/docs/de/user/repo/assets/create-repository.png differ diff --git a/docs/de/user/repo/assets/repository-branch-detailView.png b/docs/de/user/repo/assets/repository-branch-detailView.png new file mode 100644 index 0000000000..d099a96f13 Binary files /dev/null and b/docs/de/user/repo/assets/repository-branch-detailView.png differ diff --git a/docs/de/user/repo/assets/repository-branches-overview.png b/docs/de/user/repo/assets/repository-branches-overview.png new file mode 100644 index 0000000000..2474c97b4d Binary files /dev/null and b/docs/de/user/repo/assets/repository-branches-overview.png differ diff --git a/docs/de/user/repo/assets/repository-code-changesetDetails.png b/docs/de/user/repo/assets/repository-code-changesetDetails.png new file mode 100644 index 0000000000..41fc42ffee Binary files /dev/null and b/docs/de/user/repo/assets/repository-code-changesetDetails.png differ diff --git a/docs/de/user/repo/assets/repository-code-changesetsView.png b/docs/de/user/repo/assets/repository-code-changesetsView.png new file mode 100644 index 0000000000..4974525c4e Binary files /dev/null and b/docs/de/user/repo/assets/repository-code-changesetsView.png differ diff --git a/docs/de/user/repo/assets/repository-code-fileHistory.png b/docs/de/user/repo/assets/repository-code-fileHistory.png new file mode 100644 index 0000000000..3c6b21d1e9 Binary files /dev/null and b/docs/de/user/repo/assets/repository-code-fileHistory.png differ diff --git a/docs/de/user/repo/assets/repository-code-fileViewer.png b/docs/de/user/repo/assets/repository-code-fileViewer.png new file mode 100644 index 0000000000..f141c07920 Binary files /dev/null and b/docs/de/user/repo/assets/repository-code-fileViewer.png differ diff --git a/docs/de/user/repo/assets/repository-code-sourcesView.png b/docs/de/user/repo/assets/repository-code-sourcesView.png new file mode 100644 index 0000000000..fe113bbc4c Binary files /dev/null and b/docs/de/user/repo/assets/repository-code-sourcesView.png differ diff --git a/docs/de/user/repo/assets/repository-create-branch.png b/docs/de/user/repo/assets/repository-create-branch.png new file mode 100644 index 0000000000..d719c21466 Binary files /dev/null and b/docs/de/user/repo/assets/repository-create-branch.png differ diff --git a/docs/de/user/repo/assets/repository-information.png b/docs/de/user/repo/assets/repository-information.png new file mode 100644 index 0000000000..47fdb91d2e Binary files /dev/null and b/docs/de/user/repo/assets/repository-information.png differ diff --git a/docs/de/user/repo/assets/repository-overview-branches.png b/docs/de/user/repo/assets/repository-overview-branches.png new file mode 100644 index 0000000000..bd71d1d6cd Binary files /dev/null and b/docs/de/user/repo/assets/repository-overview-branches.png differ diff --git a/docs/de/user/repo/assets/repository-overview-changesets.png b/docs/de/user/repo/assets/repository-overview-changesets.png new file mode 100644 index 0000000000..3130d770da Binary files /dev/null and b/docs/de/user/repo/assets/repository-overview-changesets.png differ diff --git a/docs/de/user/repo/assets/repository-overview-settings.png b/docs/de/user/repo/assets/repository-overview-settings.png new file mode 100644 index 0000000000..658bd1933e Binary files /dev/null and b/docs/de/user/repo/assets/repository-overview-settings.png differ diff --git a/docs/de/user/repo/assets/repository-overview-sources.png b/docs/de/user/repo/assets/repository-overview-sources.png new file mode 100644 index 0000000000..3ddc6ff598 Binary files /dev/null and b/docs/de/user/repo/assets/repository-overview-sources.png differ diff --git a/docs/de/user/repo/assets/repository-overview.png b/docs/de/user/repo/assets/repository-overview.png new file mode 100644 index 0000000000..3b62267fd3 Binary files /dev/null and b/docs/de/user/repo/assets/repository-overview.png differ diff --git a/docs/de/user/repo/assets/repository-settings-general-git.png b/docs/de/user/repo/assets/repository-settings-general-git.png new file mode 100644 index 0000000000..697562a126 Binary files /dev/null and b/docs/de/user/repo/assets/repository-settings-general-git.png differ diff --git a/docs/de/user/repo/assets/repository-settings-permissionList.png b/docs/de/user/repo/assets/repository-settings-permissionList.png new file mode 100644 index 0000000000..7449c2564a Binary files /dev/null and b/docs/de/user/repo/assets/repository-settings-permissionList.png differ diff --git a/docs/de/user/repo/assets/repository-settings-permissionOverview.png b/docs/de/user/repo/assets/repository-settings-permissionOverview.png new file mode 100644 index 0000000000..b459c9b8b4 Binary files /dev/null and b/docs/de/user/repo/assets/repository-settings-permissionOverview.png differ diff --git a/docs/de/user/repo/branches.md b/docs/de/user/repo/branches.md new file mode 100644 index 0000000000..b52b6492b2 --- /dev/null +++ b/docs/de/user/repo/branches.md @@ -0,0 +1,22 @@ +--- +title: Repository +subtitle: Branches +--- +### Übersicht +Auf der Branches-Übersicht sind die bereits existierenden Branches aufgeführt. Bei einem Klick auf einen Branch wird man zur Detailseite des Branches weitergeleitet. + +Der Tag "Default" gibt an welcher Branch aktuell, als Standard-Branch dieses Repository im SCM-Manager markiert ist. Der Standard-Branch wird immer zuerst angezeigt, wenn man das Repository im SCM-Manager öffnet. + +Über den "Branch erstellen"-Button gelangt man zum Formular, um neue Branches anzulegen. + +![Branches Übersicht](assets/repository-branches-overview.png) + +### Branch erstellen +Mit dem "Branch erstellen"-Formular können neue Branches für das Repository erzeugt werden. Dafür muss ausgewählt werden von welchem existierenden Branch der neue Branch abzweigen soll und wie der neue Branch heißen soll. In einem leeren Git Repository können keine Branches erzeugt werden. + +![Branch erstellen](assets/repository-create-branch.png) + +### Branch Detailseite +Hier werden einige Befehle zum Arbeiten mit dem Branch auf einer Kommandozeile aufgeführt. + +![Branch Detailseite](assets/repository-branch-detailView.png) diff --git a/docs/de/user/repo/code.md b/docs/de/user/repo/code.md new file mode 100644 index 0000000000..7a2f5ed3df --- /dev/null +++ b/docs/de/user/repo/code.md @@ -0,0 +1,42 @@ +--- +title: Repository +subtitle: Code +displayToc: true +--- +Die Sektion "Code" enthält sämtliche Informationen, die sich auf den Code bzw. Inhalt des Repository beziehen. Oben auf der Seite befindet sich ein Aktionsbalken über den innerhalb der Code-Sektion navigiert werden kann. + +### Sources +Die Übersicht der Sources zeigt die Dateien und Ordner in dem Repository an. Wenn Branches existieren, werden die Sources für den ausgewählten Branch angezeigt. + +Es gibt unter dem Aktionsbalken eine Breadcrumbs Navigation, die den Pfad der angezeigten Dateien darstellt. Durch Klicken auf die einzelnen Pfad-Bestandteile, kann man sich durch die Dateistruktur des Repository (zurück-)navigieren. + +![Repository-Code-Sources](assets/repository-code-sourcesView.png) + +### Changesets +Die Übersicht der Changesets/Commits zeigt die Änderungshistorie je Branch an. Jeder Listeneintrag stellt einen Commit dar. + +Über den Details-Button kann man sich den Inhalt / die Änderungen dieses Changesets ansehen. + +Über den Sources-Button gelangt man zur Sources-Übersicht und es wird der Datenstand zum Zeitpunkt nach diesem Commit angezeigt. + +![Repository-Code-Changesets](assets/repository-code-changesetsView.png) + +### Changeset Details +Auf der Detailseite eines Changesets sieht man zusätzlich zu den Metadaten des Changesets sämtliche Änderungen, die in diesem Changeset enthalten sind. Die Diffs werden dabei im bekannten Format je Datei inklusive Syntax-Highlighting angezeigt. + +![Repository-Code-Changesets](assets/repository-code-changesetDetails.png) + +### Datei Details +Nach einem Klick auf eine Datei in den Sources landet man in der Detailansicht der Datei. Dabei sind je nach Dateiformat unterschiedliche Ansichten zu sehen: + +- Bild-Datei: Bild wird gerendert angezeigt. +- Markdown-Datei: Markdown wird gerendert dargestellt. Die Ansicht kann auf eine nicht gerenderte Textansicht umgeschaltet werden. +- Text-basierte Datei: Der Text wird angezeigt. Falls verfügbar mit Syntax-Highlighting. +- Nicht unterstützte Formate: Ein Download-Button wird angezeigt. + +![Repository-Code-FileDetails](assets/repository-code-fileViewer.png) + +### Datei Historie +Bei der Datei Details Ansicht kann man über einen Switch oben rechts auf die Historien-Ansicht wechseln. Dort werden die Commits aufgelistet, die diese Datei verändert haben. + +![Repository-Code-FileHistory](assets/repository-code-fileHistory.png) diff --git a/docs/de/user/repo/index.md b/docs/de/user/repo/index.md new file mode 100644 index 0000000000..7798a44c47 --- /dev/null +++ b/docs/de/user/repo/index.md @@ -0,0 +1,39 @@ +--- +title: Repository +partiallyActive: true +--- +Der Bereich Repository umfasst alles auf Basis von Repositories in Namespaces. Dazu zählen alle Operationen auf Branches, der Code und Einstellungen. + +* [Branches](branches/) +* [Code](code/) +* [Einstellungen](settings/) + +### Übersicht +Auf der Übersichtsseite der Repositories werden die einzelnen Repositories nach Namespaces gegliedert aufgelistet. Jedes Repository wird durch eine Kachel dargestellt. Durch einen Klick auf diese Kachel öffnet sich die Readme Seite des jeweiligen Repositories. + +![Repository Übersicht](assets/repository-overview.png) + +Über die Suchleiste oben können die Repositories gefiltert werden. Die Suche filtert dabei nach dem Namen und der Beschreibung des Repositories. + +Ein bestimmter Tab des Repositories wie Branches, Changesets oder Sources kann über die blauen Icons geöffnet werden. + +Icon | Beschreibung +---|--- +![Repository Branches](assets/repository-overview-branches.png) | Öffnet die Branches-Übersicht für das Repository +![Repository Changesets](assets/repository-overview-changesets.png) | Öffnet die Changesets-Übersicht für das Repository +![Repository Sources](assets/repository-overview-sources.png) | Öffnet die Sources-Übersicht für das Repository +![Repository Einstellungen](assets/repository-overview-settings.png) | Öffnet die Einstellungen für das Repository + +### Repository erstellen +Im SCM-Manager können neue Git, Mercurial & Subersion (SVN) Repositories über ein Formular angelegt werden. Dieses kann über den Button "Repository erstellen" aufgerufen werden. Dabei muss ein gültiger Name eingetragen und der Repository-Typ bestimmt werden. + +Optional kann man das Repository beim Erstellen direkt initialisieren. Damit werden für Git und Mercurial jeweils der Standard-Branch (master bzw. default) angelegt. Außerdem wird ein initialer Commit ausgeführt, der eine README.md erzeugt. + +Ist die Namespace-Strategie auf "Benutzerdefiniert" eingestellt, muss noch ein Namespace eingetragen werden. + +![Repository erstellen](assets/create-repository.png) + +### Repository Informationen +Die Informationsseite eines Repository zeigt die Metadaten zum Repository an. Darunter befinden sich Beschreibungen zu den unterschiedlichen Möglichkeiten wie man mit diesem Repository arbeiten kann. + +![Repository-Information](assets/repository-information.png) diff --git a/docs/de/user/repo/settings.md b/docs/de/user/repo/settings.md new file mode 100644 index 0000000000..14cd4d5305 --- /dev/null +++ b/docs/de/user/repo/settings.md @@ -0,0 +1,23 @@ +--- +title: Repository +subtitle: Einstellungen +--- +Unter den Repository Einstellungen befinden sich zwei Einträge. Wenn weitere Plugins installiert sind, können es deutlich mehr Unterseiten sein. + +### Generell +Unter dem Eintrag "Generell" kann man die Zusatzinformationen zum Repository editieren. Da es sich im Beispiel um ein Git Repository handelt, kann ebenfalls der Standard-Branch für dieses Repository gesetzt werden. Der Standard-Branch sorgt dafür, dass beim Arbeiten mit diesem Repository dieser Branch vorrangig geöffnet wird, falls kein expliziter Branch ausgewählt wurde. + +Über den Button unten auf dieser Seite besteht noch die Möglichkeit dieses Repository aus dem SCM-Manager zu löschen. Dieser Vorgang kann nicht rückgängig gemacht werden. + +![Repository-Settings-General-Git](assets/repository-settings-general-git.png) + +### Berechtigungen +Dank des fein granularen Berechtigungskonzepts des SCM-Managers können Nutzern und Gruppen, basierend auf definierbaren Rollen oder auf individuellen Einstellungen, Rechte zugewiesen werden. Berechtigungen können global und auf Repository-Ebene vergeben werden. Globale Berechtigungen werden in der Administrations-Oberfläche des SCM-Managers vergeben. Unter diesem Eintrag handelt es sich um Repository-bezogene Berechtigungen. + +Die Berechtigungen können jeweils für Gruppen und für Benutzer vergeben werden. Dabei gibt es die Möglichkeiten die Berechtigungen über Berechtigungsrollen zu definieren oder jede Berechtigung einzeln zu vergeben. Die Berechtigungsrollen können in der Administrations-Oberfläche definiert werden. + +![Repository-Settings-PermissionOverview](assets/repository-settings-permissionOverview.png) + +Für individuelle Berechtigungen kann man über "Erweitert" einen Dialog öffnen, um jede Berechtigung einzeln zu vergeben. + +![Repository-Settings-PermissionList](assets/repository-settings-permissionList.png) diff --git a/docs/de/user/user/assets/create-user.png b/docs/de/user/user/assets/create-user.png new file mode 100644 index 0000000000..3c6b09fa75 Binary files /dev/null and b/docs/de/user/user/assets/create-user.png differ diff --git a/docs/de/user/user/assets/user-information.png b/docs/de/user/user/assets/user-information.png new file mode 100644 index 0000000000..563c1e91c7 Binary files /dev/null and b/docs/de/user/user/assets/user-information.png differ diff --git a/docs/de/user/user/assets/user-overview.png b/docs/de/user/user/assets/user-overview.png new file mode 100644 index 0000000000..49d6cf0ef8 Binary files /dev/null and b/docs/de/user/user/assets/user-overview.png differ diff --git a/docs/de/user/user/assets/user-settings-general.png b/docs/de/user/user/assets/user-settings-general.png new file mode 100644 index 0000000000..8793724642 Binary files /dev/null and b/docs/de/user/user/assets/user-settings-general.png differ diff --git a/docs/de/user/user/assets/user-settings-password.png b/docs/de/user/user/assets/user-settings-password.png new file mode 100644 index 0000000000..6a16ad2040 Binary files /dev/null and b/docs/de/user/user/assets/user-settings-password.png differ diff --git a/docs/de/user/user/assets/user-settings-permissions.png b/docs/de/user/user/assets/user-settings-permissions.png new file mode 100644 index 0000000000..bab667ceed Binary files /dev/null and b/docs/de/user/user/assets/user-settings-permissions.png differ diff --git a/docs/de/user/user/index.md b/docs/de/user/user/index.md new file mode 100644 index 0000000000..9354aa17d5 --- /dev/null +++ b/docs/de/user/user/index.md @@ -0,0 +1,26 @@ +--- +title: Benutzer +partiallyActive: true +--- +Der Bereich Benutzer umfasst alles, was auf einen einzelnen Anwender und dessen Berechtigungen herunterzubrechen ist. + +* [Einstellungen](settings/) + +### Übersicht +Auf der Benutzer Übersichtsseite wird eine Liste der existierenden Benutzer angezeigt. Durch Klicken auf einen Benutzer gelangt man zu dessen Detailseite. Über die Schaltfläche "Benutzer erstellen" können neue Benutzer angelegt werden. + +![Benutzer Übersicht](assets/user-overview.png) + +### Benutzer erstellen +Mithilfe des "Benutzer erstellen"-Formulars können neue Benutzer im SCM-Manager angelegt werden. Neue Benutzer haben noch keine Berechtigungen und sollten direkt nach dem Anlegen konfiguriert werden. + +![Benutzer erstellen](assets/create-user.png) + +### Benutzer Detailseite +Die Detailseite eines Benutzers zeigt die Informationen zu diesem an. + +Über den "Aktiv"-Marker sieht man, ob dies ein aktivierter Benutzer des SCM-Managers ist. Wird ein Benutzer auf inaktiv gesetzt, kann er sich nicht mehr am SCM-Manager anmelden. + +Der Typ eines Benutzers gibt an, aus welcher Quelle dieser Benutzer stammt. Der Typ "XML" aus dem Beispiel gibt an, dass dieser Benutzer im SCM-Manager erstellt wurde. Daneben kann es aber auch externe Benutzer geben, die beispielweise mithilfe des LDAP-Plugins aus einer LDAP-Instanz angebunden wurden. + +![Benutzer Informationen](assets/user-information.png) diff --git a/docs/de/user/user/settings.md b/docs/de/user/user/settings.md new file mode 100644 index 0000000000..d9ec76446b --- /dev/null +++ b/docs/de/user/user/settings.md @@ -0,0 +1,21 @@ +--- +title: Benutzer +subtitle: Einstellungen +--- +### Generell +In den generellen Einstellungen des Benutzers können der Anzeigename, die E-Mail-Adresse und der Aktivitätsstatus des Kontos editiert werden. + +Über die Schaltfläche unten kann der Benutzer auch komplett gelöscht werden. Dieser Vorgang kann nicht rückgängig gemacht werden. + +![Generelle Benutzereinstellungen](assets/user-settings-general.png) + +### Passwort +Unter dem Eintrag "Passwort" kann das Login-Passwort des Benutzers geändert werden. Falls ein Benutzer sein Passwort selbst ändern möchte, kann er das machen indem er sich einloggt und ganz unten in der Fußzeile der Seite auf seinen Namen klickt. Dann gelangt er in die Kontoeinstellungen kann unter anderem ein neues Passwort vergeben. + +![Passwort ändern](assets/user-settings-password.png) + +### Berechtigungen +Hier werden die globalen (nicht-Repository-bezogenen) Berechtigungen für einen Benutzer konfiguriert. +Für die einzelnen Rechte sind Tooltips verfügbar, welche Auskunft über die Auswirkungen der jeweiligen Berechtigung geben. + +![Benutzer Berechtigungen](assets/user-settings-permissions.png) diff --git a/docs/en/State of SCM-Manager 2 development.md b/docs/en/State of SCM-Manager 2 development.md deleted file mode 100644 index 38823de474..0000000000 --- a/docs/en/State of SCM-Manager 2 development.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: State of SCM-Manager 2 development ---- -The development of SCM-Manager 2.0.0 is organised in [Trello Boards](https://trello.com/scmmanager). - -## [Milestone 1](https://trello.com/b/oit1MD92/scm-manager-2-0-0-milestone-1) - -### Main goals -* remove deprecated and unused stuff -* remove old style listeners -* replace [guava eventbus](https://code.google.com/p/guava-libraries/wiki/EventBusExplained) with [legman](https://github.com/sdorra/legman) -* introduce new plugin structure -* offline plugin installation/updates/deinstallation -* use java 7 as default -* use of [annotation processors](http://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Processor.html) instead of classpath scanning -* move non core modules (plugin-backend, maven plugins, etc.) to separate repositories - -## [Milestone 2](https://trello.com/b/Afb3hoJ9/scm-manager-2-0-0-milestone-2) - -### Main goals -* use [apache shiro](http://shiro.apache.org/) everywhere -* improve authentication -* improve user and group management -* use permission instead of roles - -## [Milestone 3](https://trello.com/b/eLvqTGGe/scm-manager-2-0-0-milestone-3) - -### Main goals -* completely new designed rest api - -## Milestone 4 - -### Main goals -* completely new user interface - -## Milestone 5 - -### Main goals -* improve repository api diff --git a/docs/en/configuration/basedirectory.md b/docs/en/administration/basedirectory.md similarity index 100% rename from docs/en/configuration/basedirectory.md rename to docs/en/administration/basedirectory.md diff --git a/docs/en/administration/command-line-client.md b/docs/en/administration/command-line-client.md deleted file mode 100644 index 86c4c29671..0000000000 --- a/docs/en/administration/command-line-client.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Command line client ---- - -You can download the command line client from -[here](http://www.scm-manager.org/download/) (the scm-cli-client). - -### Examples - -**1\. Store username, password and server url** - -```bash -$ java -jar scm-cli-client-1.47-jar-with-dependencies.jar --user scmadmin --password madmin --server http://localhost:8080/scm store-config - -store config -``` - -**2\. List all repositories** - -```bash -$ java -jar scm-cli-client-1.47-jar-with-dependencies.jar list-repositories - -ID: fbb64701-6dd3-4847-8588-26f693736961 -Name: scm -Type: hg -E-Mail: s.sdorra@gmail.com -Description: SCM-Manager -Public: false -Creation-Date: 2011-06-03 16:13:19 -Last-Modified: 2011-06-03 16:15:38 -URL: http://localhost:8080/scm/hg/scm -Permissions: - WRITE - sdorra (Group: false) -``` - -**3\. Create a new user** - -```bash -$ java -jar scm-cli-client-1.47-jar-with-dependencies.jar create-user --name test --display-name "Test User" --mail "test@scm-manager.org" --password secret - -Name: test -Display Name: Test User -Type: xml -E-Mail: test@scm-manager.org -Administrator: false -Creation-Date: -Last-Modified: -``` - -**4\. Add write permission for user test to repository scm** - -```bash -$ java -jar scm-cli-client-1.47-jar-with-dependencies.jar add-permission fbb64701-6dd3-4847-8588-26f693736961 --name test -t WRITE - -ID: fbb64701-6dd3-4847-8588-26f693736961 -Name: scm -Type: hg -E-Mail: s.sdorra@gmail.com -Description: SCM-Manager -Public: false -Creation-Date: 2011-06-03 16:13:19 -Last-Modified: 2011-06-03 16:15:38 -URL: http://localhost:8080/scm/hg/scm -Permissions: - WRITE - sdorra (Group: false) - WRITE - test (Group: false) -``` diff --git a/docs/en/administration/logging.md b/docs/en/administration/logging.md index fd371e3bf5..412ecbac1d 100644 --- a/docs/en/administration/logging.md +++ b/docs/en/administration/logging.md @@ -1,5 +1,6 @@ --- title: Logging +subtitle: Configuration and locations of SCM-Manager logging --- SCM-Manager logs information which can be useful, if the system does not behave as expected. @@ -14,4 +15,21 @@ The logging behavior depends on your operating system and installation. | Mac OS X | ~/Library/Logs/SCM-Manager | | Windows | $BASEDIR\logs | -The location of the **$BASEDIR** can be found [here](basedirectory). +The location of the **$BASEDIR** can be found [here](../basedirectory/). + +## Configuration + +The logging behaviour of SCM-Manager can be configured via an xml file. +The syntax and properties can be found [here](http://logback.qos.ch/manual/configuration.html). +The location of the file depends also on the type of installation. + +| Type of Installation | Path | +|----------------------|---------| +| Docker | /opt/scm-server/conf/logging.xml | +| RPM | /etc/scm/logging.xml | +| DEB | /etc/scm/logging.xml | +| Unix | $EXTRACT_PATH/scm-server/conf/logging.xml | +| Mac OS X | $EXTRACT_PATH/scm-server/conf/logging.xml | +| Windows | $EXTRACT_PATH/scm-server/conf/logging.xml | + +**$EXTRACT_PATH** is the path were you etract the content of the package. diff --git a/docs/en/configuration/apache/apache-mod_proxy.md b/docs/en/administration/reverse-proxies.md similarity index 70% rename from docs/en/configuration/apache/apache-mod_proxy.md rename to docs/en/administration/reverse-proxies.md index 2913b433ba..57bf4e5ec0 100644 --- a/docs/en/configuration/apache/apache-mod_proxy.md +++ b/docs/en/administration/reverse-proxies.md @@ -1,8 +1,20 @@ --- -title: SCM-Server and Apache mod_proxy +title: Reverse Proxy +subtitle: How to use SCM-Manager with common reverse proxies +displayToc: true --- -### Apache configuration +TODO reverse proxies in general send X-Forwarded headers ... + +### nginx + +TODO ... + +### Apache + + ```apache ProxyPass /scm http://localhost:8080/scm @@ -16,8 +28,16 @@ ProxyPassReverse /scm http://servername:8080/scm - **Warning**: Setting ProxyPassReverseCookiePath would most likely cause problems with session handling! - **Note**: If you encounter timeout problems, please have a look at [Apache Module mod_proxy#Workers](http://httpd.apache.org/docs/current/mod/mod_proxy.html#workers). +### HA-Proxy + +TODO ... + ### SCM-Server conf/server-config.xml + + NOTE: This file is found in the installation directory, not the user\'s home directory. @@ -43,15 +63,3 @@ Example: ``` - -### SCM-Manager Configuration version 1.5 and above - -1. Login as an admin user and select \"General\" -2. Set the \"Base Url\" to the URL of the Apache (**warning:** don\'t check \"Force Base Url\") -3. Save the new new settings - -### SCM-Manager Configuration before version 1.5 - -1. Login as an admin user and select \"General\" -2. Set the Serverport to the apache port (normally port 80) -3. Save the new settings diff --git a/docs/en/administration/scm-server-ssl.md b/docs/en/administration/scm-server-ssl.md index 55444d2379..71debe72c6 100644 --- a/docs/en/administration/scm-server-ssl.md +++ b/docs/en/administration/scm-server-ssl.md @@ -2,6 +2,11 @@ title: SCM-Server SSL --- + + **Note**: This document describes a ssl configuration with a self-signed certificate diff --git a/docs/en/configuration/apache/apache-mod_jk.md b/docs/en/configuration/apache/apache-mod_jk.md deleted file mode 100644 index a30d656fd3..0000000000 --- a/docs/en/configuration/apache/apache-mod_jk.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: SCM-Server and Apache mod_jk ---- - -### Apache Configuration -```apache -JkWorkersFile /etc/apache2/jkworkers.properties -JkLogFile /var/log/apache2/mod_jk.log -JkLogLevel info - -JkMount /scm* worker1 -``` - -### JK Workers File (jkworkers.properties) -```ini -worker.list=worker1 -worker.worker1.type=ajp13 -worker.worker1.host=localhost -worker.worker1.port=8009 -worker.worker1.lbfactor=50 -worker.worker1.cachesize=10 -worker.worker1.cache_timeout=600 -worker.worker1.socket_keepalive=1 -``` - -### SCM-Server conf/server-config.xml -Uncomment the following lines: -```xml - - - - 8009 - - - -``` diff --git a/docs/en/configuration/apache/config-mod_proxy.png b/docs/en/configuration/apache/config-mod_proxy.png deleted file mode 100644 index d1d8d6cd05..0000000000 Binary files a/docs/en/configuration/apache/config-mod_proxy.png and /dev/null differ diff --git a/docs/en/configuration/index.md b/docs/en/configuration/index.md deleted file mode 100644 index c858522ba5..0000000000 --- a/docs/en/configuration/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Configuration -partiallyActive: true ---- - -* [Base Directory](basedirectory/) -* [Logging](logging/) -* [SCM-Server and Apache mod\_proxy](apache/apache-mod_proxy/) -* [SCM-Server and Apache mod\_jk](apache/apache-mod_jk/) diff --git a/docs/en/configuration/logging.md b/docs/en/configuration/logging.md deleted file mode 100644 index 8fa707f016..0000000000 --- a/docs/en/configuration/logging.md +++ /dev/null @@ -1,14 +0,0 @@ -The logging behaviour of SCM-Manager can be configured via an xml file. -The syntax and properties can be found [here](http://logback.qos.ch/manual/configuration.html). -The location of the file depends also on the type of installation. - -| Type of Installation | Path | -|----------------------|---------| -| Docker | /opt/scm-server/conf/logging.xml | -| RPM | /etc/scm/logging.xml | -| DEB | /etc/scm/logging.xml | -| Unix | $EXTRACT_PATH/scm-server/conf/logging.xml | -| Mac OS X | $EXTRACT_PATH/scm-server/conf/logging.xml | -| Windows | $EXTRACT_PATH/scm-server/conf/logging.xml | - -**$EXTRACT_PATH** is the path were you etract the content of the package. diff --git a/docs/en/decision-table.md b/docs/en/development/decision-table.md similarity index 100% rename from docs/en/decision-table.md rename to docs/en/development/decision-table.md diff --git a/docs/en/development/intellij-idea-configuration.md b/docs/en/development/intellij-idea-configuration.md index f1bdbe155f..f740281518 100644 --- a/docs/en/development/intellij-idea-configuration.md +++ b/docs/en/development/intellij-idea-configuration.md @@ -33,10 +33,6 @@ title: Intellij IDEA Configuration * Languages & Frameworks / Node.js and NPM * Package Manager: yarn -* Languages & Frameworks / Javascript - * JavaScript language version: Flow - * Flow package or executable: .../node_modules/flow-bin - * Languages & Frameworks / Javascript / Code Quality Tools / ESLint * Enable * ESLint package: .../node_modules/eslint diff --git a/docs/en/resources/permissions-mockup-global-permissions.jpg b/docs/en/development/permission-concept/assets/permissions-mockup-global-permissions.jpg similarity index 100% rename from docs/en/resources/permissions-mockup-global-permissions.jpg rename to docs/en/development/permission-concept/assets/permissions-mockup-global-permissions.jpg diff --git a/docs/en/resources/permissions-mockup-repository-permissions.jpg b/docs/en/development/permission-concept/assets/permissions-mockup-repository-permissions.jpg similarity index 100% rename from docs/en/resources/permissions-mockup-repository-permissions.jpg rename to docs/en/development/permission-concept/assets/permissions-mockup-repository-permissions.jpg diff --git a/docs/en/resources/permissions-mockup-user.jpg b/docs/en/development/permission-concept/assets/permissions-mockup-user.jpg similarity index 100% rename from docs/en/resources/permissions-mockup-user.jpg rename to docs/en/development/permission-concept/assets/permissions-mockup-user.jpg diff --git a/docs/en/administration/permission-concept.md b/docs/en/development/permission-concept/index.md similarity index 98% rename from docs/en/administration/permission-concept.md rename to docs/en/development/permission-concept/index.md index cf19bb2fa2..20f190a48d 100644 --- a/docs/en/administration/permission-concept.md +++ b/docs/en/development/permission-concept/index.md @@ -1,5 +1,6 @@ --- title: Permission Concept +subtitle: Fine-grained permission for SCM-Manager v2 --- This documents describes a concept for a fine-grained permission managing via the SCMMv2 UI. @@ -16,7 +17,7 @@ This documents describes a concept for a fine-grained permission managing via th ### Status Quo SCMv1 -[SCMMv1 's permissions](../Permissions.md) are only related to Repositories: +SCMMv1 's permissions are only related to Repositories: * Users can either have the Permission READ, WRITER or OWNER * globally (for all repositories) or @@ -121,11 +122,11 @@ In order to fulfill the requirements, this concept describes The global permission component can be reached from **either user and groups** components navigations. The following mockup shows this in the user component: -![Permissions mockup user](docs/en/resources/permissions-mockup-user.jpg) +![Permissions mockup user](assets/permissions-mockup-user.jpg) The layout of the permission component UI could look like this: -![Permissions mockup global permissions](docs/en/resources/permissions-mockup-global-permissions.jpg) +![Permissions mockup global permissions](assets/permissions-mockup-global-permissions.jpg) The UI * queries all available global permissions from the REST API (shiro strings), @@ -141,7 +142,7 @@ permissions (see `PermissionType`). The UI is extended like so: -![Permissions mockup repository permissions](docs/en/resources/permissions-mockup-repository-permissions.jpg) +![Permissions mockup repository permissions](assets/permissions-mockup-repository-permissions.jpg) #### Existing repository dialog @@ -397,7 +398,7 @@ and write. That is, Internationalization can be handled using the following conventions: -* All permission i18n are described in `plugins.json` (also for core), see [i18n for Plugins](docs/en/i18n-for-plugins.mdugins.md) +* All permission i18n are described in `plugins.json` (also for core), see [i18n for Plugins](../plugins/i18n-for-plugins) * That way the UI for users and groups can find all the translation in the same file * Convention for i18n keys: `permissions.`, containing `displayName` and `description` each. diff --git a/docs/en/development/plugins/architecture.jpg b/docs/en/development/plugins/architecture.jpg deleted file mode 100644 index 8fd77c73b4..0000000000 Binary files a/docs/en/development/plugins/architecture.jpg and /dev/null differ diff --git a/docs/en/development/plugins/migrate-plugin-from-v1.md b/docs/en/development/plugins/migrate-plugin-from-v1.md index d05570e6d4..2c8877cd33 100644 --- a/docs/en/development/plugins/migrate-plugin-from-v1.md +++ b/docs/en/development/plugins/migrate-plugin-from-v1.md @@ -2,7 +2,7 @@ title: Migrate a v1 plugin --- -Before starting, make sure to read the [Plugin Development](docs/en/plugin-development.mdpment.md). +Before starting, make sure to read the [Plugin Development](../plugin-development). To migrate an existing SCM-Manager 1.x Plugin, you have to do the following steps: @@ -119,7 +119,7 @@ diff -r a988f4cfb7ab src/main/resources/META-INF/scm/plugin.xml ### Java sources (src/main/java) * try to compile the sources: `mvn compile` -* fix problems (See [API changes](api-changes.md)) +* fix problems (TODO more help here) * Remove XML accept headers from REST Resource classes -> SCMMv2 supports JSON only * Migrate REST Resources (e.g. `v2`, add to Index Resource, Update Links) - See core plugins Git, Hg, Svn, e.g. [`GitConfigResource`](https://github.com/scm-manager/scm-manager/blob/develop/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java) @@ -139,7 +139,7 @@ diff -r a988f4cfb7ab src/main/resources/META-INF/scm/plugin.xml "postinstall" : "ui-plugins postinstall" }, "dependencies": { - "@scm-manager/ui-plugins" : "2.0.0-rc7" + "@scm-manager/ui-plugins" : "2.0.0" } } ``` @@ -164,10 +164,10 @@ Some more hints: * For Configuration UIs use [`ConfigurationBinder`](https://github.com/scm-manager/scm-manager/blob/develop/scm-ui/ui-components/src/config/ConfigurationBinder.tsx) - See core plugins Git, Hg, Svn, e.g. [scm-git-plugin/index.ts](https://github.com/scm-manager/scm-manager/blob/develop/scm-plugins/scm-git-plugin/src/main/js/index.ts). Note that `readOnly` property checks if update link is returned by REST resource - * Don't forget [i18n for Plugins](docs/en/i18n-for-plugins.mdugins.md) + * Don't forget [i18n for Plugins](../i18n-for-plugins) # Further reading -* [scm-manager/ui-extensions README](scm-ui/ui-extensions/README.md) - Extension Points within SCM-Manager +* [UI Extensions](../../ui-extensions) - Extend the SCM-Manager UI * [scm-manager/ui-components](https://github.com/scm-manager/scm-manager/tree/develop/scm-ui/ui-components) - Reusable UI components within SCM-Manager * [smp-maven-plugin](https://github.com/scm-manager/smp-maven-plugin) - Plugin that facilitates efficient plugin development for SCMM diff --git a/docs/en/development/plugins/plugin-development.md b/docs/en/development/plugins/plugin-development.md index 71aef89591..f66236bb90 100644 --- a/docs/en/development/plugins/plugin-development.md +++ b/docs/en/development/plugins/plugin-development.md @@ -95,18 +95,17 @@ In order to extend the ui the plugin requires a `package.json` in the project ro "postinstall" : "ui-plugins postinstall" }, "dependencies": { - "@scm-manager/ui-plugins" : "2.0.0-rc7" + "@scm-manager/ui-plugins" : "2.0.0" } } ``` The `main` field of the `package.json` describes the main entry point of the plugin. -The file specified at `main` should use the `binder` from the [@scm-manager/ui-extensions](../../scm-ui/ui-extensions) in oder to bind its extensions. -For more information of extensions, binder and extension points, please have a look at the [README.md](../../scm-ui/ui-extensions/README.md) of @scm-manager/ui-extensions. +The file specified at `main` should use the `binder` from the [@scm-manager/ui-extensions](../../ui-extensions) in oder to bind its extensions. If the plugins gets build (`mvn package` or `mvn install`), the [buildfrontend-maven-plugin](https://github.com/sdorra/buildfrontend-maven-plugin), will call the `build` script of `package.json`. -The build script triggers the `plugin` command of [@scm-manager/ui-scripts](../../scm-ui/ui-scripts). +The build script triggers the `plugin` command of `@scm-manager/ui-scripts`. The `ui-scripts` will do the following steps: * traverses the import statements of the script specified at `main` diff --git a/docs/en/development/plugins/publish.md b/docs/en/development/plugins/publish.md index b485e3ed7d..079725e43f 100644 --- a/docs/en/development/plugins/publish.md +++ b/docs/en/development/plugins/publish.md @@ -8,35 +8,25 @@ If you want to share your plugin with SCM-Manager users, you can publish it to t * Create a or Git repository for your plugin * Develop your plugin as described in [Create a plugin](../create/) * Fork the [Plugin Center Repository](https://github.com/scm-manager/plugin-center) -* Create a folder with the name of your plugin under the `src/plugins` directory -* Put a `index.md` which starts with frontmatter metadata, which describes your plugin e.g.: +* Create a folder with the name of your plugin under the `content/plugins` directory +* Create a `plugin.yml` in this folder, which describes your plugin e.g.: ```yaml ---- name: scm-cas-plugin displayName: CAS description: CAS Authentication plugin for version 2.x of SCM-Manager category: authentication author: Cloudogu GmbH ---- ``` -* Document your plugin with [markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) below the frontmatter header * Commit your work and open a pull request. Put the url to your plugin repository into the description of the pull request. After you have opened the pull request. We will do a few steps to integrate your plugin into the plugin center: * We will create a fork of your plugin under the [SCM-Manager Team](https://github.com/scm-manager/) and give your account write permissions -* After that we will create a Jenkins job for your plugin on [oss.cloudogu.com](https://oss.cloudogu.com/jenkins/job/scm-manager/job/scm-manager-bitbucket/) +* After that we will create a Jenkins job for your plugin on [oss.cloudogu.com](https://oss.cloudogu.com/jenkins/job/scm-manager-plugins/) * At the end we will accept your pull request From now on you can work with the repository in the [SCM-Manager Team](https://github.com/scm-manager/). -Every time you release your plugin (push a tag to the repository) the Jenkins job will build your plugin and release it to the plugin center. - - -# Architecture - -The following picture shows the architecture of the involved systems. - -![Architecture](architecture.jpg "Plugin Center Architecture") +Every time you release your plugin (push a release branch e.g.: release/1.0.1) the Jenkins job will build your plugin and release it to the plugin center. diff --git a/docs/en/development/ui-dod.md b/docs/en/development/ui-dod.md index 0f569ba71b..93267e92fe 100644 --- a/docs/en/development/ui-dod.md +++ b/docs/en/development/ui-dod.md @@ -12,4 +12,4 @@ Use this as a kind of a checklist whenever you develop something in the UI of SC | ☐ | add help icons to input components | | ☐ | not use colors directly, but refer to `is-primary` or `is-warning` | | ☐ | make sure your view works on mobile devices | -| ☐ | document [extension points in wiki](docs/en/extension-points.mdoints.md) | +| ☐ | document [extension points](../plugins/extension-points) | diff --git a/scm-ui/ui-extensions/README.md b/docs/en/development/ui-extensions.md similarity index 96% rename from scm-ui/ui-extensions/README.md rename to docs/en/development/ui-extensions.md index 63d623dbb6..1b3a22af3d 100644 --- a/scm-ui/ui-extensions/README.md +++ b/docs/en/development/ui-extensions.md @@ -1,4 +1,7 @@ -# ui-extensions +--- +title: UI-Extensions +subtitle: How to extend the SCM-Manager UI with plugins +--- UI-Extensions contains the building blocks for the [SCM-Manager](https://scm-manager.org) ui extension system. diff --git a/docs/en/faq.md b/docs/en/faq.md index 5f07c61257..ef64b4d9db 100644 --- a/docs/en/faq.md +++ b/docs/en/faq.md @@ -7,56 +7,24 @@ title: Frequently Asked Questions Username: `scmadmin`\ Password: `scmadmin` -### Where does SCM-Manager store its configuration, log files and the repositories? +### Where does SCM-Manager store its configuration, data and repositories? -SCM-Manager stores the complete data in a directory called .scm (the -SCM-Manager home directory). This directory is located in the home -directory of the user which is the owner of the process. Except for [rpm -and deb](RPM%20and%20DEB%20packages.md) -installations, for those installations the home directory is located at -/var/lib/scm. +All data which is created by SCM-Manager, is stored in the SCM-Manager base directory. +The location of the base directory depends on your type of installation. +Please have a look at the [documentation](../administration/basedirectory/). ### How can I change the SCM-Manager home directory? -You could change the SCM-Manager home directory in a few ways: - -- Edit the scm.properties (WEB-INF/classes) file and add the path to - your folder f.e. `scm.home=/var/scm` -- Set an environment variable SCM\_HOME with the path of your - directory -- Start your application server with a java property called `scm.home` - f.e. `-Dscm.home=/var/scm` - -### Can I create a directory structure for scm-manager repositories? - -Yes, since version 1.9 you can create directory structures. You can just -use a \"/\" in the name of the repository to create the structure. For -example the repositories Project/module-1, Project/module-2 and -OtherProject/module-1 will result in the following structure. - -```text -+ Project -| - module-1 -| - module-2 -+ OtherProject -| - module-1 -``` - -For more information have a look at [#47](https://github.com/scm-manager/scm-manager/issues/47 "Support for directory structure"). - -### After creation of a new public repository I am trying to clone it anonymously, but I got request of user and password. What am I doing wrong? - -You have to enable \"Allow Anonymous Access\" at Config-\>General. +There are several ways to change the location of the home directory: [documentation](../administration/basedirectory/#change-base-directory-location) ### Where does SCM-Manager stores it log files? -SCM-Manager stores the log files in a directory called \"logs\" which is -located in the home directory (see question \"Where does SCM-Manager -store its configuration, log files and the repositories?\"). +The location of the log files depends on your operation system and the type of installation. +Please have a look at the [documentation](../administration/logging/). ### How do I enable trace logging? -Edit scm-server/conf/logging.xml change the line from: +Find the location of your `logging.xml` in the [documentation](../administration/logging/#configuration) and change the following line from: ```xml @@ -67,17 +35,8 @@ to: ``` -If you are using the war version with an application server such as -tomcat, you have to edit the logback.xml in WEB-INF/classes. +After changing the configuration, SCM-Manager must be restarted. ### How do I install plugins? -Select Config-\>Plugins. This is supposed to show you a list of all -available plugins to install. It is not a place to configure existing -plugins. Install Package does not take you to the install screen\... If -you only see the installed plugins, see the next question. - -### Why don\'t I see any installable plugins on the plugin tab? - -Is the SCM-Manager server behind a proxy server? Then you have to -configure your proxyserver at Config-\>General. +Find the plugin you like to install at [plugins](/plugins#categories) and follow the installation instructions on the install page of the plugin. diff --git a/docs/en/index.md b/docs/en/index.md index 0c9a03bd07..13c4d44c5a 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -1,11 +1,14 @@ --- title: Documentation -subtitle: SCM-Managert Documentation +subtitle: SCM-Manager Documentation --- +This documentation describes the administration and usage of SCM-Manager. It is available in different languages and versions, which can be selected in the menu on the right. -This documentation describes the administration and usage of SCM-Manager. +SCM-Manager 2 is an extremely lightweight source code management tool that can be individually extended through a variety of plugins. +This documentation is divided into five categories: -## Version and language - -The documentation belongs to a specific language and version of SCM-Manager. -You can choose your version and language on the menu to the right. +- The section "Getting started" introduces the installation and configuration of SCM-Manager and answers frequently asked questions. +- The "Administration" area deals with issues that are important when operating an instance. +- The rubric "Development" contains information for the further development of SCM-Manager. It describes how SCM-Manager can be built and what needs to be considered. +- The area of "Plugin Development" focuses on the process of plugin development. +- "User-oriented" summarizes the features from the user's point of view in the basic version with the minimal set of plugins. The features of the optional plugins are introduced in separate documentations. diff --git a/docs/en/installation/docker.md b/docs/en/installation/docker.md index 3bdb207564..41e0caea71 100644 --- a/docs/en/installation/docker.md +++ b/docs/en/installation/docker.md @@ -43,6 +43,24 @@ If you want to use the ssh plugin, keep in mind that this plugin requires an ext docker run --name scm -p 2222:2222 -p 8080:8080 -v scm-home:/var/lib/scm scmmanager/scm-manager: ``` +## JVM Parameters + +If it becomes necessary to add JVM parameters to the start, there are two ways to do this: + +* As arguments e.g.: + +```bash +docker run scmmanager/scm-manager: -Dsome.property=value +``` + +* Or as JAVA_OPTS environment variable e.g.: + +```bash +docker run -e JAVA_OPTS="-Dsome.property=value" scmmanager/scm-manager: + +``` + + ## Docker Compose If you want to use the image with docker-compose have a look at the example below. diff --git a/docs/en/installation/redhat.md b/docs/en/installation/redhat.md index 7f634fedeb..d422bd4b76 100644 --- a/docs/en/installation/redhat.md +++ b/docs/en/installation/redhat.md @@ -60,6 +60,12 @@ The default username is `scmadmin` with the password `scmadmin`. ## Troubleshooting +### Upgrade from SCM-Manager 1.x + +If you had an SCM-Manager 1.x installed before, please remove (or better back up) the old `/opt/scm-server` directory. This must not exist before installing the new 2.x version. + +### Service does not start + If the service does not start have a look at the systemd journal: ```bash diff --git a/docs/en/screenshots/migration-wizard.png b/docs/en/migrate-scm-manager-from-v1/assets/migration-wizard.png similarity index 100% rename from docs/en/screenshots/migration-wizard.png rename to docs/en/migrate-scm-manager-from-v1/assets/migration-wizard.png diff --git a/docs/en/migrate-scm-manager-from-v1.md b/docs/en/migrate-scm-manager-from-v1/index.md similarity index 99% rename from docs/en/migrate-scm-manager-from-v1.md rename to docs/en/migrate-scm-manager-from-v1/index.md index d12e1de7b8..0adbed9034 100644 --- a/docs/en/migrate-scm-manager-from-v1.md +++ b/docs/en/migrate-scm-manager-from-v1/index.md @@ -23,7 +23,7 @@ To specify the new names (and namespaces), the SCM-Manager version 2 starts a mi You can open this wizard in an internet browser using the URL of your installation (eg. http://localhost:8080/scm/). -![Migration Wizard](screenshots/migration-wizard.png) +![Migration Wizard](assets/migration-wizard.png) In the figure you can see an example of the page. We tried to guess meaningful names, but for sure you want to make some changes here. Beside choosing new namespaces and names you have to select a migration strategy for each repository. The strategies are described on the page as follows: diff --git a/docs/en/navigation.yml b/docs/en/navigation.yml index 045f97f29e..55d9971267 100644 --- a/docs/en/navigation.yml +++ b/docs/en/navigation.yml @@ -1,27 +1,35 @@ - section: Getting started entries: - /installation/ - - /configuration/ - /migrate-scm-manager-from-v1/ - /faq/ - /known-issues/ +- section: User-oriented + entries: + - /user/repo/ + - /user/user/ + - /user/group/ + - /user/admin/ + - section: Administration entries: - - /administration/scm-server-ssl/ - - /administration/logging/ - - /administration/command-line-client/ + - /administration/basedirectory/ + - /administration/logging/ + - /administration/scm-server-ssl/ + - /administration/reverse-proxies/ - section: Development entries: - /development/intellij-idea-configuration/ - /development/build-from-source/ - /development/ui-common-pitfall/ - - /administration/permission-concept/ + - /development/permission-concept/ - /development/error-handling/ - /development/i18n/ - /development/definition-of-done/ - /development/ui-dod/ + - /development/decision-table/ - section: Plugin Development entries: diff --git a/docs/en/release-process.md b/docs/en/release-process.md index 2d1beac082..cd3123589d 100644 --- a/docs/en/release-process.md +++ b/docs/en/release-process.md @@ -8,7 +8,15 @@ To release a new version of SCM-Manager v2 you have to do the following steps (r Make sure you have no changes you want to keep! ``` -git fetch && git checkout develop && git reset --hard origin/develop +git fetch && git checkout -f origin/develop && git clean -fd && git checkout -B develop +``` + +## Merge support branch + +Check whether there is an integration branch for the previous release or bugfixes not merged into the develop branch. Merge them now. + +``` +git merge origin/support/ ``` ## Modify Changelog @@ -51,7 +59,15 @@ To release a new version of a Plugin for SCM-Manager v2 you have to do the follo Make sure you have no changes you want to keep! ``` -git fetch && git checkout develop && git reset --hard origin/develop +git fetch && git checkout -f origin/develop && git clean -fd && git checkout -B develop +``` + +## Merge support branch + +Check whether there is an integration branch for the previous release or bugfixes not merged into the develop branch. Merge them now. + +``` +git merge origin/support/ ``` ## Update SCM parent if necessary @@ -61,40 +77,32 @@ If you need to update the parent of the plugin to a new release of SCM-Manager, - `pom.xml`: `parent.version` - `package.json`: `dependencies.ui-plugins` -``` -rm -rf node_modules yarn.lock -mvn clean install -git add yarn.lock pom.xml package.json -git commit -m "Update to new version of SCM-Manager" -git push -``` - ## Plugin dependencies Check if all plugin dependencies are proper versions and not SNAPSHOT! +## Build, commit and push + +``` +rm -rf node_modules yarn.lock && mvn clean install \ +&& git add yarn.lock pom.xml package.json \ +&& git commit -m "Update to new version of SCM-Manager" \ +&& git push origin develop +``` + Wait for Jenkins to be green. -## Create release branch - -``` -git checkout -b release/ -``` - ## Modify Changelog Change "Unreleased" header in `CHANGELOG.md` to ` - ` -## Commit and push release +## Create, commit and push release branch ``` -git commit -am "Prepare release of " -``` - -## Push release branch - -``` -git push origin release/ +export VERSION= \ +&& git checkout -b release/$VERSION \ +&& git commit -am "Prepare release of $VERSION" \ +&& git push origin release/$VERSION ``` ## Wait for Jenkins build diff --git a/docs/en/rv-plugin-comparison.md b/docs/en/rv-plugin-comparison.md deleted file mode 100644 index a0b954b33c..0000000000 --- a/docs/en/rv-plugin-comparison.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Revision Control Plugin Comparison ---- - -| Plugin Name | Name | Core-Plugin | Requirements | Url | -| --- | --- | --- | --- | --- | -| scm-svn-plugin | Subversion | X | | | -| scm-git-plugin | Git | X | | | -| scm-hg-plugin | Mercurial | X | [Python (v.2.6)](http://www.python.org/getit/releases/2.6), Mercurial | | -| scm-bzr-plugin | Bazaar | | Python, Bazaar | | diff --git a/docs/en/state-of-development.md b/docs/en/state-of-development.md deleted file mode 100644 index ad448a7423..0000000000 --- a/docs/en/state-of-development.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: State of SCM-Manager 2 development ---- - -The development of SCM-Manager 2.0.0 is organised in [Trello Boards](https://trello.com/scmmanager). - -## [Milestone 1](https://trello.com/b/oit1MD92/scm-manager-2-0-0-milestone-1) - -### Main goals -* remove deprecated and unused stuff -* remove old style listeners -* replace [guava eventbus](https://code.google.com/p/guava-libraries/wiki/EventBusExplained) with [legman](https://github.com/sdorra/legman) -* introduce new plugin structure -* offline plugin installation/updates/deinstallation -* use java 7 as default -* use of [annotation processors](http://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Processor.html) instead of classpath scanning -* move non core modules (plugin-backend, maven plugins, etc.) to separate repositories - -## [Milestone 2](https://trello.com/b/Afb3hoJ9/scm-manager-2-0-0-milestone-2) - -### Main goals -* use [apache shiro](http://shiro.apache.org/) everywhere -* improve authentication -* improve user and group management -* use permission instead of roles - -## [Milestone 3](https://trello.com/b/eLvqTGGe/scm-manager-2-0-0-milestone-3) - -### Main goals -* completely new designed rest api - -## Milestone 4 - -### Main goals -* completely new user interface - -## Milestone 5 - -### Main goals -* improve repository api diff --git a/docs/en/user/admin/assets/administration-information.png b/docs/en/user/admin/assets/administration-information.png new file mode 100644 index 0000000000..64ffd1d7a8 Binary files /dev/null and b/docs/en/user/admin/assets/administration-information.png differ diff --git a/docs/en/user/admin/assets/administration-permissionRoles.png b/docs/en/user/admin/assets/administration-permissionRoles.png new file mode 100644 index 0000000000..4469b0030d Binary files /dev/null and b/docs/en/user/admin/assets/administration-permissionRoles.png differ diff --git a/docs/en/user/admin/assets/administration-plugins-available.png b/docs/en/user/admin/assets/administration-plugins-available.png new file mode 100644 index 0000000000..d424420456 Binary files /dev/null and b/docs/en/user/admin/assets/administration-plugins-available.png differ diff --git a/docs/en/user/admin/assets/administration-plugins-installed.png b/docs/en/user/admin/assets/administration-plugins-installed.png new file mode 100644 index 0000000000..d7a7e62d0f Binary files /dev/null and b/docs/en/user/admin/assets/administration-plugins-installed.png differ diff --git a/docs/en/user/admin/assets/administration-settings-general.png b/docs/en/user/admin/assets/administration-settings-general.png new file mode 100644 index 0000000000..c29652e101 Binary files /dev/null and b/docs/en/user/admin/assets/administration-settings-general.png differ diff --git a/docs/en/user/admin/index.md b/docs/en/user/admin/index.md new file mode 100644 index 0000000000..7c5f30c2cb --- /dev/null +++ b/docs/en/user/admin/index.md @@ -0,0 +1,14 @@ +--- +title: Administration +partiallyActive: true +--- +The SCM-Manager instance can be administered in the Administration area. From here, plugins can be managed, permission roles can be defined and adjusted, and settings can be made. + +* [Plugins](plugins/) +* [Permission Roles](roles/) +* [Settings](settings/) + +### Information +On the information page in the administration area you can find the version of your SCM-Manager instance and helpful links to get in touch with the SCM-Manager support team. + +![Administration-Information](assets/administration-information.png) diff --git a/docs/en/user/admin/plugins.md b/docs/en/user/admin/plugins.md new file mode 100644 index 0000000000..07cc6676eb --- /dev/null +++ b/docs/en/user/admin/plugins.md @@ -0,0 +1,19 @@ +--- +title: Administration +subtitle: Plugins +--- +In the plugins section, plugins for SCM-Manager can be managed with the help of the external plugin center. Plugins are distinguished between installed and available plugins and are grouped based on their main functionality like for example workflow or authentication. + +Plugins can be managed by functionality icons on the tiles. System relevant plugins that come with SCM-Manager by default cannot be deinstalled or updated. + +In order for changes to plugins to become effective, the SCM-Manager server needs to be restarted. That can be done after every single action. It is also possible to queue several actions like the installation of a new plugin, updates or the deletion of a plugin and to perform all actions with one restart. If an action (installation, deinstallation, update) for a plugin was performed, the buttons "Execute changes" and "Abort changes" appear. If you choose to execute the changes, a popup window that shows the current queue (all actions without a restart) appears. Now the user can decide whether to execute the changes by restarting the server. If there are actions in the queue that are no longer desired, the queue can be emptied with the about changes button. + +### Installed +The overview for installed plugins shows all plugins that are currently installed on the SCM-Manager instance. Plugins that are optional can be deinstalled or updated here. + +![Administration-Plugins-Installed](assets/administration-plugins-installed.png) + +### Available +The overview of all available plugins shows all plugins that are compatible with the current version of the SCM-Manager instance that are available through the SCM-plugin-center. The plugins can be downloaded by clicking on the icon and will be installed after a restart of the SCM-Manager server. + +![Administration-Plugins-Available](assets/administration-plugins-available.png) diff --git a/docs/en/user/admin/roles.md b/docs/en/user/admin/roles.md new file mode 100644 index 0000000000..7b95407c85 --- /dev/null +++ b/docs/en/user/admin/roles.md @@ -0,0 +1,11 @@ +--- +title: Administration +subtitle: Permission Roles +--- +The page "Permission Roles" can be used to create profiles with certain permissions. Each role gets a name and a set of permissions. Roles can be assigned to users and groups for repositories. + +There are a few predefined system roles which cannot be changed or deleted. + +By clicking on a role you can see the information about it and which permissions the role has. User-defined roles can be edited or deleted at any time. + +![Administration-PermissionRoles](assets/administration-permissionRoles.png) diff --git a/docs/en/user/admin/settings.md b/docs/en/user/admin/settings.md new file mode 100644 index 0000000000..6f45422ca3 --- /dev/null +++ b/docs/en/user/admin/settings.md @@ -0,0 +1,59 @@ +--- +title: Administration +subtitle: Settings +displayToc: true +--- +The settings area is where the global configuration of SCM-Manager happens. Many SCM-Manager plugins that need some kind of configuration get an area in the settings. This makes it maximally configurable if required. + +### General +#### Realm Description +The field "Realm Description" is used to set the authentication realm for logins with basic authentication. + +#### Namespace Strategy +SCM-Manager groups repositories by namespaces. This grouping can be based on different strategies: + +* Username: The username of the user that creates the repository is used. +* Current year: The current year is used. +* Repository type: The type (Git, HG, SVN) is used. +* Custom: The namespace can be freely set when creating the repository. + +#### Login Info URL +The login screen of SCM-Manager shows helpful plugins and features. If you want to show your own information in the login screen, you can change the URL here. + +#### Enable XSRF Protection +Activate this option to make attacks using cross site scripting (XSS / XSRF) on SCM-Manager more difficult. This feature is still in an experimental state. + +#### Plugin Center URL +A plugin center can be used to conveniently manage plugins. If you want to use a plugin center that is not the default one, you only have to change this URL. If SCM-Manager is operated as part of a Cloudogu EcoSystem, the plugin center URL can be changed in the etcd. + +#### Enable Anonymous Access +In SCM-Manager 2 the access for anonymous access is realized by using an "_anonymous" user. When the feature is activated, a new user with the name "_anonymous" is created. This user can be authorized just like any other user. This user is used for access to SCM-Manager without login credentials (this does not apply to access via web UI). + +Example: If anonymous access is enabled and the "_anonymous" user has full access on a certain Git repository, everybody can access this repository via command line and the classic Git commands without any login credentials. Access via SSH is not supported at this time. + +#### Login Attempt Limit +It can be configured how many failed login attempts a user can have before the account gets disabled. The counter for failed login attempts is reset after a successful login. This feature can be deactivated by setting the value "-1". + +#### Login Attempt Limit Timeout +If the deactivation of accounts after too many unsuccessful login attempts is activated. This timeout can be used to set for how long accounts will be disabled. + +#### Base URL +The base URL represents the URL that a SCM-Manager instance is available at. The checkbox "Force Base URL" can be used to force redirects to the base URL. If activated, access to this application through different URLs are redirected to the base URL. + +Example: The base URL is www.scm.manager.com/scm but there is a second URL www.scm-manager.io/scm. Both URLs point to the same SCM-Manager instance. If the redirect to the base URL is forced, access via www.scm-manager.io/scm is redirected directly to www.scm-manager.com/scm. + +**Warning:** If the base URL is set to a faulty value and "Force Base URL" is activated, the SCM-Manager instance is no longer accessible. + +#### Proxy Settings +If the SCM-Manager instance is behind a proxy server, the connection can be configured here. + +![Administration-Configuration](assets/administration-settings-general.png) + +### Mercurial +The global settings for the source code management tool "Mercurial", hg for short, can be configured here. + +### Git +The global settings for the source code management tool "Git", can be configured here. + +### Subversion +The global settings for the source code management tool "Subversion", svn for short, can be configured here. diff --git a/docs/en/user/group/assets/create-group.png b/docs/en/user/group/assets/create-group.png new file mode 100644 index 0000000000..efd510685e Binary files /dev/null and b/docs/en/user/group/assets/create-group.png differ diff --git a/docs/en/user/group/assets/external-cas-group.png b/docs/en/user/group/assets/external-cas-group.png new file mode 100644 index 0000000000..40d953fd71 Binary files /dev/null and b/docs/en/user/group/assets/external-cas-group.png differ diff --git a/docs/en/user/group/assets/groups-information.png b/docs/en/user/group/assets/groups-information.png new file mode 100644 index 0000000000..b964c07fea Binary files /dev/null and b/docs/en/user/group/assets/groups-information.png differ diff --git a/docs/en/user/group/assets/groups-overview.png b/docs/en/user/group/assets/groups-overview.png new file mode 100644 index 0000000000..1d88775b5f Binary files /dev/null and b/docs/en/user/group/assets/groups-overview.png differ diff --git a/docs/en/user/group/assets/groups-settings-general.png b/docs/en/user/group/assets/groups-settings-general.png new file mode 100644 index 0000000000..68dadb83c2 Binary files /dev/null and b/docs/en/user/group/assets/groups-settings-general.png differ diff --git a/docs/en/user/group/assets/groups-settings-permissions.png b/docs/en/user/group/assets/groups-settings-permissions.png new file mode 100644 index 0000000000..5bcdb69769 Binary files /dev/null and b/docs/en/user/group/assets/groups-settings-permissions.png differ diff --git a/docs/en/user/group/external.md b/docs/en/user/group/external.md new file mode 100644 index 0000000000..718f49b675 --- /dev/null +++ b/docs/en/user/group/external.md @@ -0,0 +1,9 @@ +--- +title: Groups +subtitle: External groups (CAS) +--- +Users who log on to SCM-Manager via a CAS (Central Authentication Service) instance receive additional groups from the CAS. External groups can be used without manual creation in SCM-Manager. However, adding the groups marked as "external" has the advantages that the groups created are taken into account in the auto-completion and authorizations can be assigned directly to an external group. Which external groups are assigned to a user can be read out on the profile page of a registered user. + +See example: CasGroup + +![External-CAS-Group](assets/external-cas-group.png) diff --git a/docs/en/user/group/index.md b/docs/en/user/group/index.md new file mode 100644 index 0000000000..7eda34d067 --- /dev/null +++ b/docs/en/user/group/index.md @@ -0,0 +1,25 @@ +--- +title: Groups +partiallyActive: true +--- +The group area includes everything that can be broken down into a group of several users and their authorizations. + +* [External groups (CAS)](external/) +* [Settings](settings/) + +It is possible to create groups in SCM-Manager to not authorize each user individually. Groups can be authorized and can contain an unlimited number of users. + +### Overview +The groups overview shows a list of all groups that are existing. Groups are distinguished between internal and external groups. Internal groups are groups that were created in SCM-Manager and are indicated by a house icon. External groups were created by external sources like a connected LDAP instance and have a world icon. + +![Groups Overview](assets/groups-overview.png) + +### Create a Group +Groups only require a name. It is also possible to add members to a group directly when they are created. Only the permissions of the group have to be added afterwards. + +![Create Group](assets/create-group.png) + +### Group Information +The information page of a group shows the meta data as well as the members. + +![Group-Information](assets/groups-information.png) diff --git a/docs/en/user/group/settings.md b/docs/en/user/group/settings.md new file mode 100644 index 0000000000..dbef35b371 --- /dev/null +++ b/docs/en/user/group/settings.md @@ -0,0 +1,13 @@ +--- +title: Groups +subtitle: Settings +--- +### General +In the general settings page of a group the description and the members can be edited. Also, it is possible to delete a group here. The deletion is irreversible. + +![General Group Settings](assets/groups-settings-general.png) + +### Permissions +Groups can get global and repository-specific permissions. All permissions of a group are valid for all its members. + +![Group Permissions](assets/groups-settings-permissions.png) diff --git a/docs/en/user/repo/assets/create-repository.png b/docs/en/user/repo/assets/create-repository.png new file mode 100644 index 0000000000..b9c99987fb Binary files /dev/null and b/docs/en/user/repo/assets/create-repository.png differ diff --git a/docs/en/user/repo/assets/repository-branch-detailView.png b/docs/en/user/repo/assets/repository-branch-detailView.png new file mode 100644 index 0000000000..7c390fae71 Binary files /dev/null and b/docs/en/user/repo/assets/repository-branch-detailView.png differ diff --git a/docs/en/user/repo/assets/repository-branches-overview.png b/docs/en/user/repo/assets/repository-branches-overview.png new file mode 100644 index 0000000000..e8dcdc9d4e Binary files /dev/null and b/docs/en/user/repo/assets/repository-branches-overview.png differ diff --git a/docs/en/user/repo/assets/repository-code-changesetDetails.png b/docs/en/user/repo/assets/repository-code-changesetDetails.png new file mode 100644 index 0000000000..056062eb96 Binary files /dev/null and b/docs/en/user/repo/assets/repository-code-changesetDetails.png differ diff --git a/docs/en/user/repo/assets/repository-code-changesetsView.png b/docs/en/user/repo/assets/repository-code-changesetsView.png new file mode 100644 index 0000000000..4b466543d6 Binary files /dev/null and b/docs/en/user/repo/assets/repository-code-changesetsView.png differ diff --git a/docs/en/user/repo/assets/repository-code-fileHistory.png b/docs/en/user/repo/assets/repository-code-fileHistory.png new file mode 100644 index 0000000000..d386c8291a Binary files /dev/null and b/docs/en/user/repo/assets/repository-code-fileHistory.png differ diff --git a/docs/en/user/repo/assets/repository-code-fileViewer.png b/docs/en/user/repo/assets/repository-code-fileViewer.png new file mode 100644 index 0000000000..622e7b0045 Binary files /dev/null and b/docs/en/user/repo/assets/repository-code-fileViewer.png differ diff --git a/docs/en/user/repo/assets/repository-code-sourcesView.png b/docs/en/user/repo/assets/repository-code-sourcesView.png new file mode 100644 index 0000000000..716d1496d2 Binary files /dev/null and b/docs/en/user/repo/assets/repository-code-sourcesView.png differ diff --git a/docs/en/user/repo/assets/repository-create-branch.png b/docs/en/user/repo/assets/repository-create-branch.png new file mode 100644 index 0000000000..0a85b7bf03 Binary files /dev/null and b/docs/en/user/repo/assets/repository-create-branch.png differ diff --git a/docs/en/user/repo/assets/repository-information.png b/docs/en/user/repo/assets/repository-information.png new file mode 100644 index 0000000000..d1fc33a0f5 Binary files /dev/null and b/docs/en/user/repo/assets/repository-information.png differ diff --git a/docs/en/user/repo/assets/repository-overview-branches.png b/docs/en/user/repo/assets/repository-overview-branches.png new file mode 100644 index 0000000000..bd71d1d6cd Binary files /dev/null and b/docs/en/user/repo/assets/repository-overview-branches.png differ diff --git a/docs/en/user/repo/assets/repository-overview-changesets.png b/docs/en/user/repo/assets/repository-overview-changesets.png new file mode 100644 index 0000000000..3130d770da Binary files /dev/null and b/docs/en/user/repo/assets/repository-overview-changesets.png differ diff --git a/docs/en/user/repo/assets/repository-overview-settings.png b/docs/en/user/repo/assets/repository-overview-settings.png new file mode 100644 index 0000000000..658bd1933e Binary files /dev/null and b/docs/en/user/repo/assets/repository-overview-settings.png differ diff --git a/docs/en/user/repo/assets/repository-overview-sources.png b/docs/en/user/repo/assets/repository-overview-sources.png new file mode 100644 index 0000000000..3ddc6ff598 Binary files /dev/null and b/docs/en/user/repo/assets/repository-overview-sources.png differ diff --git a/docs/en/user/repo/assets/repository-overview.png b/docs/en/user/repo/assets/repository-overview.png new file mode 100644 index 0000000000..565a31c1bd Binary files /dev/null and b/docs/en/user/repo/assets/repository-overview.png differ diff --git a/docs/en/user/repo/assets/repository-settings-general-git.png b/docs/en/user/repo/assets/repository-settings-general-git.png new file mode 100644 index 0000000000..422392f892 Binary files /dev/null and b/docs/en/user/repo/assets/repository-settings-general-git.png differ diff --git a/docs/en/user/repo/assets/repository-settings-permissionList.png b/docs/en/user/repo/assets/repository-settings-permissionList.png new file mode 100644 index 0000000000..4830051ad5 Binary files /dev/null and b/docs/en/user/repo/assets/repository-settings-permissionList.png differ diff --git a/docs/en/user/repo/assets/repository-settings-permissionOverview.png b/docs/en/user/repo/assets/repository-settings-permissionOverview.png new file mode 100644 index 0000000000..ef405f4512 Binary files /dev/null and b/docs/en/user/repo/assets/repository-settings-permissionOverview.png differ diff --git a/docs/en/user/repo/branches.md b/docs/en/user/repo/branches.md new file mode 100644 index 0000000000..8c39eae6f0 --- /dev/null +++ b/docs/en/user/repo/branches.md @@ -0,0 +1,22 @@ +--- +title: Repository +subtitle: Branches +--- +### Overview +The branches overview shows the branches that are already existing. By clicking on a branch, the details page of the branch is shown. + +The tag "Default" shows which branch is currently set as the default branch of the repository in SCM-Manager. The default branch is always shown first when opening the repository in SCM-Manager. + +The button "Create Branch" opens the form to create a new branch. + +![Branches Overview](assets/repository-branches-overview.png) + +### Create a Branch +New branches can be created with the "Create Branch" form. There, you have to choose the branch that the new branch will be branched from and to provide a name for the new branch. It is not possible to create branches in an empty Git repository. + +![Create Branch](assets/repository-create-branch.png) + +### Branch Details Page +This page shows some commands to work with the branch on the command line. + +![Branch Details Page](assets/repository-branch-detailView.png) diff --git a/docs/en/user/repo/code.md b/docs/en/user/repo/code.md new file mode 100644 index 0000000000..0f28634da8 --- /dev/null +++ b/docs/en/user/repo/code.md @@ -0,0 +1,42 @@ +--- +title: Repository +subtitle: Code +displayToc: true +--- +The "Code" section contains all information that refers to the code respectively the content of the repository. At the top of the page is the action bar, which can be used to navigate within the code section. + +### Sources +The sources overview shows the files and folders within the repository. If branches exist, it shows the sources for the selected branch. + +Below the action bar is a breadcrumb navigation that shows the path of the files that are displayed. By clicking on the different sections of the path it is possible to navigate (back) through the file structure of the repository. + +![Repository-Code-Sources](assets/repository-code-sourcesView.png) + +### Changesets +The changesets/commits overview shows the change history of the branch. Each entry represents a commit. + +The Details button leads to the content/changes of a changeset. + +The Sources button leads to the sources overview that shows the state from after this commit. + +![Repository-Code-Changesets](assets/repository-code-changesetsView.png) + +### Changeset Details +The details page of a changeset shows the metadata and all changes that are part of the changeset. The diffs are presented in the well-known format per file with syntax highlighting. + +![Repository-Code-Changesets](assets/repository-code-changesetDetails.png) + +### File Details +After clicking on a file in the sources, the details of the file are shown. Depending on the format of the file, there are different views: + +- Image file: The rendered image is shown. +- Markdown file: The rendered markdown is shown. The view can also be changed to a text view that is not rendered. +- Text based file: The content is shown. If available with syntax highlighting. +- Unsupported formats: A download button is shown. + +![Repository-Code-FileDetails](assets/repository-code-fileViewer.png) + +### File History +In the detailed file view there is a switch button in the upper right corner which allows to switch to the history view. The history shows all commits that changed the file. + +![Repository-Code-FileHistory](assets/repository-code-fileHistory.png) diff --git a/docs/en/user/repo/index.md b/docs/en/user/repo/index.md new file mode 100644 index 0000000000..40738ee3ae --- /dev/null +++ b/docs/en/user/repo/index.md @@ -0,0 +1,39 @@ +--- +title: Repository +partiallyActive: true +--- +The Repository area includes everything based on repositories in namespaces. This includes all operations on branches, the code and settings. + +* [Branches](branches/) +* [Code](code/) +* [Settings](settings/) + +### Overview +The repository overview screen shows all repositories sorted by namespaces. Each repository is shown as a tile. After clicking on the tile the readme screen of the repository is shown. + +![Repository Overview](assets/repository-overview.png) + +The search bar at the top can be used to filter the repositories. The search uses the name and description of the repositories. + +The different tabs like branches, changesets or sources of the repository can be accessed through the blue icons. + +Icon | Description +---|--- +![Repository Branches](assets/repository-overview-branches.png) | Opens the branches overview for the repository +![Repository Changesets](assets/repository-overview-changesets.png) | Opens the changeset overview for the repository +![Repository Sources](assets/repository-overview-sources.png) | Opens the sources overview for the repository +![Repository Settings](assets/repository-overview-settings.png) | Opens the settings for the repository + +### Create a Repository +In SCM-Manager new Git, Mercurial & Subversion (SVN) repositories can be created via a form that can be accessed via the "Create Repository" button. A valid name and the repository type are mandatory. + +Optionally, repositories can be initialized during the creation. That creates a standard branch (master or default) for Git and Mercurial repositories. Additionally, it performs a commit that creates a README.md. + +If the namespace strategy is set to custom, the namespace field is also mandatory. + +![Create Repository](assets/create-repository.png) + +### Repository Information +The information screen of repositories shows meta data about the repository. Amongst that are descriptions for the different options on how the repository can be used. + +![Repository Information](assets/repository-information.png) diff --git a/docs/en/user/repo/settings.md b/docs/en/user/repo/settings.md new file mode 100644 index 0000000000..ae92b5da33 --- /dev/null +++ b/docs/en/user/repo/settings.md @@ -0,0 +1,23 @@ +--- +title: Repository +subtitle: Settings +--- +By default, there are two items in the repository settings. Depending on additional plugins that are installed, there can be considerably more items. + +### General +The "General" item allows you to edit the additional information of the repository. Git repositories for example also have the option to change the default branch here. The default branch is the one that is used when working with the repository if no specific branch is selected. + +At the bottom of this screen is also a button to delete the repository from SCM-Manager. The deletion is irreversible. + +![Repository-Settings-General-Git](assets/repository-settings-general-git.png) + +### Permissions +Thanks to the finely granular permission concept of SCM-Manager, users and groups can be authorized based on definable roles or individual settings. Permissions can be granted globally or repository-specific. Global permissions are managed in the administration area of SCM-Manager. The following image shows repository-specific permissions. + +Permissions can be granted to groups or users. It is possible to manage each permission individually or to create roles that contain several permissions. Roles can be defined in the administration area. + +![Repository-Settings-PermissionOverview](assets/repository-settings-permissionOverview.png) + +To manage permissions individually, an "Advanced" dialog can be opened to manage every single permission. + +![Repository-Settings-PermissionList](assets/repository-settings-permissionList.png) diff --git a/docs/en/user/user/assets/create-user.png b/docs/en/user/user/assets/create-user.png new file mode 100644 index 0000000000..e84bccea58 Binary files /dev/null and b/docs/en/user/user/assets/create-user.png differ diff --git a/docs/en/user/user/assets/user-information.png b/docs/en/user/user/assets/user-information.png new file mode 100644 index 0000000000..842aa35d54 Binary files /dev/null and b/docs/en/user/user/assets/user-information.png differ diff --git a/docs/en/user/user/assets/user-overview.png b/docs/en/user/user/assets/user-overview.png new file mode 100644 index 0000000000..0d9d78e690 Binary files /dev/null and b/docs/en/user/user/assets/user-overview.png differ diff --git a/docs/en/user/user/assets/user-settings-general.png b/docs/en/user/user/assets/user-settings-general.png new file mode 100644 index 0000000000..eb6c0157e4 Binary files /dev/null and b/docs/en/user/user/assets/user-settings-general.png differ diff --git a/docs/en/user/user/assets/user-settings-password.png b/docs/en/user/user/assets/user-settings-password.png new file mode 100644 index 0000000000..5d5318741f Binary files /dev/null and b/docs/en/user/user/assets/user-settings-password.png differ diff --git a/docs/en/user/user/assets/user-settings-permissions.png b/docs/en/user/user/assets/user-settings-permissions.png new file mode 100644 index 0000000000..fabe952a70 Binary files /dev/null and b/docs/en/user/user/assets/user-settings-permissions.png differ diff --git a/docs/en/user/user/index.md b/docs/en/user/user/index.md new file mode 100644 index 0000000000..bf6223391b --- /dev/null +++ b/docs/en/user/user/index.md @@ -0,0 +1,24 @@ +--- +title: User +partiallyActive: true +--- +The user area includes everything that can be broken down to a single user and their permissions. + +* [Settings](settings/) + +### Overview +The user overview shows a list of all existing users. A page with details about a user can be accessed by clicking on the user. New users can be created with the "Create User" button. + +![User Overview](assets/user-overview.png) + +### Create User +The "Create User" form can be used to create new users in SCM-Manager. New users don’t have any permissions and should therefore be configured right after they were created. + +![Create User](assets/create-user.png) + +### User Details Page +The user details page shows the information about the user. + +The active box shows whether the user is able to use SCM-Manager. The type XML from the shown example indicates that the user was created in SCM-Manager. Users can also be created through external sources, for example based on the information from a LDAP instance that is connected through the LDAP plugin. + +![User-Information](assets/user-information.png) diff --git a/docs/en/user/user/settings.md b/docs/en/user/user/settings.md new file mode 100644 index 0000000000..ec37893bab --- /dev/null +++ b/docs/en/user/user/settings.md @@ -0,0 +1,21 @@ +--- +title: User +subtitle: Settings +--- +### General +In the general settings the display name, e-mail address and active status of an account can be edited. + +On the bottom is also a button to delete the user. The deletion is irreversible. + +![General User Settings](assets/user-settings-general.png) + +### Password +The password of a user can be changed in the password section. Users can also change their password themselves by clicking on the "Change password" link in the footer of SCM-Manager, under their username. That will take them to their settings where they can change their password, amongst others. + +![Change Password](assets/user-settings-password.png) + +### Permissions +In the permissions section, the global, therefore not repository-specific permissions, can be configured for the user. +There is a tooltip for each permission that provide some more details about the option. + +![User Permissions](assets/user-settings-permissions.png) diff --git a/lerna.json b/lerna.json index 405d16a397..e17c5bcb57 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "2.0.0-SNAPSHOT" + "version": "2.2.0-SNAPSHOT" } diff --git a/package.json b/package.json index f5194ee536..35682ca3de 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "webpack --mode=production --config=scm-ui/ui-scripts/src/webpack.config.js", "build:dev": "webpack --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js", - "test": "lerna run --scope '@scm-manager/ui-*' test", + "test": "lerna run --scope '@scm-manager/ui-*' --scope '@scm-manager/eslint-config' test", "typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck", "serve": "NODE_ENV=development webpack-dev-server --hot --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js", "deploy": "ui-scripts publish", diff --git a/pom.xml b/pom.xml index a6ce5bb7c3..db981176f6 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ sonia.scm scm pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -531,7 +531,7 @@ sonia.scm.maven smp-maven-plugin - 1.0.0-rc7 + 1.0.0 @@ -722,6 +722,7 @@ yarn.lock **/logback.ci.xml **/pkg/ourPackage/scm-source.properties + **/.vagrant/** SLASHSTAR_STYLE @@ -874,8 +875,8 @@ 1.6.2 - 9.4.28.v20200408 - 9.4.22.v20191022 + 9.4.30.v20200611 + 9.4.30.v20200611 1.2.0 diff --git a/scm-annotation-processor/pom.xml b/scm-annotation-processor/pom.xml index eafc3f5c58..d2c4b4656e 100644 --- a/scm-annotation-processor/pom.xml +++ b/scm-annotation-processor/pom.xml @@ -25,30 +25,30 @@ --> - + 4.0.0 - + sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT - + sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-annotation-processor - + - + - + sonia.scm scm-annotations - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT - + @@ -56,31 +56,31 @@ javax.ws.rs-api ${jaxrs.version} - + - + com.github.legman core ${legman.version} - + - + com.google.guava guava ${guava.version} - + - + org.kohsuke.metainf-services metainf-services 1.8 - + - + diff --git a/scm-annotations/pom.xml b/scm-annotations/pom.xml index dfc7b8d62a..ff9e074223 100644 --- a/scm-annotations/pom.xml +++ b/scm-annotations/pom.xml @@ -25,28 +25,28 @@ --> - + 4.0.0 - + sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-annotations - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-annotations - + - + com.google.inject guice ${guice.version} true - + diff --git a/scm-core/pom.xml b/scm-core/pom.xml index a03c1bb72a..ff62629f03 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -31,11 +31,11 @@ scm sonia.scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-core - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-core @@ -54,7 +54,7 @@ sonia.scm scm-annotations - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT @@ -221,7 +221,7 @@ sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT provided diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java index 95d02eba28..bd94f9e33f 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.Embedded; @@ -32,6 +32,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import java.time.Instant; +import java.util.List; @Getter @Setter @@ -58,6 +59,8 @@ public class ChangesetDto extends HalRepresentation { */ private String description; + private List contributors; + public ChangesetDto(Links links, Embedded embedded) { super(links, embedded); } diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/ContributorDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/ContributorDto.java new file mode 100644 index 0000000000..3d22db879a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/ContributorDto.java @@ -0,0 +1,37 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class ContributorDto { + private String type; + private PersonDto person; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index 6e770139df..e2c980db37 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -31,6 +31,7 @@ import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.List; @@ -79,6 +80,11 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { */ private List tags; + /** + * Trailers for this changeset like reviewers or co-authors + */ + private Collection contributors; + public Changeset() {} public Changeset(String id, Long date, Person author) @@ -225,6 +231,15 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { return tags; } + /** + * Returns collection of contributors for this changeset. + * @return collection of contributors + * @since 2.1.0 + */ + public Collection getContributors() { + return contributors; + } + /** * Returns true if the changeset is valid. * @@ -300,4 +315,37 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { this.tags = tags; } + /** + * Sets the collection of contributors. + * @param contributors collection of contributors + * @since 2.1.0 + */ + public void setContributors(Collection contributors) { + this.contributors = new ArrayList<>(contributors); + } + + /** + * Adds a contributor to the list of contributors. + * @param contributor contributor to add + * @since 2.1.0 + */ + public void addContributor(Contributor contributor) { + if (contributors == null) { + contributors = new ArrayList<>(); + } + contributors.add(contributor); + } + + /** + * Adds all contributors from the given collection to the list of contributors. + * @param contributors collection of contributor + * @since 2.1.0 + */ + public void addContributors(Collection contributors) { + if (this.contributors == null) { + this.contributors = new ArrayList<>(contributors); + } else { + this.contributors.addAll(contributors); + } + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/Contributor.java b/scm-core/src/main/java/sonia/scm/repository/Contributor.java new file mode 100644 index 0000000000..9616695638 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/Contributor.java @@ -0,0 +1,35 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import lombok.Value; + +import java.io.Serializable; + +@Value +public class Contributor implements Serializable { + private String type; + private Person person; +} diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index af4e65caf0..14fd4b4532 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; import javax.ws.rs.core.MediaType; @@ -71,13 +71,12 @@ public class VndMediaType { @SuppressWarnings("squid:S2068") public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX; public static final String PERMISSION_COLLECTION = PREFIX + "permissionCollection" + SUFFIX; - public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX; - public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX; public static final String NAMESPACE_STRATEGIES = PREFIX + "namespaceStrategies" + SUFFIX; public static final String ME = PREFIX + "me" + SUFFIX; public static final String SOURCE = PREFIX + "source" + SUFFIX; + public static final String ANNOTATE = PREFIX + "annotate" + SUFFIX; public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX; public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX; diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index bfb331ede5..2dddf39386 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -25,43 +25,43 @@ --> - + 4.0.0 - + sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT - + scm-dao-xml - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-dao-xml - + - + javax.servlet javax.servlet-api ${servlet.version} provided - + sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT - + - + sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT test - + - + diff --git a/scm-it/pom.xml b/scm-it/pom.xml index bf9b4e6d78..5206830d97 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -31,40 +31,40 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm scm-it war - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-it sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT test sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT tests test @@ -72,14 +72,14 @@ sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT test sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT tests test @@ -87,14 +87,14 @@ sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT test sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT tests test diff --git a/scm-packaging/deb/pom.xml b/scm-packaging/deb/pom.xml index 723d142b46..7a3d0c9b47 100644 --- a/scm-packaging/deb/pom.xml +++ b/scm-packaging/deb/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT deb deb - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT Packaging for Debian/Ubuntu deb @@ -46,7 +46,7 @@ sonia.scm scm-server - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT diff --git a/scm-packaging/deb/src/main/deb/control/postinst b/scm-packaging/deb/src/main/deb/control/postinst index 474eae5df9..0bd5ff952d 100644 --- a/scm-packaging/deb/src/main/deb/control/postinst +++ b/scm-packaging/deb/src/main/deb/control/postinst @@ -36,4 +36,8 @@ systemctl daemon-reload # enable and start the service sudo systemctl enable scm-server -sudo systemctl start scm-server + +# we start scm-manager after 5 seconds +# this is required, because if we install scm-manager with recommend java +# java is not fully setup if we ran our postint script +nohup sh -c "sleep 5; systemctl start scm-server" >/dev/null 2>&1 & diff --git a/scm-packaging/deb/src/main/fs/etc/default/scm-server b/scm-packaging/deb/src/main/fs/etc/default/scm-server index e942cfec7c..959947d33f 100644 --- a/scm-packaging/deb/src/main/fs/etc/default/scm-server +++ b/scm-packaging/deb/src/main/fs/etc/default/scm-server @@ -36,7 +36,7 @@ USER=scm export SCM_HOME=/var/lib/scm # force jvm path -JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" +# JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" # path to pid PIDFILE=/var/run/scm/scm.pid diff --git a/scm-packaging/deb/src/main/fs/etc/scm/server-config.xml b/scm-packaging/deb/src/main/fs/etc/scm/server-config.xml index b7a5a7efef..89f9d56ca4 100644 --- a/scm-packaging/deb/src/main/fs/etc/scm/server-config.xml +++ b/scm-packaging/deb/src/main/fs/etc/scm/server-config.xml @@ -57,6 +57,9 @@ + + + @@ -67,7 +70,8 @@ /scm - /var/webapp/scm-webapp.war + /var/webapp/scm-webapp.war + org.eclipse.jetty.servlet.Default.dirAllowed diff --git a/scm-packaging/docker/Dockerfile b/scm-packaging/docker/Dockerfile index 558222631f..86081a941d 100644 --- a/scm-packaging/docker/Dockerfile +++ b/scm-packaging/docker/Dockerfile @@ -22,6 +22,20 @@ # SOFTWARE. # +# we need to build mercurial by ourself, +# because we need mercurial with python 2 for now +# TODO remove if python3 support is available for scm-manager hg extensions +FROM adoptopenjdk/openjdk11:jdk-11.0.7_10-alpine-slim as build +WORKDIR /src +RUN apk add --upgrade alpine-sdk python2 python2-dev +RUN set -x \ + && wget https://www.mercurial-scm.org/release/mercurial-4.9.1.tar.gz \ + && tar xvfz mercurial-4.9.1.tar.gz \ + && rm -f mercurial-4.9.1.tar.gz +WORKDIR /src/mercurial-4.9.1 +RUN make build +RUN make PREFIX=/usr install-bin + FROM adoptopenjdk/openjdk11:jdk-11.0.7_10-alpine-slim ENV SCM_HOME=/var/lib/scm ENV CACHE_DIR=/var/cache/scm/work @@ -29,15 +43,20 @@ ENV CACHE_DIR=/var/cache/scm/work COPY . / RUN set -x \ - && apk add --no-cache mercurial bash \ + && apk add --no-cache python2 bash ca-certificates \ && addgroup -S -g 1000 scm \ && adduser -S -s /bin/false -G scm -h ${SCM_HOME} -D -H -u 1000 scm \ && mkdir -p ${SCM_HOME} ${CACHE_DIR} \ + && chmod +x /opt/scm-server/bin/scm-server \ && chown scm:scm ${SCM_HOME} ${CACHE_DIR} +# copy mercurial installation +COPY --from=build /usr/bin/hg /usr/bin/hg +COPY --from=build /usr/lib/python2.7 /usr/lib/python2.7 + WORKDIR "/opt/scm-server" VOLUME ["${SCM_HOME}", "${CACHE_DIR}"] EXPOSE 8080 USER scm -CMD [ "java", "-cp", "/etc/scm:/opt/scm-server/lib/*", "-Djava.awt.headless=true", "-Dlogback.configurationFile=logging.xml", "sonia.scm.server.ScmServerDaemon" ] +ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ] diff --git a/scm-packaging/docker/pom.xml b/scm-packaging/docker/pom.xml index 0d566a5421..1b3d10143b 100644 --- a/scm-packaging/docker/pom.xml +++ b/scm-packaging/docker/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT docker pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT diff --git a/scm-packaging/docker/src/main/fs/etc/scm/server-config.xml b/scm-packaging/docker/src/main/fs/etc/scm/server-config.xml index b7a5a7efef..89f9d56ca4 100644 --- a/scm-packaging/docker/src/main/fs/etc/scm/server-config.xml +++ b/scm-packaging/docker/src/main/fs/etc/scm/server-config.xml @@ -57,6 +57,9 @@ + + + @@ -67,7 +70,8 @@ /scm - /var/webapp/scm-webapp.war + /var/webapp/scm-webapp.war + org.eclipse.jetty.servlet.Default.dirAllowed diff --git a/scm-packaging/docker/src/main/fs/opt/scm-server/bin/scm-server b/scm-packaging/docker/src/main/fs/opt/scm-server/bin/scm-server new file mode 100755 index 0000000000..82f8a2f74b --- /dev/null +++ b/scm-packaging/docker/src/main/fs/opt/scm-server/bin/scm-server @@ -0,0 +1,6 @@ +#!/bin/sh +exec java -cp "/etc/scm:/opt/scm-server/lib/*" \ + -Djava.awt.headless=true \ + -Dlogback.configurationFile=logging.xml \ + $JAVA_OPTS $* \ + sonia.scm.server.ScmServerDaemon diff --git a/scm-packaging/helm/pom.xml b/scm-packaging/helm/pom.xml index 42b9f30ed5..3b2dff90f4 100644 --- a/scm-packaging/helm/pom.xml +++ b/scm-packaging/helm/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT helm helm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT 3.2.1 diff --git a/scm-packaging/helm/src/main/build/packageYaml.groovy b/scm-packaging/helm/src/main/build/packageYaml.groovy index 8338e52926..0bdc102a79 100644 --- a/scm-packaging/helm/src/main/build/packageYaml.groovy +++ b/scm-packaging/helm/src/main/build/packageYaml.groovy @@ -23,4 +23,4 @@ */ def file = new File(project.build.directory, "package.yml") -file << "type: helm\n" +file << "type: k8s\n" diff --git a/scm-packaging/pom.xml b/scm-packaging/pom.xml index 42b77b97a3..ff519e91a5 100644 --- a/scm-packaging/pom.xml +++ b/scm-packaging/pom.xml @@ -31,13 +31,13 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm.packaging scm-packaging pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT packages.scm-manager.org diff --git a/scm-packaging/release-yaml/pom.xml b/scm-packaging/release-yaml/pom.xml index 850202281a..694ca48fff 100644 --- a/scm-packaging/release-yaml/pom.xml +++ b/scm-packaging/release-yaml/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT release-yaml pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT diff --git a/scm-packaging/rpm/pom.xml b/scm-packaging/rpm/pom.xml index cf05e9538e..fa5743c9c5 100644 --- a/scm-packaging/rpm/pom.xml +++ b/scm-packaging/rpm/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT rpm rpm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT Packaging for RedHat/Centos/Fedora rpm @@ -46,7 +46,7 @@ sonia.scm scm-server - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT diff --git a/scm-packaging/rpm/src/main/fs/etc/default/scm-server b/scm-packaging/rpm/src/main/fs/etc/default/scm-server index 6ea672b267..4a5d2fe86c 100644 --- a/scm-packaging/rpm/src/main/fs/etc/default/scm-server +++ b/scm-packaging/rpm/src/main/fs/etc/default/scm-server @@ -36,7 +36,7 @@ USER=scm export SCM_HOME=/var/lib/scm # force jvm path -JAVA_HOME="/usr/lib/jvm/jre-11" +# JAVA_HOME="/usr/lib/jvm/jre-11" # path to pid PIDFILE=/var/run/scm/scm.pid diff --git a/scm-packaging/rpm/src/main/fs/etc/scm/server-config.xml b/scm-packaging/rpm/src/main/fs/etc/scm/server-config.xml index b7a5a7efef..89f9d56ca4 100644 --- a/scm-packaging/rpm/src/main/fs/etc/scm/server-config.xml +++ b/scm-packaging/rpm/src/main/fs/etc/scm/server-config.xml @@ -57,6 +57,9 @@ + + + @@ -67,7 +70,8 @@ /scm - /var/webapp/scm-webapp.war + /var/webapp/scm-webapp.war + org.eclipse.jetty.servlet.Default.dirAllowed diff --git a/scm-packaging/unix/pom.xml b/scm-packaging/unix/pom.xml index e82b1d3676..e4f72df8ef 100644 --- a/scm-packaging/unix/pom.xml +++ b/scm-packaging/unix/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT unix pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT diff --git a/scm-packaging/unix/src/main/build/packageYaml.groovy b/scm-packaging/unix/src/main/build/packageYaml.groovy index 8defc88027..57fdf10db9 100644 --- a/scm-packaging/unix/src/main/build/packageYaml.groovy +++ b/scm-packaging/unix/src/main/build/packageYaml.groovy @@ -35,5 +35,5 @@ def file = new File(project.build.directory, "package.yml") file << """ type: unix checksum: ${hashCode} -url: ${deploymentTarget}/${repository}/releases/sonia/scm/packaging/unix/${project.version}/${packageFile.name} +url: ${deploymentTarget}/repository/${repository}/sonia/scm/packaging/unix/${project.version}/unix-${project.version}-app.tar.gz """ diff --git a/scm-packaging/unix/src/main/fs/conf/server-config.xml b/scm-packaging/unix/src/main/fs/conf/server-config.xml index 46ab3eb514..08814ccf68 100644 --- a/scm-packaging/unix/src/main/fs/conf/server-config.xml +++ b/scm-packaging/unix/src/main/fs/conf/server-config.xml @@ -57,6 +57,9 @@ + + + @@ -67,7 +70,8 @@ /scm - /var/webapp/scm-webapp.war + /var/webapp/scm-webapp.war + org.eclipse.jetty.servlet.Default.dirAllowed diff --git a/scm-packaging/windows/pom.xml b/scm-packaging/windows/pom.xml index 956ccba075..3041009d2e 100644 --- a/scm-packaging/windows/pom.xml +++ b/scm-packaging/windows/pom.xml @@ -32,12 +32,12 @@ sonia.scm.packaging scm-packaging - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT windows pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT @@ -71,11 +71,11 @@ wget - https://github.com/winsw/winsw/releases/download/v2.8.0/WinSW.NETCore31.x64.exe + https://github.com/winsw/winsw/releases/download/v2.9.0/WinSW.NETCore31.x64.exe false scm-server.exe ${project.build.directory}/windows - ebb2bb0ab0746ff5a20f65c76855a71c53aa806eb55ebd08fd18ded51ea23b58 + 59d29a41652cfc9a564c9c05d77976391833a6fb686bce941ad89f8f8dff120b diff --git a/scm-packaging/windows/src/main/build/packageYaml.groovy b/scm-packaging/windows/src/main/build/packageYaml.groovy index f5f029f746..d1f672a23f 100644 --- a/scm-packaging/windows/src/main/build/packageYaml.groovy +++ b/scm-packaging/windows/src/main/build/packageYaml.groovy @@ -35,5 +35,5 @@ def file = new File(project.build.directory, "package.yml") file << """ type: windows checksum: ${hashCode} -url: ${deploymentTarget}/${repository}/releases/sonia/scm/packaging/unix/${project.version}/${packageFile.name} +url: ${deploymentTarget}/repository/${repository}/sonia/scm/packaging/windows/${project.version}/${packageFile.name} """ diff --git a/scm-packaging/windows/src/main/fs/conf/server-config.xml b/scm-packaging/windows/src/main/fs/conf/server-config.xml index b7a5a7efef..89f9d56ca4 100644 --- a/scm-packaging/windows/src/main/fs/conf/server-config.xml +++ b/scm-packaging/windows/src/main/fs/conf/server-config.xml @@ -57,6 +57,9 @@ + + + @@ -67,7 +70,8 @@ /scm - /var/webapp/scm-webapp.war + /var/webapp/scm-webapp.war + org.eclipse.jetty.servlet.Default.dirAllowed diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 988e0bcf0a..d4696c5ce8 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -31,13 +31,13 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm.plugins scm-plugins pom - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-plugins @@ -59,7 +59,7 @@ sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT provided @@ -68,7 +68,7 @@ sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT provided @@ -98,7 +98,7 @@ sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT test diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 01572012d1..846f96276c 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-git-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "license": "MIT", "main": "./src/main/js/index.ts", "scripts": { @@ -20,6 +20,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.2.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index fbfd5ff62f..6e4db9e3f3 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -31,7 +31,7 @@ scm-plugins sonia.scm.plugins - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-git-plugin diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java index 6e19e10d6f..0f08f54aaa 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -171,8 +171,8 @@ public class GitChangesetConverter implements Closeable long date = GitUtil.getCommitTime(commit); PersonIdent authorIndent = commit.getAuthorIdent(); - Person author = new Person(authorIndent.getName(), - authorIndent.getEmailAddress()); + PersonIdent committerIdent = commit.getCommitterIdent(); + Person author = createPersonFor(authorIndent); String message = commit.getFullMessage(); if (message != null) @@ -181,6 +181,9 @@ public class GitChangesetConverter implements Closeable } Changeset changeset = new Changeset(id, date, author, message); + if (!committerIdent.equals(authorIndent)) { + changeset.addContributor(new Contributor("Committed-by", createPersonFor(committerIdent))); + } if (parentList != null) { @@ -201,6 +204,9 @@ public class GitChangesetConverter implements Closeable return changeset; } + public Person createPersonFor(PersonIdent personIndent) { + return new Person(personIndent.getName(), personIndent.getEmailAddress()); + } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHunkParser.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHunkParser.java index 197c1ed03a..9b2abd378a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHunkParser.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHunkParser.java @@ -49,7 +49,7 @@ final class GitHunkParser { public List parse(String content) { List hunks = new ArrayList<>(); - try (Scanner scanner = new Scanner(content).useDelimiter("[\n\r\u2028\u2029\u0085]+")) { + try (Scanner scanner = new Scanner(content).useDelimiter("\n")) { while (scanner.hasNext()) { String line = scanner.next(); if (line.startsWith("@@")) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/update/GitV2UpdateStep.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/update/GitV2UpdateStep.java index 0bbb479ed6..d805fae53c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/update/GitV2UpdateStep.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/update/GitV2UpdateStep.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.update; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; @@ -38,6 +38,7 @@ import sonia.scm.version.Version; import javax.inject.Inject; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import static sonia.scm.version.Version.parse; @@ -60,7 +61,8 @@ public class GitV2UpdateStep implements UpdateStep { (repositoryId, path) -> { Repository repository = repositoryMetadataAccess.read(path); if (isGitDirectory(repository)) { - try (org.eclipse.jgit.lib.Repository gitRepository = build(path.resolve("data").toFile())) { + final Path effectiveGitPath = determineEffectiveGitFolder(path); + try (org.eclipse.jgit.lib.Repository gitRepository = build(effectiveGitPath.toFile())) { new GitConfigHelper().createScmmConfig(repository, gitRepository); } catch (IOException e) { throw new UpdateException("could not update repository with id " + repositoryId + " in path " + path, e); @@ -70,6 +72,18 @@ public class GitV2UpdateStep implements UpdateStep { ); } + public Path determineEffectiveGitFolder(Path path) { + Path bareGitFolder = path.resolve("data"); + Path nonBareGitFolder = bareGitFolder.resolve(".git"); + final Path effectiveGitPath; + if (Files.exists(nonBareGitFolder)) { + effectiveGitPath = nonBareGitFolder; + } else { + effectiveGitPath = bareGitFolder; + } + return effectiveGitPath; + } + private org.eclipse.jgit.lib.Repository build(File directory) throws IOException { return new FileRepositoryBuilder() .setGitDir(directory) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitHunkParserTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitHunkParserTest.java index fda99ed7bf..919adb7e88 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitHunkParserTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitHunkParserTest.java @@ -203,7 +203,7 @@ class GitHunkParserTest { DiffLine line1 = lines.next(); assertThat(line1.getOldLineNumber()).hasValue(10); assertThat(line1.getNewLineNumber()).hasValue(10); - assertThat(line1.getContent()).isEqualTo("indent_style = space"); + assertThat(line1.getContent()).isEqualTo("indent_style = space\r\r"); lines.next(); lines.next(); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 61c2009405..2a8c40db58 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -25,7 +25,6 @@ package sonia.scm.repository.spi; import com.google.common.io.Files; -import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -34,6 +33,7 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.Modifications; +import sonia.scm.repository.Person; import java.io.File; import java.io.IOException; @@ -271,6 +271,20 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("master", changesets.getBranchName()); } + @Test + public void shouldAppendCommitterAsContributor() { + LogCommandRequest request = new LogCommandRequest(); + request.setStartChangeset("fcd0ef1831e4002ac43ea539f4094334c79ea9ec"); + request.setEndChangeset("fcd0ef1831e4002ac43ea539f4094334c79ea9ec"); + + ChangesetPagingResult changesets = createCommand().getChangesets(request); + Changeset changeset = changesets.getChangesets().get(0); + + assertThat(changeset.getContributors()).hasSize(1); + assertThat(changeset.getContributors().iterator().next().getPerson()) + .isEqualTo(new Person("Sebastian Sdorra", "s.sdorra@ostfalia.de")); + } + private void setRepositoryHeadReference(String s) throws IOException { Files.write(s, repositoryHeadReferenceFile(), defaultCharset()); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/update/GitV2UpdateStepTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/update/GitV2UpdateStepTest.java new file mode 100644 index 0000000000..c207a4bf54 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/update/GitV2UpdateStepTest.java @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.update; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryLocationResolver; +import sonia.scm.update.UpdateStepRepositoryMetadataAccess; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.BiConsumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GitV2UpdateStepTest { + + @Mock + RepositoryLocationResolver locationResolver; + @Mock + RepositoryLocationResolver.RepositoryLocationResolverInstance locationResolverInstance; + @Mock + UpdateStepRepositoryMetadataAccess repositoryMetadataAccess; + + @InjectMocks + GitV2UpdateStep updateStep; + + @BeforeEach + void createDataDirectory(@TempDir Path temp) throws IOException { + Files.createDirectories(temp.resolve("data")); + } + + @BeforeEach + void initRepositoryFolder(@TempDir Path temp) { + when(locationResolver.forClass(Path.class)).thenReturn(locationResolverInstance); + when(repositoryMetadataAccess.read(temp)).thenReturn(new Repository("123", "git", "space", "X")); + doAnswer(invocation -> { + invocation.getArgument(0, BiConsumer.class).accept("123", temp); + return null; + }).when(locationResolverInstance).forAllLocations(any()); + } + + @Test + void shouldWriteConfigFileForBareRepositories(@TempDir Path temp) { + updateStep.doUpdate(); + + assertThat(temp.resolve("data").resolve("config")).exists(); + } + + @Test + void shouldWriteConfigFileForNonBareRepositories(@TempDir Path temp) throws IOException { + Files.createDirectories(temp.resolve("data").resolve(".git")); + + updateStep.doUpdate(); + + assertThat(temp.resolve("data").resolve("config")).doesNotExist(); + assertThat(temp.resolve("data").resolve(".git").resolve("config")).exists(); + } +} diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index f6bb8709f8..b36c0e6f29 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-hg-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "license": "MIT", "main": "./src/main/js/index.ts", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.2.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 0c231fda9f..b2b55e0ffc 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -31,7 +31,7 @@ sonia.scm.plugins scm-plugins - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-hg-plugin diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py index f64c566833..44cb661385 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py @@ -48,7 +48,7 @@ except ImportError: from mercurial import util _parsedate = util.parsedate -FILE_MARKER = b'' +FILE_MARKER = '' class File_Collector: @@ -56,13 +56,13 @@ class File_Collector: self.recursive = recursive self.structure = defaultdict(dict, ((FILE_MARKER, []),)) - def collect(self, paths, path = b"", dir_only = False): + def collect(self, paths, path = "", dir_only = False): for p in paths: if p.startswith(path): self.attach(self.extract_name_without_parent(path, p), self.structure, dir_only) def attach(self, branch, trunk, dir_only = False): - parts = branch.split(b'/', 1) + parts = branch.split('/', 1) if len(parts) == 1: # branch is a file if dir_only: trunk[parts[0]] = defaultdict(dict, ((FILE_MARKER, []),)) @@ -78,7 +78,7 @@ class File_Collector: def extract_name_without_parent(self, parent, name_with_parent): if len(parent) > 0: name_without_parent = name_with_parent[len(parent):] - if name_without_parent.startswith(b"/"): + if name_without_parent.startswith("/"): name_without_parent = name_without_parent[1:] return name_without_parent return name_with_parent @@ -91,11 +91,11 @@ class File_Object: self.sub_repository = None def get_name(self): - parts = self.path.split(b"/") + parts = self.path.split("/") return parts[len(parts) - 1] def get_parent(self): - idx = self.path.rfind(b"/") + idx = self.path.rfind("/") if idx > 0: return self.path[0:idx] return "" @@ -143,11 +143,11 @@ class File_Walker: def create_path(self, parent, path): if len(parent) > 0: - return parent + b"/" + path + return parent + "/" + path return path - def walk(self, structure, parent = b""): - sortedItems = sorted(structure.items(), key = lambda item: self.sortKey(item)) + def walk(self, structure, parent = ""): + sortedItems = sorted(structure.iteritems(), key = lambda item: self.sortKey(item)) for key, value in sortedItems: if key == FILE_MARKER: if value: @@ -162,9 +162,9 @@ class File_Walker: def sortKey(self, item): if (item[0] == FILE_MARKER): - return b"2" + return "2" else: - return b"1" + item[0] + return "1" + item[0] class SubRepository: url = None @@ -173,9 +173,9 @@ class SubRepository: def collect_sub_repositories(revCtx): subrepos = {} try: - hgsub = revCtx.filectx(b'.hgsub').data().split('\n') + hgsub = revCtx.filectx('.hgsub').data().split('\n') for line in hgsub: - parts = line.split(b'=') + parts = line.split('=') if len(parts) > 1: subrepo = SubRepository() subrepo.url = parts[1].strip() @@ -184,9 +184,9 @@ def collect_sub_repositories(revCtx): pass try: - hgsubstate = revCtx.filectx(b'.hgsubstate').data().split('\n') + hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n') for line in hgsubstate: - parts = line.split(b' ') + parts = line.split(' ') if len(parts) > 1: subrev = parts[0].strip() subrepo = subrepos[parts[1].strip()] @@ -219,31 +219,31 @@ class File_Printer: def print_directory(self, path): if not self.initial_path_printed or self.offset == 0 or self.shouldPrintResult(): self.initial_path_printed = True - format = b'%s/\n' + format = '%s/\n' if self.transport: - format = b'd%s/\0' + format = 'd%s/\0' self.writer.write( format % path) def print_file(self, path): self.result_count += 1 if self.shouldPrintResult(): file = self.revCtx[path] - date = b'0 0' - description = b'n/a' + date = '0 0' + description = 'n/a' if not self.disableLastCommit: linkrev = self.repo[file.linkrev()] - date = b'%d %d' % _parsedate(linkrev.date()) + date = '%d %d' % _parsedate(linkrev.date()) description = linkrev.description() - format = b'%s %i %s %s\n' + format = '%s %i %s %s\n' if self.transport: - format = b'f%s\n%i %s %s\0' + format = 'f%s\n%i %s %s\0' self.writer.write( format % (file.path(), file.size(), date, description) ) def print_sub_repository(self, path, subrepo): if self.shouldPrintResult(): - format = b'%s/ %s %s\n' + format = '%s/ %s %s\n' if self.transport: - format = b's%s/\n%s %s\0' + format = 's%s/\n%s %s\0' self.writer.write( format % (path, subrepo.revision, subrepo.url)) def visit(self, file): @@ -263,9 +263,9 @@ class File_Printer: def finish(self): if self.isTruncated(): if self.transport: - self.writer.write(b"t") + self.writer.write( "t") else: - self.writer.write(b"truncated") + self.writer.write("truncated") class File_Viewer: def __init__(self, revCtx, visitor): @@ -275,11 +275,11 @@ class File_Viewer: self.recursive = False def remove_ending_slash(self, path): - if path.endswith(b"/"): + if path.endswith("/"): return path[:-1] return path - def view(self, path = b""): + def view(self, path = ""): manifest = self.revCtx.manifest() if len(path) > 0 and path in manifest: self.visitor.visit(File_Object(False, path)) @@ -294,15 +294,15 @@ class File_Viewer: collector.collect(self.sub_repositories.keys(), p, True) walker.walk(collector.structure, p) -@command(b'fileview', [ - (b'r', b'revision', b'tip', b'revision to print'), - (b'p', b'path', b'', b'path to print'), - (b'c', b'recursive', False, b'browse repository recursive'), - (b'd', b'disableLastCommit', False, b'disables last commit description and date'), - (b's', b'disableSubRepositoryDetection', False, b'disables detection of sub repositories'), - (b't', b'transport', False, b'format the output for command server'), - (b'l', b'limit', 100, b'limit the number of results'), - (b'o', b'offset', 0, b'proceed from the given result number (zero based)'), +@command('fileview', [ + ('r', 'revision', 'tip', 'revision to print'), + ('p', 'path', '', 'path to print'), + ('c', 'recursive', False, 'browse repository recursive'), + ('d', 'disableLastCommit', False, 'disables last commit description and date'), + ('s', 'disableSubRepositoryDetection', False, 'disables detection of sub repositories'), + ('t', 'transport', False, 'format the output for command server'), + ('l', 'limit', 100, 'limit the number of results'), + ('o', 'offset', 0, 'proceed from the given result number (zero based)'), ]) def fileview(ui, repo, **opts): revCtx = scmutil.revsingle(repo, opts["revision"]) diff --git a/scm-plugins/scm-legacy-plugin/package.json b/scm-plugins/scm-legacy-plugin/package.json index abd99dc69a..395f888c0f 100644 --- a/scm-plugins/scm-legacy-plugin/package.json +++ b/scm-plugins/scm-legacy-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-legacy-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "license": "MIT", "main": "./src/main/js/index.tsx", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.2.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml index 0010f81b11..8069f82678 100644 --- a/scm-plugins/scm-legacy-plugin/pom.xml +++ b/scm-plugins/scm-legacy-plugin/pom.xml @@ -29,12 +29,12 @@ sonia.scm.plugins scm-plugins - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-legacy-plugin Support migrated repository urls and v1 passwords - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT smp diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index 8be18995df..66b453d0a9 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-svn-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "license": "MIT", "main": "./src/main/js/index.ts", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.2.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index b8a011ef85..154af84d44 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -31,7 +31,7 @@ scm-plugins sonia.scm.plugins - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-svn-plugin diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java index ed502338cc..e1ea9dde09 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -76,6 +76,7 @@ public class SvnRepositoryHandler private static final Logger logger = LoggerFactory.getLogger(SvnRepositoryHandler.class); + private SvnRepositoryHook hook; @Inject public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory, @@ -94,7 +95,8 @@ public class SvnRepositoryHandler // register hook if (eventFacade != null) { - FSHooks.registerHook(new SvnRepositoryHook(eventFacade, this)); + hook = new SvnRepositoryHook(eventFacade, this); + FSHooks.registerHook(hook); } else if (logger.isWarnEnabled()) { @@ -212,4 +214,12 @@ public class SvnRepositoryHandler String getRepositoryId(File directory) { return new SvnConfigHelper().getRepositoryId(directory); } + + @Override + public void close() throws IOException { + if (hook != null) { + FSHooks.unregisterHook(hook); + } + super.close(); + } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index 9282fd2487..bd12979dc2 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; @@ -59,6 +59,8 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory); + private SvnRepositoryHandler handler; + @Override protected void postSetUp() throws IOException, RepositoryPathNotFoundException { initMocks(this); @@ -82,7 +84,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver, null); + SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, facade, locationResolver, null); handler.init(contextProvider); @@ -95,16 +97,20 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { } @Test - public void getDirectory() { + public void getDirectory() throws IOException { when(factory.withType(any())).thenCallRealMethod(); SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, facade, locationResolver, null); - SvnConfig svnConfig = new SvnConfig(); - repositoryHandler.setConfig(svnConfig); + try { + SvnConfig svnConfig = new SvnConfig(); + repositoryHandler.setConfig(svnConfig); - initRepository(); - File path = repositoryHandler.getDirectory(repository.getId()); - assertEquals(repoPath.toString()+File.separator+ RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); + initRepository(); + File path = repositoryHandler.getDirectory(repository.getId()); + assertEquals(repoPath.toString() + File.separator + RepositoryDirectoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); + } finally { + repositoryHandler.close(); + } } } diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 5d44052667..5b4a881ad8 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -31,12 +31,12 @@ scm sonia.scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm scm-server - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index f73082968f..78b213f594 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -31,12 +31,12 @@ scm sonia.scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-test @@ -50,7 +50,7 @@ sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT diff --git a/scm-ui/babel-preset/package.json b/scm-ui/babel-preset/package.json index cb0ea1ac52..e0f0d08c0b 100644 --- a/scm-ui/babel-preset/package.json +++ b/scm-ui/babel-preset/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/babel-preset", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "license": "MIT", "description": "Babel configuration for scm-manager and its plugins", "main": "index.js", diff --git a/scm-ui/eslint-config/package.json b/scm-ui/eslint-config/package.json index ec7216fe5b..15d5283148 100644 --- a/scm-ui/eslint-config/package.json +++ b/scm-ui/eslint-config/package.json @@ -1,17 +1,21 @@ { "name": "@scm-manager/eslint-config", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "ESLint configuration for scm-manager and its plugins", - "main": "index.js", + "main": "src/index.js", "author": "Sebastian Sdorra ", "license": "MIT", "private": false, + "scripts": { + "test": "jest" + }, "prettier": "@scm-manager/prettier-config", "dependencies": { "@typescript-eslint/eslint-plugin": "^2.12.0", "@typescript-eslint/parser": "^2.12.0", "babel-eslint": "^10.0.3", - "eslint": "^6.5.1", + "eslint": "^7.2.0", + "eslint-config-airbnb-base": "^14.2.0", "eslint-config-prettier": "^6.4.0", "eslint-config-react-app": "^5.0.2", "eslint-plugin-flowtype": "^4.3.0", @@ -21,6 +25,9 @@ "eslint-plugin-react": "^7.16.0", "eslint-plugin-react-hooks": "^2.1.2" }, + "devDependencies": { + "jest": "^26.0.1" + }, "publishConfig": { "access": "public" } diff --git a/scm-ui/eslint-config/src/__resources__/AllowRootImport.tsx b/scm-ui/eslint-config/src/__resources__/AllowRootImport.tsx new file mode 100644 index 0000000000..049c911adf --- /dev/null +++ b/scm-ui/eslint-config/src/__resources__/AllowRootImport.tsx @@ -0,0 +1,30 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC } from "react"; +import { Button } from "@scm-manager/ui-components"; + +const SpecialButton: FC = () => ; + +export default SpecialButton; diff --git a/scm-ui/eslint-config/src/__resources__/AvoidSourceImport.tsx b/scm-ui/eslint-config/src/__resources__/AvoidSourceImport.tsx new file mode 100644 index 0000000000..f201cc63fb --- /dev/null +++ b/scm-ui/eslint-config/src/__resources__/AvoidSourceImport.tsx @@ -0,0 +1,30 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC } from "react"; +import Button from "@scm-manager/ui-components/src/buttons/Button"; + +const SpecialButton: FC = () => ; + +export default SpecialButton; diff --git a/scm-ui/eslint-config/src/__resources__/Node.js b/scm-ui/eslint-config/src/__resources__/Node.js new file mode 100644 index 0000000000..28df359b00 --- /dev/null +++ b/scm-ui/eslint-config/src/__resources__/Node.js @@ -0,0 +1,26 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +var path = require('path') +console.log('we do not use path') diff --git a/scm-ui/eslint-config/src/__resources__/Typescript.ts b/scm-ui/eslint-config/src/__resources__/Typescript.ts new file mode 100644 index 0000000000..bd0e5ce277 --- /dev/null +++ b/scm-ui/eslint-config/src/__resources__/Typescript.ts @@ -0,0 +1,26 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const path = require('path'); +console.log("from typscript"); diff --git a/scm-ui/eslint-config/src/__resources__/TypescriptWithJsx.tsx b/scm-ui/eslint-config/src/__resources__/TypescriptWithJsx.tsx new file mode 100644 index 0000000000..fd72adacb4 --- /dev/null +++ b/scm-ui/eslint-config/src/__resources__/TypescriptWithJsx.tsx @@ -0,0 +1,25 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +console.log('from Sample.tsx') diff --git a/scm-ui/eslint-config/src/index.js b/scm-ui/eslint-config/src/index.js new file mode 100644 index 0000000000..ac998d81fb --- /dev/null +++ b/scm-ui/eslint-config/src/index.js @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const rules = { + "prettier/prettier": "warn", + semi: ["error", "always"], + quotes: ["error", "double", "avoid-escape"], + "no-var": "error" +}; + +const nodeConfiguration = { + extends: ["airbnb-base", "plugin:prettier/recommended"], + rules: { + "no-console": "off", + ...rules + } +}; + +const restrictImportConfig = { + patterns: ["@scm-manager/*/*"] +}; + +const typescriptConfiguration = { + parser: "@typescript-eslint/parser", + extends: ["react-app", "plugin:@typescript-eslint/recommended"], + rules: { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/ban-ts-ignore": "warn", + "no-console": "error", + "jsx-a11y/href-no-hash": "off", + "no-restricted-imports": ["error", restrictImportConfig], + ...rules + } +}; + +module.exports = { + overrides: [ + { + files: ["*.test.js"], + env: { + node: true, + jest: true, + browser: false + }, + ...nodeConfiguration + }, + { + files: ["*.js"], + env: { + node: true, + browser: false + }, + ...nodeConfiguration + }, + { + files: ["*.test.ts", "*.test.tsx"], + env: { + node: true, + jest: true, + browser: false + }, + ...typescriptConfiguration + }, + { + files: ["*.ts", "*.tsx"], + env: { + node: false, + browser: true + }, + ...typescriptConfiguration + } + ] +}; diff --git a/scm-ui/eslint-config/src/index.test.js b/scm-ui/eslint-config/src/index.test.js new file mode 100644 index 0000000000..99acf09da9 --- /dev/null +++ b/scm-ui/eslint-config/src/index.test.js @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const { ESLint } = require("eslint"); +const path = require("path"); + +const eslint = new ESLint(); +const resource = path.join(__dirname, "__resources__"); + +const lint = async file => { + const results = await eslint.lintFiles([path.join(resource, file)]); + + const { messages } = results[0]; + + const warnings = messages.filter(m => m.severity === 1).map(m => m.ruleId); + const errors = messages.filter(m => m.severity === 2).map(m => m.ruleId); + return { + errors, + warnings + }; +}; + +const expectContains = (results, ...ids) => { + ids.forEach(id => expect(results).toContain(id)); +}; + +describe("should lint different file types", () => { + it("should lint tsx files", async () => { + const { errors, warnings } = await lint("TypescriptWithJsx.tsx"); + expectContains(errors, "no-console", "quotes", "semi"); + expectContains(warnings, "prettier/prettier"); + }); + + it("should lint js files", async () => { + const { errors, warnings } = await lint("Node.js"); + expectContains(errors, "no-var", "quotes", "semi"); + expectContains(warnings, "prettier/prettier"); + }); + + it("should lint ts files", async () => { + const { errors, warnings } = await lint("Typescript.ts"); + expectContains(errors, "no-console", "quotes"); + expectContains(warnings, "prettier/prettier"); + }); +}); + +describe("lint @scm-manager imports", () => { + it("should return an error for source imports", async () => { + const { errors } = await lint("AvoidSourceImport.tsx"); + expectContains(errors, "no-restricted-imports"); + }); + + it("should return no error for package imports", async () => { + const { errors, warnings } = await lint("AllowRootImport.tsx"); + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + }); +}); diff --git a/scm-ui/jest-preset/package.json b/scm-ui/jest-preset/package.json index 4384226cd9..86a50e5caa 100644 --- a/scm-ui/jest-preset/package.json +++ b/scm-ui/jest-preset/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/jest-preset", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "Jest presets for SCM-Manager and its plugins", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index f4d64a9951..86da9d94d7 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -32,13 +32,13 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT sonia.scm scm-ui war - 2.0.0-SNAPSHOT + 2.2.0-SNAPSHOT scm-ui diff --git a/scm-ui/prettier-config/package.json b/scm-ui/prettier-config/package.json index 5c5fce4869..12bdd0b638 100644 --- a/scm-ui/prettier-config/package.json +++ b/scm-ui/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/prettier-config", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "license": "MIT", "description": "Prettier configuration", "author": "Sebastian Sdorra ", diff --git a/scm-ui/tsconfig/package.json b/scm-ui/tsconfig/package.json index 9595f56444..be69821d18 100644 --- a/scm-ui/tsconfig/package.json +++ b/scm-ui/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/tsconfig", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "license": "MIT", "description": "TypeScript configuration", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index 8900a701e3..60f9437650 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-components", - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "description": "UI Components for SCM-Manager and its plugins", "main": "src/index.ts", "files": [ @@ -18,7 +18,7 @@ "update-storyshots": "jest --testPathPattern=\"storyshots.test.ts\" --collectCoverage=false -u" }, "devDependencies": { - "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-tests": "^2.1.0", "@storybook/addon-actions": "^5.2.3", "@storybook/addon-storyshots": "^5.2.3", "@storybook/react": "^5.2.3", @@ -34,7 +34,7 @@ "@types/react-select": "^2.0.19", "@types/react-syntax-highlighter": "^11.0.1", "@types/storybook__addon-storyshots": "^5.1.1", - "@types/styled-components": "^4.1.19", + "@types/styled-components": "^5.1.0", "enzyme-context": "^1.1.2", "enzyme-context-react-router-4": "^2.0.0", "fetch-mock": "^7.5.1", @@ -46,8 +46,8 @@ "worker-plugin": "^3.2.0" }, "dependencies": { - "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-extensions": "^2.1.0", + "@scm-manager/ui-types": "^2.1.0", "classnames": "^2.2.6", "date-fns": "^2.4.1", "gitdiff-parser": "^0.1.2", @@ -60,7 +60,7 @@ "react-markdown": "^4.0.6", "react-router-dom": "^5.1.2", "react-select": "^2.1.2", - "react-syntax-highlighter": "^11.0.2" + "react-syntax-highlighter": "https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7" }, "babel": { "presets": [ diff --git a/scm-ui/ui-components/src/CommaSeparatedList.tsx b/scm-ui/ui-components/src/CommaSeparatedList.tsx new file mode 100644 index 0000000000..a597ed0dc0 --- /dev/null +++ b/scm-ui/ui-components/src/CommaSeparatedList.tsx @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC } from "react"; +import { useTranslation } from "react-i18next"; + +const CommaSeparatedList: FC = ({ children }) => { + const [t] = useTranslation("commons"); + const childArray = React.Children.toArray(children); + return ( + <> + {childArray.map((p, i) => { + if (i === 0) { + return {p}; + } else if (i + 1 === childArray.length) { + return ( + + {" "} + {t("commaSeparatedList.lastDivider")} {p}{" "} + + ); + } else { + return , {p}; + } + })} + + ); +}; + +export default CommaSeparatedList; diff --git a/scm-ui/ui-components/src/DateFromNow.stories.tsx b/scm-ui/ui-components/src/Date.stories.tsx similarity index 68% rename from scm-ui/ui-components/src/DateFromNow.stories.tsx rename to scm-ui/ui-components/src/Date.stories.tsx index 8259bf6240..4d2fe16fbe 100644 --- a/scm-ui/ui-components/src/DateFromNow.stories.tsx +++ b/scm-ui/ui-components/src/Date.stories.tsx @@ -24,25 +24,41 @@ import React from "react"; import DateFromNow from "./DateFromNow"; import { storiesOf } from "@storybook/react"; +import DateShort from "./DateShort"; +import styled from "styled-components"; const baseProps = { timeZone: "Europe/Berlin", baseDate: "2019-10-12T13:56:42+02:00" }; -storiesOf("DateFromNow", module).add("Default", () => ( -
-

- -

-

- -

-

- -

-

- -

-
-)); +const dates = [ + "2009-06-30T18:30:00+02:00", + "2019-06-30T18:30:00+02:00", + "2019-10-12T13:56:40+02:00", + "2019-10-11T13:56:40+02:00" +]; + +const Wrapper = styled.div` + padding: 2rem; +`; + +storiesOf("Date", module) + .add("Date from now", () => ( + + {dates.map(d => ( +

+ +

+ ))} +
+ )) + .add("Short", () => ( + + {dates.map(d => ( +

+ +

+ ))} +
+ )); diff --git a/scm-ui/ui-components/src/DateElement.ts b/scm-ui/ui-components/src/DateElement.ts new file mode 100644 index 0000000000..5c4dd25afa --- /dev/null +++ b/scm-ui/ui-components/src/DateElement.ts @@ -0,0 +1,31 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import styled from "styled-components"; + +const DateElement = styled.time` + border-bottom: 1px dotted rgba(219, 219, 219); + cursor: help; +`; + +export default DateElement; diff --git a/scm-ui/ui-components/src/DateFromNow.tsx b/scm-ui/ui-components/src/DateFromNow.tsx index 6b68bd8bd2..8b5a6c0a37 100644 --- a/scm-ui/ui-components/src/DateFromNow.tsx +++ b/scm-ui/ui-components/src/DateFromNow.tsx @@ -21,109 +21,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; -import { withTranslation, WithTranslation } from "react-i18next"; -import { formatDistance, format, parseISO, Locale } from "date-fns"; -import { enUS, de, es } from "date-fns/locale"; -import styled from "styled-components"; -type LocaleMap = { - [key: string]: Locale; +import React, { FC } from "react"; +import useDateFormatter, { DateProps } from "./useDateFormatter"; +import DateElement from "./DateElement"; + +type Props = DateProps & { + className?: string; }; -type DateInput = Date | string; - -export const supportedLocales: LocaleMap = { - enUS, - en: enUS, - de, - es -}; - -type Props = WithTranslation & { - date?: DateInput; - timeZone?: string; - - /** - * baseDate is the date from which the distance is calculated, - * default is the current time (new Date()). This property - * is required to keep snapshots tests green over the time on - * ci server. - */ - baseDate?: DateInput; -}; - -type Options = { - addSuffix: boolean; - locale: Locale; - timeZone?: string; -}; - -const DateElement = styled.time` - border-bottom: 1px dotted rgba(219, 219, 219); - cursor: help; -`; - -export const chooseLocale = (language: string, languages?: string[]) => { - for (const lng of languages || []) { - const locale = supportedLocales[lng]; - if (locale) { - return locale; - } - } - - const locale = supportedLocales[language]; - if (locale) { - return locale; - } - - return enUS; -}; - -class DateFromNow extends React.Component { - getLocale = (): Locale => { - const { i18n } = this.props; - return chooseLocale(i18n.language, i18n.languages); - }; - - createOptions = () => { - const { timeZone } = this.props; - const options: Options = { - addSuffix: true, - locale: this.getLocale() - }; - if (timeZone) { - options.timeZone = timeZone; - } - return options; - }; - - toDate = (value: DateInput): Date => { - if (value instanceof Date) { - return value; - } - return parseISO(value); - }; - - getBaseDate = () => { - const { baseDate } = this.props; - if (baseDate) { - return this.toDate(baseDate); - } - return new Date(); - }; - - render() { - const { date } = this.props; - if (date) { - const isoDate = this.toDate(date); - const options = this.createOptions(); - const distance = formatDistance(isoDate, this.getBaseDate(), options); - const formatted = format(isoDate, "yyyy-MM-dd HH:mm:ss", options); - return {distance}; - } +const DateFromNow: FC = ({ className, ...dateProps }) => { + const formatter = useDateFormatter(dateProps); + if (!formatter) { return null; } -} -export default withTranslation()(DateFromNow); + return ( + + {formatter.formatDistance()} + + ); +}; + +export default DateFromNow; diff --git a/scm-ui/ui-components/src/DateShort.tsx b/scm-ui/ui-components/src/DateShort.tsx new file mode 100644 index 0000000000..d0da04cb87 --- /dev/null +++ b/scm-ui/ui-components/src/DateShort.tsx @@ -0,0 +1,46 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC } from "react"; +import useDateFormatter, { DateProps } from "./useDateFormatter"; +import DateElement from "./DateElement"; + +type Props = DateProps & { + className?: string; +}; + +const DateShort: FC = ({ className, ...dateProps }) => { + const formatter = useDateFormatter(dateProps); + if (!formatter) { + return null; + } + + return ( + + {formatter.formatShort()} + + ); +}; + +export default DateShort; diff --git a/scm-ui/ui-components/src/Icon.tsx b/scm-ui/ui-components/src/Icon.tsx index 77b6591529..91edfbfd10 100644 --- a/scm-ui/ui-components/src/Icon.tsx +++ b/scm-ui/ui-components/src/Icon.tsx @@ -30,6 +30,7 @@ type Props = { name: string; color: string; className?: string; + onClick?: () => void; }; export default class Icon extends React.Component { @@ -39,12 +40,12 @@ export default class Icon extends React.Component { }; render() { - const { title, iconStyle, name, color, className } = this.props; + const { title, iconStyle, name, color, className, onClick } = this.props; if (title) { return ( - + ); } - return ; + return ; } } diff --git a/scm-ui/ui-components/src/Image.tsx b/scm-ui/ui-components/src/Image.tsx index 570252fc89..56f32cde7d 100644 --- a/scm-ui/ui-components/src/Image.tsx +++ b/scm-ui/ui-components/src/Image.tsx @@ -27,6 +27,7 @@ import { withContextPath } from "./urls"; type Props = { src: string; alt: string; + title?: string; className?: string; }; @@ -40,8 +41,8 @@ class Image extends React.Component { }; render() { - const { alt, className } = this.props; - return {alt}; + const { alt, title, className } = this.props; + return {alt}; } } diff --git a/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx b/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx index 7a5b8e68dc..037bf0f73f 100644 --- a/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx +++ b/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx @@ -21,7 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import { isAnchorLink, isExternalLink, isLinkWithProtocol, createLocalLink } from "./MarkdownLinkRenderer"; +import { + isAnchorLink, + isExternalLink, + isLinkWithProtocol, + createLocalLink, + isInternalScmRepoLink +} from "./MarkdownLinkRenderer"; describe("test isAnchorLink", () => { it("should return true", () => { @@ -67,6 +73,18 @@ describe("test isLinkWithProtocol", () => { }); }); +describe("test isInternalScmRepoLink", () => { + it("should return true", () => { + expect(isInternalScmRepoLink("/repo/scmadmin/git/code/changeset/1234567")).toBe(true); + expect(isInternalScmRepoLink("/repo/scmadmin/git")).toBe(true); + }); + it("should return false", () => { + expect(isInternalScmRepoLink("repo/path/link")).toBe(false); + expect(isInternalScmRepoLink("/some/path/link")).toBe(false); + expect(isInternalScmRepoLink("#some-anchor")).toBe(false); + }); +}); + describe("test createLocalLink", () => { it("should handle relative links", () => { expectLocalLink("/src", "/src/README.md", "docs/Home.md", "/src/docs/Home.md"); diff --git a/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx b/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx index 8ae1bc6135..fc38bd388b 100644 --- a/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx +++ b/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx @@ -21,10 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, {FC} from "react"; -import {Link, useLocation} from "react-router-dom"; +import React, { FC } from "react"; +import { Link, useLocation } from "react-router-dom"; import ExternalLink from "./navigation/ExternalLink"; -import {withContextPath} from "./urls"; +import { withContextPath } from "./urls"; const externalLinkRegex = new RegExp("^http(s)?://"); export const isExternalLink = (link: string) => { @@ -35,6 +35,10 @@ export const isAnchorLink = (link: string) => { return link.startsWith("#"); }; +export const isInternalScmRepoLink = (link: string) => { + return link.startsWith("/repo/"); +}; + const linkWithProtcolRegex = new RegExp("^[a-z]+:"); export const isLinkWithProtocol = (link: string) => { return linkWithProtcolRegex.test(link); @@ -56,10 +60,10 @@ const normalizePath = (path: string) => { if (part === "..") { stack.pop(); } else if (part !== ".") { - stack.push(part) + stack.push(part); } } - const normalizedPath = stack.join("/") + const normalizedPath = stack.join("/"); if (normalizedPath.startsWith("/")) { return normalizedPath; } @@ -75,6 +79,9 @@ const isSubDirectoryOf = (basePath: string, currentPath: string) => { }; export const createLocalLink = (basePath: string, currentPath: string, link: string) => { + if (isInternalScmRepoLink(link)) { + return link; + } if (isAbsolute(link)) { return join(basePath, link); } @@ -102,7 +109,7 @@ type Props = LinkProps & { base: string; }; -const MarkdownLinkRenderer: FC = ({href, base, children}) => { +const MarkdownLinkRenderer: FC = ({ href, base, children }) => { const location = useLocation(); if (isExternalLink(href)) { return {children}; diff --git a/scm-ui/ui-components/src/MarkdownView.stories.tsx b/scm-ui/ui-components/src/MarkdownView.stories.tsx index 2c2e1cc9f4..54c87cc6f2 100644 --- a/scm-ui/ui-components/src/MarkdownView.stories.tsx +++ b/scm-ui/ui-components/src/MarkdownView.stories.tsx @@ -31,6 +31,7 @@ import MarkdownWithoutLang from "./__resources__/markdown-without-lang.md"; import MarkdownXmlCodeBlock from "./__resources__/markdown-xml-codeblock.md"; import MarkdownInlineXml from "./__resources__/markdown-inline-xml.md"; import MarkdownLinks from "./__resources__/markdown-links.md"; +import MarkdownCommitLinks from "./__resources__/markdown-commit-link.md"; import Title from "./layout/Title"; import { Subtitle } from "./layout"; import { MemoryRouter } from "react-router-dom"; @@ -52,4 +53,5 @@ storiesOf("MarkdownView", module) )) - .add("Links", () => ); + .add("Links", () => ) + .add("Commit Links", () => ); diff --git a/scm-ui/ui-components/src/MarkdownView.tsx b/scm-ui/ui-components/src/MarkdownView.tsx index 546d1610a6..e46c95370a 100644 --- a/scm-ui/ui-components/src/MarkdownView.tsx +++ b/scm-ui/ui-components/src/MarkdownView.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import React, { FC } from "react"; -import { withRouter, RouteComponentProps } from "react-router-dom"; +import { RouteComponentProps, withRouter } from "react-router-dom"; // @ts-ignore import Markdown from "react-markdown/with-html"; import { binder } from "@scm-manager/ui-extensions"; @@ -30,10 +30,11 @@ import ErrorBoundary from "./ErrorBoundary"; import SyntaxHighlighter from "./SyntaxHighlighter"; import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer"; import { create } from "./MarkdownLinkRenderer"; -import { useTranslation } from "react-i18next"; +import {useTranslation, WithTranslation, withTranslation} from "react-i18next"; import Notification from "./Notification"; +import { createTransformer } from "./remarkCommitLinksParser"; -type Props = RouteComponentProps & { +type Props = RouteComponentProps & WithTranslation & { content: string; renderContext?: object; renderers?: any; @@ -100,7 +101,7 @@ class MarkdownView extends React.Component { } render() { - const { content, renderers, renderContext, enableAnchorHeadings, skipHtml, basePath } = this.props; + const { content, renderers, renderContext, enableAnchorHeadings, skipHtml, basePath, t } = this.props; const rendererFactory = binder.getExtension("markdown-renderer-factory"); let rendererList = renderers; @@ -134,6 +135,7 @@ class MarkdownView extends React.Component { escapeHtml={skipHtml} source={content} renderers={rendererList} + astPlugins={[createTransformer(t)]} /> @@ -141,4 +143,4 @@ class MarkdownView extends React.Component { } } -export default withRouter(MarkdownView); +export default withRouter(withTranslation("repos")(MarkdownView)); diff --git a/scm-ui/ui-components/src/Tooltip.stories.tsx b/scm-ui/ui-components/src/Tooltip.stories.tsx index f6f4cbad5b..13cfb93228 100644 --- a/scm-ui/ui-components/src/Tooltip.stories.tsx +++ b/scm-ui/ui-components/src/Tooltip.stories.tsx @@ -32,24 +32,38 @@ import Button from "./buttons/Button"; const Wrapper = styled.div` margin: 2rem; - max-width: 400px; + width: 100%; `; const Spacing = styled.div` padding: 2em 6em; + width: 50%; + margin: 0 auto; `; const positions = ["right", "top", "left", "bottom"]; const message = "Heart of Gold"; +const mutltiline = `Characters: + +- Arthur Dent +- Ford Prefect +- Zaphod Beeblebrox +- Marvin the Paranoid Android +- Trillian +- Slartibartfast`; + +const shortMultiline = `* a +* b +* c`; + const RoutingDecorator = (story: () => ReactNode) => {story()}; storiesOf("Tooltip", module) .addDecorator(RoutingDecorator) - .addDecorator(storyFn => {storyFn()}) .add("Default", () => ( -
+ {positions.map(position => ( @@ -57,5 +71,27 @@ storiesOf("Tooltip", module) ))} -
+ + )) + .add("Multiline", () => ( + + {positions.map(position => ( + + + +

+

+ +

+ + + + + +`; + +exports[`Storyshots Changesets Commiter and Co-Authors with avatar 1`] = ` +
+
+
+
+ +
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Changesets Default 1`] = ` +
+
+
+
+
+
+
+
+

+ initialize repository +

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + +

+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Changesets With Committer 1`] = ` +
+
+
+
+
+
+
+
+

+ Change heading to "Heart Of Gold" +

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + + + commaSeparatedList.lastDivider + + changeset.contributors.committedBy + + + Zaphod Beeblebrox + + +

+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Changesets With Committer and Co-Author 1`] = ` +
+
+
+
+
+
+
+
+

+ The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive. The craft was stolen by then-President Zaphod Beeblebrox at the official launch of the ship, as he was supposed to be officiating the launch. Later, during the use of the Infinite Improbability Drive, the ship picked up Arthur Dent and Ford Prefect, who were floating unprotected in deep space in the same star sector, having just escaped the destruction of the same planet. +

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + + , + changeset.contributors.committedBy + + + Zaphod Beeblebrox + + + commaSeparatedList.lastDivider + + changeset.contributors.coAuthoredBy + + + Ford Prefect + + +

+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Changesets With avatar 1`] = ` +
+
+
+
+
+
+
+
+
+ SCM Administrator +
+
+
+

+ initialize repository +

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + +

+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Changesets With multiple Co-Authors 1`] = ` +
+
+
+
+
+
+
+
+

+ Added design docs +

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + + + commaSeparatedList.lastDivider + + changeset.contributors.coAuthoredBy + + + changeset.contributors.more + + +

+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Date Date from now 1`] = ` +

`; +exports[`Storyshots Date Short 1`] = ` +
+

+ +

+

+ +

+

+ +

+

+ +

+
+`; + exports[`Storyshots Diff Binaries 1`] = `
@@ -890,18 +2745,18 @@ exports[`Storyshots Diff Binaries 1`] = ` className="fas fa-angle-down has-text-inherit" /> Main.java modify
@@ -944,18 +2799,6 @@ exports[`Storyshots Diff Binaries 1`] = ` /> - - - - - @@ -1089,26 +2932,26 @@ exports[`Storyshots Diff Binaries 1`] = `
conflict.png add @@ -1119,7 +2962,7 @@ exports[`Storyshots Diff Binaries 1`] = ` className="panel-block is-paddingless" >
- -
@@ -1139,19 +2982,19 @@ exports[`Storyshots Diff Binaries 1`] = ` exports[`Storyshots Diff Collapsed 1`] = `
@@ -1159,18 +3002,18 @@ exports[`Storyshots Diff Collapsed 1`] = ` className="fas fa-angle-right has-text-inherit" /> src/main/java/com/cloudogu/scm/review/events/EventListener.java modify
@@ -1216,18 +3059,18 @@ exports[`Storyshots Diff Collapsed 1`] = ` className="fas fa-angle-right has-text-inherit" /> src/main/js/ChangeNotification.tsx modify
@@ -1273,18 +3116,18 @@ exports[`Storyshots Diff Collapsed 1`] = ` className="fas fa-angle-right has-text-inherit" /> src/main/resources/locales/de/plugins.json modify
@@ -1330,18 +3173,18 @@ exports[`Storyshots Diff Collapsed 1`] = ` className="fas fa-angle-right has-text-inherit" /> src/main/resources/locales/en/plugins.json modify
@@ -1619,18 +3462,6 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = ` /> - - - - - @@ -1817,7 +3648,7 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = ` colSpan={3} >
@@ -2400,16 +4231,16 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
- -
@@ -2471,18 +4302,6 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = ` /> - - - - - @@ -2848,16 +4667,16 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
- -
@@ -2919,18 +4738,6 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = ` /> - - - - - @@ -3296,16 +5103,16 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
- -
@@ -3488,18 +5295,6 @@ exports[`Storyshots Diff Default 1`] = ` /> - - - - - @@ -3992,16 +5787,16 @@ exports[`Storyshots Diff Default 1`] = `
- -
@@ -4063,18 +5858,6 @@ exports[`Storyshots Diff Default 1`] = ` /> - - - - - @@ -4261,7 +6044,7 @@ exports[`Storyshots Diff Default 1`] = ` colSpan={3} >
@@ -4844,16 +6627,16 @@ exports[`Storyshots Diff Default 1`] = `
- -
@@ -4915,18 +6698,6 @@ exports[`Storyshots Diff Default 1`] = ` /> - - - - - @@ -5292,16 +7063,16 @@ exports[`Storyshots Diff Default 1`] = `
- -
@@ -5363,18 +7134,6 @@ exports[`Storyshots Diff Default 1`] = ` /> - - - - - @@ -5740,16 +7499,16 @@ exports[`Storyshots Diff Default 1`] = `
- -
@@ -5811,18 +7570,6 @@ exports[`Storyshots Diff Default 1`] = ` /> - - - - - @@ -6326,7 +8073,7 @@ exports[`Storyshots Diff Default 1`] = ` colSpan={3} >
@@ -6540,7 +8287,7 @@ exports[`Storyshots Diff Default 1`] = ` colSpan={3} >
@@ -6795,16 +8542,16 @@ exports[`Storyshots Diff Default 1`] = `
- -
@@ -6866,18 +8613,6 @@ exports[`Storyshots Diff Default 1`] = ` /> - - - - - @@ -7320,21 +9055,21 @@ exports[`Storyshots Diff Default 1`] = ` `; -exports[`Storyshots Diff File Annotation 1`] = ` +exports[`Storyshots Diff Expandable 1`] = `
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 1 + + 1 + + package com.cloudogu.scm.review.events; +
+ 2 + + 2 + + +
+ 3 + + + import com.cloudogu.scm.review.comment.service.BasicComment; +
+ 4 + + + import com.cloudogu.scm.review.comment.service.BasicCommentEvent; +
+ 5 + + + import com.cloudogu.scm.review.comment.service.CommentEvent; +
+ 6 + + + import com.cloudogu.scm.review.comment.service.ReplyEvent; +
+ 7 + + 3 + + import com.cloudogu.scm.review.pullrequest.service.BasicPullRequestEvent; +
+ 8 + + 4 + + import com.cloudogu.scm.review.pullrequest.service.PullRequest; +
+ 9 + + + import com.cloudogu.scm.review.pullrequest.service.PullRequestEvent; +
+ 10 + + 5 + + import com.github.legman.Subscribe; +
+ 11 + + + import lombok.Data; +
+ 12 + + 6 + + import org.apache.shiro.SecurityUtils; +
+ 13 + + 7 + + import org.apache.shiro.subject.PrincipalCollection; +
+ 14 + + 8 + + import org.apache.shiro.subject.Subject; +
+ 15 + + 9 + + import sonia.scm.EagerSingleton; +
+ 16 + + + import sonia.scm.HandlerEventType; +
+ 17 + + + import sonia.scm.event.HandlerEvent; +
+ 18 + + 10 + + import sonia.scm.plugin.Extension; +
+ 19 + + 11 + + import sonia.scm.repository.Repository; +
+ 20 + + 12 + + import sonia.scm.security.SessionId; +
+
+ + + + diff.expandLastBottomByLines + + + + + + diff.expandLastBottomComplete + +
+
+
+
+
+
+
+
+ + + src/main/js/ChangeNotification.tsx + + + modify + +
+
+
+
+ + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + diff.expandComplete + +
+
+ 2 + + 2 + + import { Link } from "@scm-manager/ui-types"; +
+ 3 + + 3 + + import { apiClient, Toast, ToastButtons, ToastButton } from "@scm-manager/ui-components"; +
+ 4 + + 4 + + import { PullRequest } from "./types/PullRequest"; +
+ + 5 + + import { useTranslation } from "react-i18next"; +
+ 5 + + 6 + + +
+ 6 + + 7 + + type HandlerProps = { +
+ 7 + + 8 + + url: string; +
+
+ + + + diff.expandComplete + +
+
+
+ + + + diff.expandComplete + +
+
+ 15 + + 16 + + pullRequest: setEvent +
+ 16 + + 17 + + }); +
+ 17 + + 18 + + }, [url]); +
+ + 19 + + const { t } = useTranslation("plugins"); +
+ 18 + + 20 + + if (event) { +
+ 19 + + 21 + + return ( +
+ 20 + + + <Toast type="warning" title="New Changes"> +
+ 21 + + + <p>The underlying Pull-Request has changed. Press reload to see the changes.</p> +
+ 22 + + + <p>Warning: Non saved modification will be lost.</p> +
+ + 22 + + <Toast type="warning" title={t("scm-review-plugin.changeNotification.title")}> +
+ + 23 + + <p>{t("scm-review-plugin.changeNotification.description")}</p> +
+ + 24 + + <p>{t("scm-review-plugin.changeNotification.modificationWarning")}</p> +
+ 23 + + 25 + + <ToastButtons> +
+ 24 + + + <ToastButton icon="redo" onClick={reload}>Reload</ToastButton> +
+ 25 + + + <ToastButton icon="times" onClick={() => setEvent(undefined)}>Ignore</ToastButton> +
+ + 26 + + <ToastButton icon="redo" onClick={reload}> +
+ + 27 + + {t("scm-review-plugin.changeNotification.buttons.reload")} +
+ + 28 + + </ToastButton> +
+ + 29 + + <ToastButton icon="times" onClick={() => setEvent(undefined)}> +
+ + 30 + + {t("scm-review-plugin.changeNotification.buttons.ignore")} +
+ + 31 + + </ToastButton> +
+ 26 + + 32 + + </ToastButtons> +
+ 27 + + 33 + + </Toast> +
+ 28 + + 34 + + ); +
+
+ + + + diff.expandLastBottomByLines + + + + + + diff.expandLastBottomComplete + +
+
+
+
+
+
+
+
+ + + src/main/resources/locales/de/plugins.json + + + modify + +
+
+
+
+ + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + diff.expandByLines + + + + + + diff.expandComplete + +
+
+ 181 + + 181 + + "titleClickable": "Der Kommentar bezieht sich auf eine ältere Version des Source- oder Target-Branches. Klicken Sie hier, um den ursprünglichen Kontext zu sehen." +
+ 182 + + 182 + + } +
+ 183 + + 183 + + } +
+ + 184 + + }, +
+ + 185 + + "changeNotification": { +
+ + 186 + + "title": "Neue Änderungen", +
+ + 187 + + "description": "An diesem Pull Request wurden Änderungen vorgenommen. Laden Sie die Seite neu um diese anzuzeigen.", +
+ + 188 + + "modificationWarning": "Warnung: Nicht gespeicherte Eingaben gehen verloren.", +
+ + 189 + + "buttons": { +
+ + 190 + + "reload": "Neu laden", +
+ + 191 + + "ignore": "Ignorieren" +
+ + 192 + + } +
+ 184 + + 193 + + } +
+ 185 + + 194 + + }, +
+ 186 + + 195 + + "permissions": { +
+
+ + + + diff.expandLastBottomByLines + + + + + + diff.expandLastBottomComplete + +
+
+
+
+
+
+
+
+ + + src/main/resources/locales/en/plugins.json + + + modify + +
+
+
+
+ + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + diff.expandByLines + + + + + + diff.expandComplete + +
+
+ 181 + + 181 + + "titleClickable": "The comment is related to an older of the source or target branch. Click here to see the original context." +
+ 182 + + 182 + + } +
+ 183 + + 183 + + } +
+ + 184 + + }, +
+ + 185 + + "changeNotification": { +
+ + 186 + + "title": "New Changes", +
+ + 187 + + "description": "The underlying Pull-Request has changed. Press reload to see the changes.", +
+ + 188 + + "modificationWarning": "Warning: Non saved modification will be lost.", +
+ + 189 + + "buttons": { +
+ + 190 + + "reload": "Reload", +
+ + 191 + + "ignore": "Ignore" +
+ + 192 + + } +
+ 184 + + 193 + + } +
+ 185 + + 194 + + }, +
+ 186 + + 195 + + "permissions": { +
+
+ + + + diff.expandLastBottomByLines + + + + + + diff.expandLastBottomComplete + +
+
+
+
+
+
+
+
+ + + src/test/java/com/cloudogu/scm/review/events/ClientTest.java + + + modify + +
+
+
+
+ + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + diff.expandComplete + +
+
+ 7 + + 7 + + import org.mockito.Mock; +
+ 8 + + 8 + + import org.mockito.junit.jupiter.MockitoExtension; +
+ 9 + + 9 + + import sonia.scm.security.SessionId; +
+ + 10 + + +
+ 10 + + 11 + + import javax.ws.rs.sse.OutboundSseEvent; +
+ 11 + + 12 + + import javax.ws.rs.sse.SseEventSink; +
+ 12 + + + +
+ 13 + + 13 + + import java.time.Clock; +
+ 14 + + 14 + + import java.time.Instant; +
+ 15 + + 15 + + import java.time.LocalDateTime; +
+ 16 + + 16 + + import java.time.ZoneOffset; +
+ 17 + + 17 + + import java.time.temporal.ChronoField; +
+ 18 + + + import java.time.temporal.ChronoUnit; +
+ 19 + + + import java.time.temporal.TemporalField; +
+ 20 + + 18 + + import java.util.concurrent.CompletableFuture; +
+ 21 + + 19 + + import java.util.concurrent.CompletionStage; +
+ 22 + + + import java.util.concurrent.atomic.AtomicLong; +
+ 23 + + 20 + + import java.util.concurrent.atomic.AtomicReference; +
+ 24 + + 21 + + +
+ 25 + + 22 + + import static java.time.temporal.ChronoUnit.MINUTES; +
+
+ + + + diff.expandByLines + + + + + + diff.expandComplete + +
+
+
+ + + + diff.expandByLines + + + + + + diff.expandComplete + +
+
+ 83 + + 80 + + +
+ 84 + + 81 + + @Test +
+ 85 + + 82 + + @SuppressWarnings("unchecked") +
+ 86 + + + void shouldCloseEventSinkOnFailure() throws InterruptedException { +
+ + 83 + + void shouldCloseEventSinkOnFailure() { +
+ 87 + + 84 + + CompletionStage future = CompletableFuture.supplyAsync(() -> { +
+ 88 + + 85 + + throw new RuntimeException("failed to send message"); +
+ 89 + + 86 + + }); +
+
+ + + + diff.expandComplete + +
+
+
+ + + + diff.expandComplete + +
+
+ 91 + + 88 + + +
+ 92 + + 89 + + client.send(message); +
+ 93 + + 90 + + +
+ 94 + + + Thread.sleep(50L); +
+ 95 + + + +
+ 96 + + + verify(eventSink).close(); +
+ + 91 + + verify(eventSink, timeout(50L)).close(); +
+ 97 + + 92 + + } +
+ 98 + + 93 + + +
+ 99 + + 94 + + @Test +
+
+ + + + diff.expandLastBottomByLines + + + + + + diff.expandLastBottomComplete + +
+
+
+
+
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + 1 + + import java.io.PrintStream; +
+ 1 + + 2 + + import java.util.Arrays; +
+ 2 + + 3 + + +
+ 3 + + 4 + + class Main { +
+ + 5 + + private static final PrintStream OUT = System.out; +
+ + 6 + + +
+ 4 + + 7 + + public static void main(String[] args) { +
+ + 8 + + <<<<<<< HEAD +
+ 5 + + 9 + + System.out.println("Expect nothing more to happen."); +
+ 6 + + 10 + + System.out.println("The command line parameters are:"); +
+ 7 + + 11 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(System.out::println); +
+ + 12 + + ======= +
+ + 13 + + OUT.println("Expect nothing more to happen."); +
+ + 14 + + OUT.println("Parameters:"); +
+ + 15 + + Arrays.stream(args).map(arg -> "- " + arg).forEach(OUT::println); +
+ + 16 + + >>>>>>> feature/use_constant +
+ 8 + + 17 + + } +
+ 9 + + 18 + + } +
+
+ + + + diff.expandLastBottomByLines + + + + + + diff.expandLastBottomComplete + +
+
+
+
+
+`; + +exports[`Storyshots Diff File Annotation 1`] = ` +
+
+
+
+
+ + + src/main/java/com/cloudogu/scm/review/events/EventListener.java + + + modify + +
+
+
+
+ + @@ -7400,18 +13441,6 @@ exports[`Storyshots Diff File Annotation 1`] = ` /> - - - - - @@ -7904,16 +13933,16 @@ exports[`Storyshots Diff File Annotation 1`] = `
@@ -7921,18 +13950,18 @@ exports[`Storyshots Diff File Annotation 1`] = ` className="fas fa-angle-down has-text-inherit" /> src/main/js/ChangeNotification.tsx modify
- -
@@ -7979,18 +14008,6 @@ exports[`Storyshots Diff File Annotation 1`] = ` /> - - - - - @@ -8177,7 +14194,7 @@ exports[`Storyshots Diff File Annotation 1`] = ` colSpan={3} >
@@ -8760,16 +14777,16 @@ exports[`Storyshots Diff File Annotation 1`] = `
- -
@@ -8835,18 +14852,6 @@ exports[`Storyshots Diff File Annotation 1`] = ` /> - - - - - @@ -9212,16 +15217,16 @@ exports[`Storyshots Diff File Annotation 1`] = `
- -
@@ -9287,18 +15292,6 @@ exports[`Storyshots Diff File Annotation 1`] = ` /> - - - - - @@ -9664,16 +15657,16 @@ exports[`Storyshots Diff File Annotation 1`] = `
- -
@@ -9739,18 +15732,6 @@ exports[`Storyshots Diff File Annotation 1`] = ` /> - - - - - @@ -10254,7 +16235,7 @@ exports[`Storyshots Diff File Annotation 1`] = ` colSpan={3} >
@@ -10468,7 +16449,7 @@ exports[`Storyshots Diff File Annotation 1`] = ` colSpan={3} >
@@ -10723,16 +16704,16 @@ exports[`Storyshots Diff File Annotation 1`] = `
- -
@@ -10798,18 +16779,6 @@ exports[`Storyshots Diff File Annotation 1`] = ` /> - - - - - @@ -11254,19 +17223,19 @@ exports[`Storyshots Diff File Annotation 1`] = ` exports[`Storyshots Diff File Controls 1`] = `
- -
@@ -11346,18 +17315,6 @@ exports[`Storyshots Diff File Controls 1`] = ` /> - - - - - @@ -11850,16 +17807,16 @@ exports[`Storyshots Diff File Controls 1`] = `
- -
@@ -11939,18 +17896,6 @@ exports[`Storyshots Diff File Controls 1`] = ` /> - - - - - @@ -12137,7 +18082,7 @@ exports[`Storyshots Diff File Controls 1`] = ` colSpan={3} >
@@ -12720,16 +18665,16 @@ exports[`Storyshots Diff File Controls 1`] = `
- -
@@ -12809,18 +18754,6 @@ exports[`Storyshots Diff File Controls 1`] = ` /> - - - - - @@ -13186,16 +19119,16 @@ exports[`Storyshots Diff File Controls 1`] = `
- -
@@ -13275,18 +19208,6 @@ exports[`Storyshots Diff File Controls 1`] = ` /> - - - - - @@ -13652,16 +19573,16 @@ exports[`Storyshots Diff File Controls 1`] = `
- -
@@ -13741,18 +19662,6 @@ exports[`Storyshots Diff File Controls 1`] = ` /> - - - - - @@ -14256,7 +20165,7 @@ exports[`Storyshots Diff File Controls 1`] = ` colSpan={3} >
@@ -14470,7 +20379,7 @@ exports[`Storyshots Diff File Controls 1`] = ` colSpan={3} >
@@ -14725,16 +20634,16 @@ exports[`Storyshots Diff File Controls 1`] = `
- -
@@ -14814,18 +20723,6 @@ exports[`Storyshots Diff File Controls 1`] = ` /> - - - - - @@ -15270,19 +21167,19 @@ exports[`Storyshots Diff File Controls 1`] = ` exports[`Storyshots Diff Hunks 1`] = `
- -
@@ -15344,18 +21241,6 @@ exports[`Storyshots Diff Hunks 1`] = ` /> - - - - - @@ -15565,7 +21450,7 @@ exports[`Storyshots Diff Hunks 1`] = ` colSpan={3} >
@@ -15756,7 +21641,7 @@ exports[`Storyshots Diff Hunks 1`] = ` colSpan={3} >
@@ -16107,19 +21992,19 @@ exports[`Storyshots Diff Hunks 1`] = ` exports[`Storyshots Diff Line Annotation 1`] = `
- -
@@ -16181,18 +22066,6 @@ exports[`Storyshots Diff Line Annotation 1`] = ` /> - - - - - @@ -16697,16 +22570,16 @@ exports[`Storyshots Diff Line Annotation 1`] = `
- -
@@ -16768,18 +22641,6 @@ exports[`Storyshots Diff Line Annotation 1`] = ` /> - - - - - @@ -16978,7 +22839,7 @@ exports[`Storyshots Diff Line Annotation 1`] = ` colSpan={3} >
@@ -17561,16 +23422,16 @@ exports[`Storyshots Diff Line Annotation 1`] = `
- -
@@ -17632,18 +23493,6 @@ exports[`Storyshots Diff Line Annotation 1`] = ` /> - - - - - @@ -18009,16 +23858,16 @@ exports[`Storyshots Diff Line Annotation 1`] = `
- -
@@ -18080,18 +23929,6 @@ exports[`Storyshots Diff Line Annotation 1`] = ` /> - - - - - @@ -18457,16 +24294,16 @@ exports[`Storyshots Diff Line Annotation 1`] = `
- -
@@ -18528,18 +24365,6 @@ exports[`Storyshots Diff Line Annotation 1`] = ` /> - - - - - @@ -19043,7 +24868,7 @@ exports[`Storyshots Diff Line Annotation 1`] = ` colSpan={3} >
@@ -19257,7 +25082,7 @@ exports[`Storyshots Diff Line Annotation 1`] = ` colSpan={3} >
@@ -19512,16 +25337,16 @@ exports[`Storyshots Diff Line Annotation 1`] = `
- -
@@ -19583,18 +25408,6 @@ exports[`Storyshots Diff Line Annotation 1`] = ` /> - - - - - @@ -20051,19 +25864,19 @@ exports[`Storyshots Diff Line Annotation 1`] = ` exports[`Storyshots Diff OnClick 1`] = `
- -
@@ -20125,18 +25938,6 @@ exports[`Storyshots Diff OnClick 1`] = ` /> - - - - - @@ -20669,16 +26470,16 @@ exports[`Storyshots Diff OnClick 1`] = `
- -
@@ -20740,18 +26541,6 @@ exports[`Storyshots Diff OnClick 1`] = ` /> - - - - - @@ -20952,7 +26741,7 @@ exports[`Storyshots Diff OnClick 1`] = ` colSpan={3} >
@@ -21583,16 +27372,16 @@ exports[`Storyshots Diff OnClick 1`] = `
- -
@@ -21654,18 +27443,6 @@ exports[`Storyshots Diff OnClick 1`] = ` /> - - - - - @@ -22061,16 +27838,16 @@ exports[`Storyshots Diff OnClick 1`] = `
- -
@@ -22132,18 +27909,6 @@ exports[`Storyshots Diff OnClick 1`] = ` /> - - - - - @@ -22539,16 +28304,16 @@ exports[`Storyshots Diff OnClick 1`] = `
- -
@@ -22610,18 +28375,6 @@ exports[`Storyshots Diff OnClick 1`] = ` /> - - - - - @@ -23165,7 +28918,7 @@ exports[`Storyshots Diff OnClick 1`] = ` colSpan={3} >
@@ -23395,7 +29148,7 @@ exports[`Storyshots Diff OnClick 1`] = ` colSpan={3} >
@@ -23670,16 +29423,16 @@ exports[`Storyshots Diff OnClick 1`] = `
- -
@@ -23741,18 +29494,6 @@ exports[`Storyshots Diff OnClick 1`] = ` /> - - - - - @@ -24233,19 +29974,19 @@ exports[`Storyshots Diff OnClick 1`] = ` exports[`Storyshots Diff Side-By-Side 1`] = `
- -
@@ -24308,18 +30049,6 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` /> - - - - - @@ -24904,16 +30633,16 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
- -
@@ -24976,18 +30705,6 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` /> - - - - - @@ -25217,7 +30934,7 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` colSpan={4} >
@@ -25846,16 +31563,16 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
- -
@@ -25918,18 +31635,6 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` /> - - - - - @@ -26346,16 +32051,16 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
- -
@@ -26418,18 +32123,6 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` /> - - - - - @@ -26846,16 +32539,16 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
- -
@@ -26918,18 +32611,6 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` /> - - - - - @@ -27543,7 +33224,7 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` colSpan={4} >
@@ -27785,7 +33466,7 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` colSpan={4} >
@@ -28070,16 +33751,16 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
- -
@@ -28142,18 +33823,6 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` /> - - - - - @@ -28670,19 +34339,19 @@ exports[`Storyshots Diff Side-By-Side 1`] = ` exports[`Storyshots Diff SyntaxHighlighting 1`] = `
- -
@@ -28744,18 +34413,6 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` /> - - - - - @@ -29248,16 +34905,16 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
- -
@@ -29319,18 +34976,6 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` /> - - - - - @@ -29517,7 +35162,7 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` colSpan={3} >
@@ -30100,16 +35745,16 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
- -
@@ -30171,18 +35816,6 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` /> - - - - - @@ -30548,16 +36181,16 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
- -
@@ -30619,18 +36252,6 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` /> - - - - - @@ -30996,16 +36617,16 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
- -
@@ -31067,18 +36688,6 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` /> - - - - - @@ -31582,7 +37191,7 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` colSpan={3} >
@@ -31796,7 +37405,7 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` colSpan={3} >
@@ -32051,16 +37660,16 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
- -
@@ -32122,18 +37731,6 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` /> - - - - - @@ -32578,21 +38175,20 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = ` exports[`Storyshots Forms|Checkbox Default 1`] = `
+
+
+ +
+
`; exports[`Storyshots Forms|Checkbox Disabled 1`] = `
- -
@@ -36747,7 +43380,7 @@ exports[`Storyshots Table|Table Default 1`] = ` > Last Name @@ -36832,7 +43465,7 @@ exports[`Storyshots Table|Table TextColumn 1`] = ` > Id @@ -36899,7 +43532,7 @@ exports[`Storyshots Table|Table TextColumn 1`] = ` exports[`Storyshots Tag Clickable 1`] = `
-
-
+ - - + right - -
-
+ + +
+
+ - - + top - -
-
+ + +
+
+ - - + left - -
-
+ + +
+
+ - - + bottom - -
+ + + +
+
+`; + +exports[`Storyshots Tooltip Multiline 1`] = ` +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+`; + +exports[`Storyshots Tooltip Short Multiline 1`] = ` +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + +
`; diff --git a/scm-ui/ui-components/src/avatar/Avatar.ts b/scm-ui/ui-components/src/avatar/Avatar.ts index 362a00210d..9690c37ad6 100644 --- a/scm-ui/ui-components/src/avatar/Avatar.ts +++ b/scm-ui/ui-components/src/avatar/Avatar.ts @@ -22,9 +22,10 @@ * SOFTWARE. */ -export type Person = { - name: string; - mail?: string; -}; +import { Person } from "@scm-manager/ui-types"; + +// re export type to avoid breaking changes, +// after the type was moved to ui-types +export { Person }; export const EXTENSION_POINT = "avatar.factory"; diff --git a/scm-ui/ui-components/src/forms/Checkbox.stories.tsx b/scm-ui/ui-components/src/forms/Checkbox.stories.tsx index 9f714e5af0..c83252aaa5 100644 --- a/scm-ui/ui-components/src/forms/Checkbox.stories.tsx +++ b/scm-ui/ui-components/src/forms/Checkbox.stories.tsx @@ -35,6 +35,7 @@ storiesOf("Forms|Checkbox", module) + )) .add("Disabled", () => ( diff --git a/scm-ui/ui-components/src/forms/Checkbox.tsx b/scm-ui/ui-components/src/forms/Checkbox.tsx index 70888cf483..52321fd37f 100644 --- a/scm-ui/ui-components/src/forms/Checkbox.tsx +++ b/scm-ui/ui-components/src/forms/Checkbox.tsx @@ -24,11 +24,13 @@ import React, { ChangeEvent } from "react"; import { Help } from "../index"; import LabelWithHelpIcon from "./LabelWithHelpIcon"; +import TriStateCheckbox from "./TriStateCheckbox"; type Props = { label?: string; onChange?: (value: boolean, name?: string) => void; checked: boolean; + indeterminate?: boolean; name?: string; title?: string; disabled?: boolean; @@ -36,9 +38,9 @@ type Props = { }; export default class Checkbox extends React.Component { - onCheckboxChange = (event: ChangeEvent) => { + onCheckboxChange = () => { if (this.props.onChange) { - this.props.onChange(event.target.checked, this.props.name); + this.props.onChange(!this.props.checked, this.props.name); } }; @@ -57,18 +59,19 @@ export default class Checkbox extends React.Component { }; render() { - const { label, checked, disabled } = this.props; + const { label, checked, indeterminate, disabled } = this.props; return (
{this.renderLabelWithHelp()} -
+
{/* we have to ignore the next line, because jsx label does not the custom disabled attribute but bulma does. // @ts-ignore */}
diff --git a/scm-ui/ui-components/src/forms/TriStateCheckbox.tsx b/scm-ui/ui-components/src/forms/TriStateCheckbox.tsx new file mode 100644 index 0000000000..b9276da2c0 --- /dev/null +++ b/scm-ui/ui-components/src/forms/TriStateCheckbox.tsx @@ -0,0 +1,64 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC } from "react"; +import Icon from "../Icon"; + +type Props = { + checked: boolean; + indeterminate?: boolean; + disabled?: boolean; + label?: string; +}; + +const TriStateCheckbox: FC = ({ checked, indeterminate, disabled, label }) => { + let icon; + if (indeterminate) { + icon = "minus-square"; + } else if (checked) { + icon = "check-square"; + } else { + icon = "square"; + } + + let className; + if (!checked || indeterminate) { + className = "far"; + } else { + className = "fa"; + } + + let color; + if (disabled) { + color = "grey-light"; + } else if (checked || indeterminate) { + color = "link"; + } else { + color = "black"; + } + + return <>{" "} + {label}; +}; + +export default TriStateCheckbox; diff --git a/scm-ui/ui-components/src/index.ts b/scm-ui/ui-components/src/index.ts index c31066edc1..19ba35a1f4 100644 --- a/scm-ui/ui-components/src/index.ts +++ b/scm-ui/ui-components/src/index.ts @@ -44,6 +44,7 @@ import { export { validation, urls, repositories }; export { default as DateFromNow } from "./DateFromNow"; +export { default as DateShort } from "./DateShort"; export { default as ErrorNotification } from "./ErrorNotification"; export { default as ErrorPage } from "./ErrorPage"; export { default as Icon } from "./Icon"; @@ -76,6 +77,7 @@ export { default as OverviewPageActions } from "./OverviewPageActions"; export { default as CardColumnGroup } from "./CardColumnGroup"; export { default as CardColumn } from "./CardColumn"; export { default as CardColumnSmall } from "./CardColumnSmall"; +export { default as CommaSeparatedList } from "./CommaSeparatedList"; export { default as comparators } from "./comparators"; diff --git a/scm-ui/ui-components/src/navigation/NavLink.tsx b/scm-ui/ui-components/src/navigation/NavLink.tsx index d9cb3c4e7e..eba1373d9a 100644 --- a/scm-ui/ui-components/src/navigation/NavLink.tsx +++ b/scm-ui/ui-components/src/navigation/NavLink.tsx @@ -23,10 +23,11 @@ */ import * as React from "react"; import classNames from "classnames"; -import { Link, useRouteMatch } from "react-router-dom"; +import { Link } from "react-router-dom"; import { RoutingProps } from "./RoutingProps"; import { FC } from "react"; import useMenuContext from "./MenuContext"; +import useActiveMatch from "./useActiveMatch"; type Props = RoutingProps & { label: string; @@ -34,11 +35,8 @@ type Props = RoutingProps & { icon?: string; }; -const NavLink: FC = ({ to, activeOnlyWhenExact, icon, label, title }) => { - const match = useRouteMatch({ - path: to, - exact: activeOnlyWhenExact - }); +const NavLink: FC = ({ to, activeWhenMatch, activeOnlyWhenExact, icon, label, title }) => { + const active = useActiveMatch({ to, activeWhenMatch, activeOnlyWhenExact }); const context = useMenuContext(); const collapsed = context.isCollapsed(); @@ -54,7 +52,7 @@ const NavLink: FC = ({ to, activeOnlyWhenExact, icon, label, title }) => return (
  • - + {showIcon} {collapsed ? null : label} diff --git a/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx b/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx index 73531dcb18..715e566cd2 100644 --- a/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx +++ b/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx @@ -86,4 +86,18 @@ storiesOf("Navigation|Secondary", module) ); - }); + }) + .add("Active when match", () => + withRoute("/hog")( + + + route.location.pathname === "/hog"} + to="/heart-of-gold" + icon="fas fa-star" + label="Heart Of Gold" + title="Heart Of Gold" + /> + + ) + ); diff --git a/scm-ui/ui-components/src/navigation/SubNavigation.tsx b/scm-ui/ui-components/src/navigation/SubNavigation.tsx index 56a6c438d3..a59c271257 100644 --- a/scm-ui/ui-components/src/navigation/SubNavigation.tsx +++ b/scm-ui/ui-components/src/navigation/SubNavigation.tsx @@ -21,11 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, useContext } from "react"; -import { Link, useRouteMatch } from "react-router-dom"; +import React, { FC } from "react"; +import { Link } from "react-router-dom"; import classNames from "classnames"; -import useMenuContext, { MenuContext } from "./MenuContext"; +import useMenuContext from "./MenuContext"; import { RoutingProps } from "./RoutingProps"; +import useActiveMatch from "./useActiveMatch"; type Props = RoutingProps & { label: string; @@ -33,32 +34,33 @@ type Props = RoutingProps & { icon?: string; }; -const SubNavigation: FC = ({ to, activeOnlyWhenExact, icon, title, label, children }) => { +const SubNavigation: FC = ({ to, activeOnlyWhenExact, activeWhenMatch, icon, title, label, children }) => { + const context = useMenuContext(); + const collapsed = context.isCollapsed(); + const parents = to.split("/"); parents.splice(-1, 1); const parent = parents.join("/"); - const match = useRouteMatch({ - path: parent, - exact: activeOnlyWhenExact + const active = useActiveMatch({ + to: parent, + activeOnlyWhenExact, + activeWhenMatch }); - const context = useMenuContext(); - const collapsed = context.isCollapsed(); - let defaultIcon = "fas fa-cog"; if (icon) { defaultIcon = icon; } let childrenList = null; - if (match && !collapsed) { + if (active && !collapsed) { childrenList =
      {children}
    ; } return (
  • - + {collapsed ? "" : label} {childrenList} diff --git a/scm-ui/eslint-config/index.js b/scm-ui/ui-components/src/navigation/useActiveMatch.ts similarity index 58% rename from scm-ui/eslint-config/index.js rename to scm-ui/ui-components/src/navigation/useActiveMatch.ts index 8cfd2c1c97..d4550534fd 100644 --- a/scm-ui/eslint-config/index.js +++ b/scm-ui/ui-components/src/navigation/useActiveMatch.ts @@ -21,36 +21,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -module.exports = { - extends: [ - "react-app", - "plugin:prettier/recommended", - "plugin:flowtype/recommended" - ], - rules: { - semi: ["error", "always"], - quotes: ["error", "double"], - "jsx-a11y/href-no-hash": [0], - "flowtype/no-types-missing-file-annotation": 2, - "no-console": "error" - }, - overrides: [ - { - files: ["*.ts", "*.tsx"], - parser: "@typescript-eslint/parser", - extends: [ - "react-app", - "plugin:prettier/recommended", - "plugin:@typescript-eslint/recommended" - ], - rules: { - semi: ["error", "always"], - quotes: ["error", "double"], - "jsx-a11y/href-no-hash": [0], - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/ban-ts-ignore": "warn", - "no-console": "error" - } + +import { useLocation, useRouteMatch } from "react-router-dom"; +import { RoutingProps } from "./RoutingProps"; + +const useActiveMatch = ({ to, activeOnlyWhenExact, activeWhenMatch }: RoutingProps) => { + const match = useRouteMatch({ + path: to, + exact: activeOnlyWhenExact + }); + + const location = useLocation(); + + const isActiveWhenMatch = () => { + if (activeWhenMatch) { + return activeWhenMatch({ + location + }); } - ] + return false; + }; + + return !!match || isActiveWhenMatch(); }; + +export default useActiveMatch; diff --git a/scm-ui/ui-components/src/remarkCommitLinksParser.test.ts b/scm-ui/ui-components/src/remarkCommitLinksParser.test.ts new file mode 100644 index 0000000000..9d54e3539a --- /dev/null +++ b/scm-ui/ui-components/src/remarkCommitLinksParser.test.ts @@ -0,0 +1,66 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { regExpPattern } from "./remarkCommitLinksParser"; + +describe("Remark Commit Links RegEx Tests", () => { + it("should match simple names", () => { + const regExp = new RegExp(regExpPattern, "g"); + expect("namespace/name@1a5s4w8a".match(regExp)).toBeTruthy(); + }); + it("should match complex names", () => { + const regExp = new RegExp(regExpPattern, "g"); + expect("hitchhiker/heart-of-gold@c7237cb60689046990dc9dc2a388a517adb3e2b2".match(regExp)).toBeTruthy(); + }); + it("should replace match", () => { + const regExp = new RegExp(regExpPattern, "g"); + expect("Prefix namespace/name@42 suffix".replace(regExp, "replaced")).toBe("Prefix replaced suffix"); + }); + it("should match groups", () => { + const regExp = new RegExp(regExpPattern, "g"); + const match = regExp.exec("namespace/name@42"); + expect(match).toBeTruthy(); + if (match) { + expect(match[1]).toBe("namespace"); + expect(match[2]).toBe("name"); + expect(match[3]).toBe("42"); + } + }); + it("should match multiple links in text", () => { + const regExp = new RegExp(regExpPattern, "g"); + const text = "Prefix hitchhiker/heart-of-gold@42 some text hitchhiker/heart-of-gold@21 suffix"; + const matches = []; + + let match = regExp.exec(text); + while (match !== null) { + matches.push(match[0]); + match = regExp.exec(text); + } + + console.log(matches) + + expect(matches[0]).toBe("hitchhiker/heart-of-gold@42"); + expect(matches[1]).toBe("hitchhiker/heart-of-gold@21"); + }); +}); diff --git a/scm-ui/ui-components/src/remarkCommitLinksParser.ts b/scm-ui/ui-components/src/remarkCommitLinksParser.ts new file mode 100644 index 0000000000..fa26c793db --- /dev/null +++ b/scm-ui/ui-components/src/remarkCommitLinksParser.ts @@ -0,0 +1,103 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { MarkdownAbstractSyntaxTree, MdastPlugin } from "react-markdown"; +import { nameRegex } from "./validation"; +// @ts-ignore No types available +import visit from "unist-util-visit"; +import { TFunction } from "i18next"; + +const namePartRegex = nameRegex.source.substring(1, nameRegex.source.length - 1); + +// Visible for testing +export const regExpPattern = `(${namePartRegex})\\/(${namePartRegex})@([\\w\\d]+)`; + +function match(value: string): RegExpMatchArray[] { + const regExp = new RegExp(regExpPattern, "g"); + const matches = []; + let m = regExp.exec(value); + while (m) { + matches.push(m); + m = regExp.exec(value); + } + return matches; +} + +export const createTransformer = (t: TFunction): MdastPlugin => { + + return (tree: MarkdownAbstractSyntaxTree) => { + visit(tree, "text", (node: MarkdownAbstractSyntaxTree, index: number, parent: MarkdownAbstractSyntaxTree) => { + if (parent.type === "link" || !node.value) { + return; + } + + let nodeText = node.value; + const matches = match(nodeText); + + if (matches.length > 0) { + const children = []; + for (const m of matches) { + const i = nodeText.indexOf(m[0]); + if (i > 0) { + children.push({ + type: "text", + value: nodeText.substring(0, i) + }); + } + + children.push({ + type: "link", + url: `/repo/${m[1]}/${m[2]}/code/changeset/${m[3]}`, + title: t("changeset.shortlink.title", { + namespace: m[1], + name: m[2], + id: m[3] + }), + children: [ + { + type: "text", + value: m[0] + } + ] + }); + + nodeText = nodeText.substring(i + m[0].length); + } + + if (nodeText.length > 0) { + children.push({ + type: "text", + value: nodeText + }); + } + + parent.children![index] = { + type: "text", + children + }; + } + }); + return tree; + }; +}; diff --git a/scm-ui/ui-components/src/repos/Diff.stories.tsx b/scm-ui/ui-components/src/repos/Diff.stories.tsx index 861be0d364..7032aeb74f 100644 --- a/scm-ui/ui-components/src/repos/Diff.stories.tsx +++ b/scm-ui/ui-components/src/repos/Diff.stories.tsx @@ -114,4 +114,11 @@ storiesOf("Diff", module) }) .add("CollapsingWithFunction", () => ( oldPath.endsWith(".java")} /> - )); + )) + .add("Expandable", () => { + const filesWithLanguage = diffFiles.map((file: File) => { + file._links = { lines: { href: "http://example.com/" } }; + return file; + }); + return ; + }); diff --git a/scm-ui/ui-components/src/repos/DiffExpander.test.ts b/scm-ui/ui-components/src/repos/DiffExpander.test.ts new file mode 100644 index 0000000000..3534cf77d2 --- /dev/null +++ b/scm-ui/ui-components/src/repos/DiffExpander.test.ts @@ -0,0 +1,404 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import fetchMock from "fetch-mock"; +import DiffExpander from "./DiffExpander"; +import { File, Hunk } from "./DiffTypes"; + +const HUNK_0: Hunk = { + content: "@@ -1,8 +1,8 @@", + oldStart: 1, + newStart: 1, + oldLines: 8, + newLines: 8, + changes: [ + { content: "line", type: "normal", oldLineNumber: 1, newLineNumber: 1, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 2, newLineNumber: 2, isNormal: true }, + { content: "line", type: "delete", lineNumber: 3, isDelete: true }, + { content: "line", type: "delete", lineNumber: 4, isDelete: true }, + { content: "line", type: "delete", lineNumber: 5, isDelete: true }, + { content: "line", type: "insert", lineNumber: 3, isInsert: true }, + { content: "line", type: "insert", lineNumber: 4, isInsert: true }, + { content: "line", type: "insert", lineNumber: 5, isInsert: true }, + { content: "line", type: "normal", oldLineNumber: 6, newLineNumber: 6, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 7, newLineNumber: 7, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 8, newLineNumber: 8, isNormal: true } + ] +}; +const HUNK_1: Hunk = { + content: "@@ -14,6 +14,7 @@", + oldStart: 14, + newStart: 14, + oldLines: 6, + newLines: 7, + changes: [ + { content: "line", type: "normal", oldLineNumber: 14, newLineNumber: 14, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 15, newLineNumber: 15, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 16, newLineNumber: 16, isNormal: true }, + { content: "line", type: "insert", lineNumber: 17, isInsert: true }, + { content: "line", type: "normal", oldLineNumber: 17, newLineNumber: 18, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 18, newLineNumber: 19, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 19, newLineNumber: 20, isNormal: true } + ] +}; +const HUNK_2: Hunk = { + content: "@@ -21,7 +22,7 @@", + oldStart: 21, + newStart: 22, + oldLines: 7, + newLines: 7, + changes: [ + { content: "line", type: "normal", oldLineNumber: 21, newLineNumber: 22, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 22, newLineNumber: 23, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 23, newLineNumber: 24, isNormal: true }, + { content: "line", type: "delete", lineNumber: 24, isDelete: true }, + { content: "line", type: "insert", lineNumber: 25, isInsert: true }, + { content: "line", type: "normal", oldLineNumber: 25, newLineNumber: 26, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 26, newLineNumber: 27, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 27, newLineNumber: 28, isNormal: true } + ] +}; +const HUNK_3: Hunk = { + content: "@@ -33,6 +34,7 @@", + oldStart: 33, + newStart: 34, + oldLines: 6, + newLines: 7, + changes: [ + { content: "line", type: "normal", oldLineNumber: 33, newLineNumber: 34, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 34, newLineNumber: 35, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 35, newLineNumber: 36, isNormal: true }, + { content: "line", type: "insert", lineNumber: 37, isInsert: true }, + { content: "line", type: "normal", oldLineNumber: 36, newLineNumber: 38, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 37, newLineNumber: 39, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 38, newLineNumber: 40, isNormal: true } + ] +}; +const TEST_CONTENT_WITH_HUNKS: File = { + hunks: [HUNK_0, HUNK_1, HUNK_2, HUNK_3], + newEndingNewLine: true, + newPath: "src/main/js/CommitMessage.js", + newRevision: "4305a8df175b7bec25acbe542a13fbe2a718a608", + oldEndingNewLine: true, + oldPath: "src/main/js/CommitMessage.js", + oldRevision: "e05c8495bb1dc7505d73af26210c8ff4825c4500", + type: "modify", + language: "javascript", + _links: { + lines: { + href: "http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start={start}&end={end}", + templated: true + } + } +}; + +const TEST_CONTENT_WITH_NEW_BINARY_FILE: File = { + oldPath: "/dev/null", + newPath: "src/main/fileUploadV2.png", + oldEndingNewLine: true, + newEndingNewLine: true, + oldRevision: "0000000000000000000000000000000000000000", + newRevision: "86c370aae0727d628a5438f79a5cdd45752b9d99", + type: "add" +}; + +const TEST_CONTENT_WITH_NEW_TEXT_FILE: File = { + oldPath: "/dev/null", + newPath: "src/main/markdown/README.md", + oldEndingNewLine: true, + newEndingNewLine: true, + oldRevision: "0000000000000000000000000000000000000000", + newRevision: "4e173d365d796b9a9e7562fcd0ef90398ae37046", + type: "add", + language: "markdown", + hunks: [ + { + content: "@@ -0,0 +1,2 @@", + newStart: 1, + newLines: 2, + changes: [ + { content: "line 1", type: "insert", lineNumber: 1, isInsert: true }, + { content: "line 2", type: "insert", lineNumber: 2, isInsert: true } + ] + } + ], + _links: { + lines: { + href: + "http://localhost:8081/scm/api/v2/repositories/scm-manager/scm-editor-plugin/content/c63898d35520ee47bcc3a8291660979918715762/src/main/markdown/README.md?start={start}&end={end}", + templated: true + } + } +}; + +const TEST_CONTENT_WITH_DELETED_TEXT_FILE: File = { + oldPath: "README.md", + newPath: "/dev/null", + oldEndingNewLine: true, + newEndingNewLine: true, + oldRevision: "4875ab3b7a1bb117e1948895148557fc5c0b6f75", + newRevision: "0000000000000000000000000000000000000000", + type: "delete", + language: "markdown", + hunks: [ + { + content: "@@ -1 +0,0 @@", + oldStart: 1, + oldLines: 1, + changes: [{ content: "# scm-editor-plugin", type: "delete", lineNumber: 1, isDelete: true }] + } + ], + _links: { lines: { href: "http://localhost:8081/dev/null?start={start}&end={end}", templated: true } } +}; + +const TEST_CONTENT_WITH_DELETED_LINES_AT_END: File = { + oldPath: "pom.xml", + newPath: "pom.xml", + oldEndingNewLine: true, + newEndingNewLine: true, + oldRevision: "b207512c0eab22536c9e5173afbe54cc3a24a22e", + newRevision: "5347c3fe0c2c4d4de7f308ae61bd5546460d7e93", + type: "modify", + language: "xml", + hunks: [ + { + content: "@@ -108,15 +108,3 @@", + oldStart: 108, + newStart: 108, + oldLines: 15, + newLines: 3, + changes: [ + { content: "line", type: "normal", oldLineNumber: 108, newLineNumber: 108, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 109, newLineNumber: 109, isNormal: true }, + { content: "line", type: "normal", oldLineNumber: 110, newLineNumber: 110, isNormal: true }, + { content: "line", type: "delete", lineNumber: 111, isDelete: true }, + { content: "line", type: "delete", lineNumber: 112, isDelete: true }, + { content: "line", type: "delete", lineNumber: 113, isDelete: true }, + { content: "line", type: "delete", lineNumber: 114, isDelete: true }, + { content: "line", type: "delete", lineNumber: 115, isDelete: true }, + { content: "line", type: "delete", lineNumber: 116, isDelete: true }, + { content: "line", type: "delete", lineNumber: 117, isDelete: true }, + { content: "line", type: "delete", lineNumber: 118, isDelete: true }, + { content: "line", type: "delete", lineNumber: 119, isDelete: true }, + { content: "line", type: "delete", lineNumber: 120, isDelete: true }, + { content: "line", type: "delete", lineNumber: 121, isDelete: true }, + { content: "line", type: "delete", lineNumber: 122, isDelete: true } + ] + } + ], + _links: { + lines: { + href: "http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start={start}&end={end}", + templated: true + } + } +}; + +const TEST_CONTENT_WITH_ALL_LINES_REMOVED_FROM_FILE: File = { + oldPath: "pom.xml", + newPath: "pom.xml", + oldEndingNewLine: true, + newEndingNewLine: true, + oldRevision: "2cc811c64f71ceda28f1ec0d97e1973395b299ff", + newRevision: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + type: "modify", + language: "xml", + hunks: [ + { + content: "@@ -1,3 +0,0 @@", + oldStart: 1, + oldLines: 3, + changes: [ + { content: "line", type: "delete", lineNumber: 1, isDelete: true }, + { content: "line", type: "delete", lineNumber: 2, isDelete: true }, + { content: "line", type: "delete", lineNumber: 3, isDelete: true } + ] + } + ], + _links: { + lines: { + href: + "http://localhost:8081/scm/api/v2/repositories/scm-manager/scm-editor-plugin/content/b313a7690f028c77df98417c1ed6cba67e5692ec/pom.xml?start={start}&end={end}", + templated: true + } + } +}; + +describe("with hunks the diff expander", () => { + const diffExpander = new DiffExpander(TEST_CONTENT_WITH_HUNKS); + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should have hunk count from origin", () => { + expect(diffExpander.hunkCount()).toBe(4); + }); + + it("should return correct hunk", () => { + expect(diffExpander.getHunk(1).hunk).toBe(HUNK_1); + }); + + it("should return max expand head range for first hunk", () => { + expect(diffExpander.getHunk(0).maxExpandHeadRange).toBe(0); + }); + + it("should return max expand head range for hunks in the middle", () => { + expect(diffExpander.getHunk(1).maxExpandHeadRange).toBe(5); + }); + + it("should return max expand bottom range for hunks in the middle", () => { + expect(diffExpander.getHunk(1).maxExpandBottomRange).toBe(1); + }); + + it("should return a really bix number for the expand bottom range of the last hunk", () => { + expect(diffExpander.getHunk(3).maxExpandBottomRange).toBe(-1); + }); + it("should create new hunk with new line from api client at the bottom", async () => { + expect(diffExpander.getHunk(1).hunk.changes.length).toBe(7); + const oldHunkCount = diffExpander.hunkCount(); + const expandedHunk = diffExpander.getHunk(1).hunk; + const subsequentHunk = diffExpander.getHunk(2).hunk; + fetchMock.get("http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start=20&end=21", "new line 1"); + let newFile: File; + await diffExpander + .getHunk(1) + .expandBottom(1) + .then(file => (newFile = file)); + expect(fetchMock.done()).toBe(true); + expect(newFile!.hunks!.length).toBe(oldHunkCount + 1); + expect(newFile!.hunks![1]).toBe(expandedHunk); + + const newHunk = newFile!.hunks![2]; + expect(newHunk.changes.length).toBe(1); + expect(newHunk.changes[0].content).toBe("new line 1"); + expect(newHunk.expansion).toBe(true); + + expect(newFile!.hunks![3]).toBe(subsequentHunk); + }); + it("should create new hunk with new line from api client at the top", async () => { + expect(diffExpander.getHunk(1).hunk.changes.length).toBe(7); + const oldHunkCount = diffExpander.hunkCount(); + const expandedHunk = diffExpander.getHunk(1).hunk; + const preceedingHunk = diffExpander.getHunk(0).hunk; + fetchMock.get( + "http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start=8&end=13", + "new line 9\nnew line 10\nnew line 11\nnew line 12\nnew line 13" + ); + let newFile: File; + await diffExpander + .getHunk(1) + .expandHead(5) + .then(file => (newFile = file)); + expect(fetchMock.done()).toBe(true); + expect(newFile!.hunks!.length).toBe(oldHunkCount + 1); + expect(newFile!.hunks![0]).toBe(preceedingHunk); + expect(newFile!.hunks![2]).toBe(expandedHunk); + + const newHunk = newFile!.hunks![1]; + expect(newHunk.changes.length).toBe(5); + expect(newHunk.changes[0].content).toBe("new line 9"); + expect(newHunk.changes[0].oldLineNumber).toBe(9); + expect(newHunk.changes[0].newLineNumber).toBe(9); + expect(newHunk.changes[1].content).toBe("new line 10"); + expect(newHunk.changes[1].oldLineNumber).toBe(10); + expect(newHunk.changes[1].newLineNumber).toBe(10); + expect(newHunk.changes[4].content).toBe("new line 13"); + expect(newHunk.changes[4].oldLineNumber).toBe(13); + expect(newHunk.changes[4].newLineNumber).toBe(13); + expect(newHunk.expansion).toBe(true); + }); + it("should set fully expanded to true if expanded completely", async () => { + const oldHunkCount = diffExpander.hunkCount(); + fetchMock.get( + "http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start=40&end=50", + "new line 40\nnew line 41\nnew line 42" + ); + let newFile: File; + await diffExpander + .getHunk(3) + .expandBottom(10) + .then(file => (newFile = file)); + expect(newFile!.hunks!.length).toBe(oldHunkCount + 1); + expect(newFile!.hunks![4].fullyExpanded).toBe(true); + }); + it("should set end to -1 if requested to expand to the end", async () => { + fetchMock.get( + "http://localhost:8081/scm/api/v2/content/abc/CommitMessage.js?start=40&end=-1", + "new line 40\nnew line 41\nnew line 42" + ); + let newFile: File; + await diffExpander + .getHunk(3) + .expandBottom(-1) + .then(file => (newFile = file)); + await fetchMock.flush(true); + expect(newFile!.hunks![4].fullyExpanded).toBe(true); + }); +}); + +describe("for a new file with text input the diff expander", () => { + const diffExpander = new DiffExpander(TEST_CONTENT_WITH_NEW_TEXT_FILE); + it("should create answer for single hunk", () => { + expect(diffExpander.hunkCount()).toBe(1); + }); + it("should neither give expandable lines for top nor bottom", () => { + const hunk = diffExpander.getHunk(0); + expect(hunk.maxExpandHeadRange).toBe(0); + expect(hunk.maxExpandBottomRange).toBe(0); + }); +}); + +describe("for a deleted file with text input the diff expander", () => { + const diffExpander = new DiffExpander(TEST_CONTENT_WITH_DELETED_TEXT_FILE); + it("should create answer for single hunk", () => { + expect(diffExpander.hunkCount()).toBe(1); + }); + it("should neither give expandable lines for top nor bottom", () => { + const hunk = diffExpander.getHunk(0); + expect(hunk.maxExpandHeadRange).toBe(0); + expect(hunk.maxExpandBottomRange).toBe(0); + }); +}); + +describe("for a new file with binary input the diff expander", () => { + const diffExpander = new DiffExpander(TEST_CONTENT_WITH_NEW_BINARY_FILE); + it("should create answer for no hunk", () => { + expect(diffExpander.hunkCount()).toBe(0); + }); +}); + +describe("with deleted lines at the end", () => { + const diffExpander = new DiffExpander(TEST_CONTENT_WITH_DELETED_LINES_AT_END); + it("should not be expandable", () => { + expect(diffExpander.getHunk(0)!.maxExpandBottomRange).toBe(0); + }); +}); + +describe("with all lines removed from file", () => { + const diffExpander = new DiffExpander(TEST_CONTENT_WITH_ALL_LINES_REMOVED_FROM_FILE); + it("should not be expandable", () => { + expect(diffExpander.getHunk(0)!.maxExpandBottomRange).toBe(0); + }); +}); diff --git a/scm-ui/ui-components/src/repos/DiffExpander.ts b/scm-ui/ui-components/src/repos/DiffExpander.ts new file mode 100644 index 0000000000..63ec2b6c6d --- /dev/null +++ b/scm-ui/ui-components/src/repos/DiffExpander.ts @@ -0,0 +1,198 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { apiClient } from "@scm-manager/ui-components"; +import { Change, File, Hunk } from "./DiffTypes"; +import { Link } from "@scm-manager/ui-types"; + +class DiffExpander { + file: File; + + constructor(file: File) { + this.file = file; + } + + hunkCount = () => { + if (this.file.hunks) { + return this.file.hunks.length; + } else { + return 0; + } + }; + + minLineNumber: (n: number) => number = (n: number) => { + return this.file.hunks![n]!.newStart!; + }; + + maxLineNumber: (n: number) => number = (n: number) => { + return this.file.hunks![n]!.newStart! + this.file.hunks![n]!.newLines! - 1; + }; + + computeMaxExpandHeadRange = (n: number) => { + if (this.file.type === "delete") { + return 0; + } else if (n === 0) { + return this.minLineNumber(n) - 1; + } + return this.minLineNumber(n) - this.maxLineNumber(n - 1) - 1; + }; + + computeMaxExpandBottomRange = (n: number) => { + if (this.file.type === "add" || this.file.type === "delete") { + return 0; + } + const changes = this.file.hunks![n].changes; + if (changes[changes.length - 1].type === "normal") { + if (n === this.file!.hunks!.length - 1) { + return this.file!.hunks![this.file!.hunks!.length - 1].fullyExpanded ? 0 : -1; + } + return this.minLineNumber(n + 1) - this.maxLineNumber(n) - 1; + } else { + return 0; + } + }; + + expandHead: (n: number, count: number) => Promise = (n, count) => { + const start = this.minLineNumber(n) - Math.min(count, this.computeMaxExpandHeadRange(n)) - 1; + const end = this.minLineNumber(n) - 1; + return this.loadLines(start, end).then(lines => { + const hunk = this.file.hunks![n]; + + const newHunk = this.createNewHunk( + hunk.oldStart! - lines.length, + hunk.newStart! - lines.length, + lines, + lines.length + ); + + return this.addHunkToFile(newHunk, n); + }); + }; + + expandBottom: (n: number, count: number) => Promise = (n, count) => { + const maxExpandBottomRange = this.computeMaxExpandBottomRange(n); + const start = this.maxLineNumber(n); + const end = + count > 0 + ? start + Math.min(count, maxExpandBottomRange > 0 ? maxExpandBottomRange : Number.MAX_SAFE_INTEGER) + : -1; + return this.loadLines(start, end).then(lines => { + const hunk = this.file.hunks![n]; + + const newHunk: Hunk = this.createNewHunk( + this.getMaxOldLineNumber(hunk.changes) + 1, + this.getMaxNewLineNumber(hunk.changes) + 1, + lines, + count + ); + + return this.addHunkToFile(newHunk, n + 1); + }); + }; + + loadLines = (start: number, end: number) => { + const lineRequestUrl = (this.file._links!.lines as Link).href + .replace("{start}", start.toString()) + .replace("{end}", end.toString()); + return apiClient + .get(lineRequestUrl) + .then(response => response.text()) + .then(text => text.split("\n")) + .then(lines => (lines[lines.length - 1] === "" ? lines.slice(0, lines.length - 1) : lines)); + }; + + addHunkToFile = (newHunk: Hunk, position: number) => { + const newHunks: Hunk[] = []; + this.file.hunks!.forEach((oldHunk: Hunk, i: number) => { + if (i === position) { + newHunks.push(newHunk); + } + newHunks.push(oldHunk); + }); + if (position === newHunks.length) { + newHunks.push(newHunk); + } + return { ...this.file, hunks: newHunks }; + }; + + createNewHunk = (oldFirstLineNumber: number, newFirstLineNumber: number, lines: string[], requestedLines: number) => { + const newChanges: Change[] = []; + + let oldLineNumber: number = oldFirstLineNumber; + let newLineNumber: number = newFirstLineNumber; + + lines.forEach(line => { + newChanges.push({ + content: line, + type: "normal", + oldLineNumber, + newLineNumber, + isNormal: true + }); + oldLineNumber += 1; + newLineNumber += 1; + }); + + return { + changes: newChanges, + content: "", + oldStart: oldFirstLineNumber, + newStart: newFirstLineNumber, + oldLines: lines.length, + newLines: lines.length, + expansion: true, + fullyExpanded: requestedLines < 0 || lines.length < requestedLines + }; + }; + + getMaxOldLineNumber = (newChanges: Change[]) => { + const lastChange = newChanges[newChanges.length - 1]; + return lastChange.oldLineNumber || lastChange.lineNumber!; + }; + + getMaxNewLineNumber = (newChanges: Change[]) => { + const lastChange = newChanges[newChanges.length - 1]; + return lastChange.newLineNumber || lastChange.lineNumber!; + }; + + getHunk: (n: number) => ExpandableHunk = n => { + return { + maxExpandHeadRange: this.computeMaxExpandHeadRange(n), + maxExpandBottomRange: this.computeMaxExpandBottomRange(n), + expandHead: (count: number) => this.expandHead(n, count), + expandBottom: (count: number) => this.expandBottom(n, count), + hunk: this.file?.hunks![n] + }; + }; +} + +export type ExpandableHunk = { + hunk: Hunk; + maxExpandHeadRange: number; + maxExpandBottomRange: number; + expandHead: (count: number) => Promise; + expandBottom: (count: number) => Promise; +}; + +export default DiffExpander; diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index 4b042f0c4d..98fa9959e3 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -34,6 +34,11 @@ import { Change, ChangeEvent, DiffObjectProps, File, Hunk as HunkType } from "./ import TokenizedDiffView from "./TokenizedDiffView"; import DiffButton from "./DiffButton"; import { MenuContext } from "@scm-manager/ui-components"; +import DiffExpander, { ExpandableHunk } from "./DiffExpander"; +import HunkExpandLink from "./HunkExpandLink"; +import { Modal } from "../modals"; +import ErrorNotification from "../ErrorNotification"; +import HunkExpandDivider from "./HunkExpandDivider"; const EMPTY_ANNOTATION_FACTORY = {}; @@ -47,7 +52,10 @@ type Collapsible = { }; type State = Collapsible & { + file: File; sideBySide?: boolean; + diffExpander: DiffExpander; + expansionError?: any; }; const DiffFilePanel = styled.div` @@ -92,7 +100,9 @@ class DiffFile extends React.Component { super(props); this.state = { collapsed: this.defaultCollapse(), - sideBySide: props.sideBySide + sideBySide: props.sideBySide, + diffExpander: new DiffExpander(props.file), + file: props.file }; } @@ -116,7 +126,7 @@ class DiffFile extends React.Component { }; toggleCollapse = () => { - const { file } = this.props; + const { file } = this.state; if (this.hasContent(file)) { this.setState(state => ({ collapsed: !state.collapsed @@ -139,16 +149,122 @@ class DiffFile extends React.Component { }); }; - createHunkHeader = (hunk: HunkType, i: number) => { - if (i > 0) { - return ; + createHunkHeader = (expandableHunk: ExpandableHunk) => { + if (expandableHunk.maxExpandHeadRange > 0) { + if (expandableHunk.maxExpandHeadRange <= 10) { + return ( + + + + ); + } else { + return ( + + {" "} + + + ); + } } // hunk header must be defined return ; }; + createHunkFooter = (expandableHunk: ExpandableHunk) => { + if (expandableHunk.maxExpandBottomRange > 0) { + if (expandableHunk.maxExpandBottomRange <= 10) { + return ( + + + + ); + } else { + return ( + + {" "} + + + ); + } + } + // hunk footer must be defined + return ; + }; + + createLastHunkFooter = (expandableHunk: ExpandableHunk) => { + if (expandableHunk.maxExpandBottomRange !== 0) { + return ( + + {" "} + + + ); + } + // hunk header must be defined + return ; + }; + + expandHead = (expandableHunk: ExpandableHunk, count: number) => { + return () => { + return expandableHunk + .expandHead(count) + .then(this.diffExpanded) + .catch(this.diffExpansionFailed); + }; + }; + + expandBottom = (expandableHunk: ExpandableHunk, count: number) => { + return () => { + return expandableHunk + .expandBottom(count) + .then(this.diffExpanded) + .catch(this.diffExpansionFailed); + }; + }; + + diffExpanded = (newFile: File) => { + this.setState({ file: newFile, diffExpander: new DiffExpander(newFile) }); + }; + + diffExpansionFailed = (err: any) => { + this.setState({ expansionError: err }); + }; + collectHunkAnnotations = (hunk: HunkType) => { - const { annotationFactory, file } = this.props; + const { annotationFactory } = this.props; + const { file } = this.state; if (annotationFactory) { return annotationFactory({ hunk, @@ -160,7 +276,8 @@ class DiffFile extends React.Component { }; handleClickEvent = (change: Change, hunk: HunkType) => { - const { file, onClick } = this.props; + const { onClick } = this.props; + const { file } = this.state; const context = { changeId: getChangeKey(change), change, @@ -183,19 +300,35 @@ class DiffFile extends React.Component { } }; - renderHunk = (hunk: HunkType, i: number) => { + renderHunk = (file: File, expandableHunk: ExpandableHunk, i: number) => { + const hunk = expandableHunk.hunk; if (this.props.markConflicts && hunk.changes) { this.markConflicts(hunk); } - return [ - {this.createHunkHeader(hunk, i)}, + const items = []; + if (file._links?.lines) { + items.push(this.createHunkHeader(expandableHunk)); + } else if (i > 0) { + items.push(); + } + + items.push( - ]; + ); + if (file._links?.lines) { + if (i === file.hunks!.length - 1) { + items.push(this.createLastHunkFooter(expandableHunk)); + } else { + items.push(this.createHunkFooter(expandableHunk)); + } + } + return items; }; markConflicts = (hunk: HunkType) => { @@ -251,19 +384,11 @@ class DiffFile extends React.Component { return ; }; - concat = (array: object[][]) => { - if (array.length > 0) { - return array.reduce((a, b) => a.concat(b)); - } else { - return []; - } - }; - hasContent = (file: File) => file && !file.isBinary && file.hunks && file.hunks.length > 0; render() { - const { file, fileControlFactory, fileAnnotationFactory, t } = this.props; - const { collapsed, sideBySide } = this.state; + const { fileControlFactory, fileAnnotationFactory, t } = this.props; + const { file, collapsed, sideBySide, diffExpander, expansionError } = this.state; const viewType = sideBySide ? "split" : "unified"; let body = null; @@ -275,7 +400,11 @@ class DiffFile extends React.Component {
    {fileAnnotations} - {(hunks: HunkType[]) => this.concat(hunks.map(this.renderHunk))} + {(hunks: HunkType[]) => + hunks?.map((hunk, n) => { + return this.renderHunk(file, diffExpander.getHunk(n), n); + }) + }
    ); @@ -306,8 +435,21 @@ class DiffFile extends React.Component { ) : null; + let errorModal; + if (expansionError) { + errorModal = ( + this.setState({ expansionError: undefined })} + body={} + active={true} + /> + ); + } + return ( + {errorModal}
    string; }; diff --git a/scm-ui/ui-components/src/repos/HunkExpandDivider.tsx b/scm-ui/ui-components/src/repos/HunkExpandDivider.tsx new file mode 100644 index 0000000000..db2da6c247 --- /dev/null +++ b/scm-ui/ui-components/src/repos/HunkExpandDivider.tsx @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC } from "react"; +// @ts-ignore +import { Decoration } from "react-diff-view"; +import styled from "styled-components"; + +const HunkDivider = styled.div` + background: #98d8f3; + font-size: 0.7rem; + padding-left: 1.78em; +`; + +const HunkExpandDivider: FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export default HunkExpandDivider; diff --git a/scm-ui/ui-components/src/repos/HunkExpandLink.tsx b/scm-ui/ui-components/src/repos/HunkExpandLink.tsx new file mode 100644 index 0000000000..0b337c5bd2 --- /dev/null +++ b/scm-ui/ui-components/src/repos/HunkExpandLink.tsx @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC, useState } from "react"; +import { useTranslation } from "react-i18next"; +import classNames from "classnames"; +import styled from "styled-components"; + +type Props = { + icon: string; + text: string; + onClick: () => Promise; +}; + +const ExpandLink = styled.span` + cursor: pointer; +`; + +const HunkExpandLink: FC = ({ icon, text, onClick }) => { + const [t] = useTranslation("repos"); + const [loading, setLoading] = useState(false); + + const onClickWithLoadingMarker = () => { + if (loading) { + return; + } + setLoading(true); + onClick().then(() => setLoading(false)); + }; + + return ( + + {loading ? t("diff.expanding") : text} + + ); +}; + +export default HunkExpandLink; diff --git a/scm-ui/ui-components/src/repos/annotate/Annotate.stories.tsx b/scm-ui/ui-components/src/repos/annotate/Annotate.stories.tsx new file mode 100644 index 0000000000..41345b2744 --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/Annotate.stories.tsx @@ -0,0 +1,131 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { storiesOf } from "@storybook/react"; +import React, { FC } from "react"; +import styled from "styled-components"; +import Annotate from "./Annotate"; +import { MemoryRouter } from "react-router-dom"; +import repository from "../../__resources__/repository"; +import { Binder, BinderContext } from "@scm-manager/ui-extensions"; +import { Person } from "../../avatar/Avatar"; +import { AnnotatedSource } from "@scm-manager/ui-types"; + +const Wrapper = styled.div` + margin: 2rem; +`; + +const commitCreateNewApp = { + revision: "0d8c1d328f4599b363755671afe667c7ace52bae", + author: { + name: "Arthur Dent", + mail: "arthur.dent@hitchhiker.com" + }, + description: "create new app", + when: new Date("2020-04-09T13:07:42Z") +}; + +const commitFixedMissingImport = { + revision: "fab38559ce3ab8c388e067712b4bd7ab94b9fa9b", + author: { + name: "Tricia Marie McMillan", + mail: "trillian@hitchhiker.com" + }, + description: "fixed missing import", + when: new Date("2020-05-10T09:18:42Z") +}; + +const commitImplementMain = { + revision: "5203292ab2bc0c020dd22adc4d3897da4930e43f", + author: { + name: "Ford Prefect", + mail: "ford.prefect@hitchhiker.com" + }, + description: "implemented main function", + when: new Date("2020-04-12T16:29:42Z") +}; + +const source: AnnotatedSource = { + language: "go", + lines: [ + { + lineNumber: 1, + code: "package main", + ...commitCreateNewApp + }, + { + lineNumber: 2, + code: "", + ...commitCreateNewApp + }, + { + lineNumber: 3, + code: 'import "fmt"', + ...commitFixedMissingImport + }, + { + lineNumber: 4, + code: "", + ...commitFixedMissingImport + }, + { + lineNumber: 5, + code: "func main() {", + ...commitCreateNewApp + }, + { + lineNumber: 6, + code: ' fmt.Println("Hello World")', + ...commitImplementMain + }, + { + lineNumber: 7, + code: "}", + ...commitCreateNewApp + }, + { + lineNumber: 8, + code: "", + ...commitCreateNewApp + } + ] +}; + +const Robohash: FC = ({ children }) => { + const binder = new Binder("robohash"); + binder.bind("avatar.factory", (person: Person) => `https://robohash.org/${person.mail}.png`); + return {children}; +}; + +storiesOf("Annotate", module) + .addDecorator(storyFn => {storyFn()}) + .addDecorator(storyFn => {storyFn()}) + .add("Default", () => ( + + )) + .add("With Avatars", () => ( + + + + )); diff --git a/scm-ui/ui-components/src/repos/annotate/Annotate.tsx b/scm-ui/ui-components/src/repos/annotate/Annotate.tsx new file mode 100644 index 0000000000..d95b24fcdf --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/Annotate.tsx @@ -0,0 +1,157 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC, useReducer } from "react"; +import { Repository, AnnotatedSource, AnnotatedLine } from "@scm-manager/ui-types"; + +// @ts-ignore +import { LightAsync as ReactSyntaxHighlighter, createElement } from "react-syntax-highlighter"; +import { arduinoLight } from "react-syntax-highlighter/dist/cjs/styles/hljs"; +import { DateInput } from "../../useDateFormatter"; +import Popover from "./Popover"; +import AnnotateLine from "./AnnotateLine"; +import { Action } from "./actions"; + +type Props = { + source: AnnotatedSource; + repository: Repository; + baseDate?: DateInput; +}; + +type State = { + annotation?: AnnotatedLine; + offset?: number; + line?: number; + onPopover: boolean; + onLine: boolean; +}; + +const initialState = { + onPopover: false, + onLine: false +}; + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "enter-line": { + if (state.onPopover) { + return state; + } + return { + annotation: action.annotation, + offset: action.offset, + line: action.line, + onLine: true, + onPopover: false + }; + } + case "leave-line": { + if (state.onPopover) { + return { + ...state, + onLine: false + }; + } + return initialState; + } + case "enter-popover": { + return { + ...state, + onPopover: true + }; + } + case "leave-popover": { + if (state.onLine) { + return { + ...state, + onPopover: false + }; + } + return initialState; + } + } +}; + +const Annotate: FC = ({ source, repository, baseDate }) => { + const [state, dispatch] = useReducer(reducer, initialState); + + const defaultRenderer = ({ rows, stylesheet, useInlineStyles }: any) => { + let lastRevision = ""; + return rows.map((node: React.ReactNode, i: number) => { + const line = createElement({ + node, + stylesheet, + useInlineStyles, + key: `code-segment${i}` + }); + + if (i + 1 < rows.length) { + const annotation = source.lines[i]; + const newAnnotation = annotation.revision !== lastRevision; + lastRevision = annotation.revision; + return ( + + {line} + + ); + } + + return line; + }); + }; + + let popover = null; + if ((state.onPopover || state.onLine) && state.annotation) { + popover = ( + + ); + } + + const code = source.lines.reduce((content, line) => { + content += line.code + "\n"; + return content; + }, ""); + + return ( +
    + {popover} + + {code} + +
    + ); +}; + +export default Annotate; diff --git a/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx b/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx new file mode 100644 index 0000000000..7d8401aa70 --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx @@ -0,0 +1,145 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC, Dispatch, useRef } from "react"; +import styled from "styled-components"; +import AuthorImage from "./AuthorImage"; +import DateShort from "../../DateShort"; +import { Action } from "./actions"; +import { AnnotatedLine } from "@scm-manager/ui-types"; + +const LineElement = styled.div` + display: inline-block; + margin: 0; + padding: 0; + height: 100%; + vertical-align: top; +`; + +const Author = styled(LineElement)` + width: 8em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const When = styled(LineElement)` + display: inline-block; + + width: 6.5em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + margin: 0 0.5em; +`; + +const LineNumber = styled(LineElement)` + width: 3em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + border-left: 1px solid lightgrey; + border-right: 1px solid lightgrey; + + text-align: right; + + padding: 0 0.5em; +`; + +const Line = styled.div` + margin: 0; + padding: 0; + height: 1.5em; + vertical-align: top; +`; + +const Metadata = styled(LineElement)` + cursor: help; +`; + +const EmptyMetadata = styled(LineElement)` + width: 16.7em; +`; + +const dispatchDeferred = (dispatch: Dispatch, action: Action) => { + setTimeout(() => dispatch(action), 250); +}; + +type Props = { + annotation: AnnotatedLine; + showAnnotation: boolean; + nr: number; + dispatch: Dispatch; +}; + +const AnnotateLine: FC = ({ annotation, showAnnotation, dispatch, nr, children }) => { + const link = useRef(null); + + const onMouseEnter = () => { + if (showAnnotation) { + dispatchDeferred(dispatch, { + annotation, + line: nr, + offset: link.current!.offsetTop, + type: "enter-line" + }); + } + }; + + const OnMouseLeave = () => { + if (showAnnotation) { + dispatchDeferred(dispatch, { + line: nr, + type: "leave-line" + }); + } + }; + + if (!showAnnotation) { + return ( + + + {nr} {children} + + ); + } + + return ( + + + + + {annotation.author.name} + {" "} + + + {" "} + + {nr} {children} + + ); +}; + +export default AnnotateLine; diff --git a/scm-ui/ui-components/src/repos/annotate/AuthorImage.tsx b/scm-ui/ui-components/src/repos/annotate/AuthorImage.tsx new file mode 100644 index 0000000000..04d74700e7 --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/AuthorImage.tsx @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { AvatarImage } from "../../avatar"; +import styled from "styled-components"; + +const AuthorImage = styled(AvatarImage)` + width: 1em; + height: 1em; + margin-right: 0.2em; + margin-bottom: 0.2em; + vertical-align: middle; +`; + +export default AuthorImage; diff --git a/scm-ui/ui-components/src/repos/annotate/Popover.tsx b/scm-ui/ui-components/src/repos/annotate/Popover.tsx new file mode 100644 index 0000000000..b1f3f3a55e --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/Popover.tsx @@ -0,0 +1,143 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC, useState, useRef, useLayoutEffect, Dispatch } from "react"; +import styled from "styled-components"; +import { Link } from "react-router-dom"; +import DateFromNow from "../../DateFromNow"; +import { SingleContributor } from "../changesets"; +import { DateInput } from "../../useDateFormatter"; +import { Repository, AnnotatedLine } from "@scm-manager/ui-types"; +import AuthorImage from "./AuthorImage"; +import { Action } from "./actions"; +import {useTranslation} from "react-i18next"; + +const PopoverContainer = styled.div` + position: absolute; + left: 2.25em; + z-index: 100; + width: 30em; + display: block; + + &:before { + position: absolute; + content: ""; + border-style: solid; + pointer-events: none; + height: 0; + width: 0; + top: 100%; + left: 5.5em; + border-color: transparent; + border-bottom-color: white; + border-left-color: white; + border-width: 0.4rem; + margin-left: -0.4rem; + margin-top: -0.4rem; + -webkit-transform-origin: center; + transform-origin: center; + box-shadow: -1px 1px 2px rgba(10, 10, 10, 0.2); + transform: rotate(-45deg); + } +`; + +const SmallHr = styled.hr` + margin: 0.5em 0; +`; + +const PopoverHeading = styled.div` + height: 1.5em; +`; + +const PopoverDescription = styled.p` + margin-top: 0.5em; + overflow-wrap: break-word; +`; + +const shortRevision = (revision: string) => { + if (revision.length > 7) { + return revision.substring(0, 7); + } + return revision; +}; + +type PopoverProps = { + annotation: AnnotatedLine; + offsetTop?: number; + repository: Repository; + baseDate?: DateInput; + dispatch: Dispatch; +}; + +const Popover: FC = ({ annotation, offsetTop, repository, baseDate, dispatch }) => { + const [t] = useTranslation("repos"); + const [height, setHeight] = useState(125); + const ref = useRef(null); + useLayoutEffect(() => { + if (ref.current) { + setHeight(ref.current.clientHeight); + } + }, [ref]); + + const onMouseEnter = () => { + dispatch({ + type: "enter-popover" + }); + }; + + const OnMouseLeave = () => { + dispatch({ + type: "leave-popover" + }); + }; + + const top = (offsetTop || 0) - height - 5; + return ( + + + + + + + + + +

    + {t("changeset.label") + " "} + + {shortRevision(annotation.revision)} + +

    + {annotation.description} +
    + ); +}; + +export default Popover; diff --git a/scm-ui/ui-components/src/repos/annotate/actions.ts b/scm-ui/ui-components/src/repos/annotate/actions.ts new file mode 100644 index 0000000000..8ac1158b4f --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/actions.ts @@ -0,0 +1,47 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { AnnotatedLine } from "@scm-manager/ui-types"; + +type EnterLine = { + annotation: AnnotatedLine; + offset: number; + line: number; + type: "enter-line"; +}; + +type LeaveLine = { + line: number; + type: "leave-line"; +}; + +type EnterPopover = { + type: "enter-popover"; +}; + +type LeavePopover = { + type: "leave-popover"; +}; + +export type Action = EnterLine | LeaveLine | EnterPopover | LeavePopover; diff --git a/scm-ui/ui-components/src/repos/annotate/index.ts b/scm-ui/ui-components/src/repos/annotate/index.ts new file mode 100644 index 0000000000..84534a9c60 --- /dev/null +++ b/scm-ui/ui-components/src/repos/annotate/index.ts @@ -0,0 +1,25 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export { default as Annotate } from "./Annotate"; diff --git a/scm-ui/ui-components/src/repos/changesets/ChangesetAuthor.tsx b/scm-ui/ui-components/src/repos/changesets/ChangesetAuthor.tsx index c70ce4ca56..b326cd0b21 100644 --- a/scm-ui/ui-components/src/repos/changesets/ChangesetAuthor.tsx +++ b/scm-ui/ui-components/src/repos/changesets/ChangesetAuthor.tsx @@ -21,53 +21,162 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; -import { Changeset } from "@scm-manager/ui-types"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import { WithTranslation, withTranslation } from "react-i18next"; +import React, { FC } from "react"; +import { Changeset, Person } from "@scm-manager/ui-types"; +import { useTranslation } from "react-i18next"; +import { useBinder } from "@scm-manager/ui-extensions"; +import { EXTENSION_POINT } from "../../avatar/Avatar"; +import styled from "styled-components"; +import CommaSeparatedList from "../../CommaSeparatedList"; +import ContributorAvatar from "./ContributorAvatar"; -type Props = WithTranslation & { +type Props = { changeset: Changeset; }; -class ChangesetAuthor extends React.Component { - render() { - const { changeset } = this.props; - if (!changeset.author) { - return null; - } +type PersonProps = { + person: Person; + className?: string; + displayTextOnly?: boolean; +}; - const { name, mail } = changeset.author; - if (mail) { - return this.withExtensionPoint(this.renderWithMail(name, mail)); - } - return this.withExtensionPoint(<>{name}); +const useAvatar = (person: Person): string | undefined => { + const binder = useBinder(); + const factory: (person: Person) => string | undefined = binder.getExtension(EXTENSION_POINT); + if (factory) { + return factory(person); } +}; - renderWithMail(name: string, mail: string) { - const { t } = this.props; +const AvatarList = styled.span` + & > :not(:last-child) { + margin-right: 0.25em; + } +`; + +type PersonAvatarProps = { + person: Person; + avatar: string; +}; + +const ContributorWithAvatar: FC = ({ person, avatar }) => { + const [t] = useTranslation("repos"); + if (person.mail) { return ( - - {name} + + ); } + return ; +}; - withExtensionPoint(child: any) { - const { t } = this.props; +export const SingleContributor: FC = ({ person, className, displayTextOnly }) => { + const [t] = useTranslation("repos"); + const avatar = useAvatar(person); + if (!displayTextOnly && avatar) { + return ; + } + if (person.mail) { + return ( + + {person.name} + + ); + } + return {person.name}; +}; + +type PersonsProps = { + persons: ReadonlyArray; + label: string; + displayTextOnly?: boolean; +}; + +const Contributors: FC = ({ persons, label, displayTextOnly }) => { + const binder = useBinder(); + + const [t] = useTranslation("repos"); + if (persons.length === 1) { return ( <> - {t("changeset.author.prefix")} {child} - + {t(label)} ); } -} -export default withTranslation("repos")(ChangesetAuthor); + const avatarFactory = binder.getExtension(EXTENSION_POINT); + if (avatarFactory) { + return ( + <> + {t(label)}{" "} + + {persons.map(p => ( + + ))} + + + ); + } else { + return ( + <> + {t(label)}{" "} + "- " + person.name).join("\n")}> + {t("changeset.contributors.more", { count: persons.length })} + + + ); + } +}; + +const emptyListOfContributors: ReadonlyArray = []; + +const ChangesetAuthor: FC = ({ changeset }) => { + const binder = useBinder(); + + const getCoAuthors = () => { + return filterContributorsByType("Co-authored-by"); + }; + + const getCommitters = () => { + return filterContributorsByType("Committed-by"); + }; + + const filterContributorsByType = (type: string) => { + if (changeset.contributors) { + return changeset.contributors.filter(p => p.type === type).map(contributor => contributor.person); + } + return emptyListOfContributors; + }; + + const authorLine = []; + if (changeset.author) { + authorLine.push( + + ); + } + + const commiters = getCommitters(); + if (commiters.length > 0) { + authorLine.push(); + } + + const coAuthors = getCoAuthors(); + if (coAuthors.length > 0) { + authorLine.push(); + } + + // extensions + const extensions = binder.getExtensions("changesets.author.suffix", { changeset }); + if (extensions) { + authorLine.push(...extensions); + } + + return {authorLine}; +}; + +export default ChangesetAuthor; diff --git a/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx b/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx index b6163ab3f9..0956f7f131 100644 --- a/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx +++ b/scm-ui/ui-components/src/repos/changesets/ChangesetRow.tsx @@ -123,7 +123,7 @@ class ChangesetRow extends React.Component {

    - + diff --git a/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx b/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx new file mode 100644 index 0000000000..7408e24dc1 --- /dev/null +++ b/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { storiesOf } from "@storybook/react"; +import * as React from "react"; +import styled from "styled-components"; +import { MemoryRouter } from "react-router-dom"; +import repository from "../../__resources__/repository"; +import ChangesetRow from "./ChangesetRow"; +import {one, two, three, four} from "../../__resources__/changesets"; +import {Binder, BinderContext} from "@scm-manager/ui-extensions"; +// @ts-ignore +import hitchhiker from "../../__resources__/hitchhiker.png"; +import {Person} from "../../avatar/Avatar"; +import {Changeset} from "@scm-manager/ui-types/src"; + +const Wrapper = styled.div` + margin: 2rem; +`; + +const robohash = (person: Person) => { + return `https://robohash.org/${person.mail}`; +} + +const withAvatarFactory = (factory: (person: Person) => string, changeset: Changeset) => { + const binder = new Binder("changeset stories"); + binder.bind("avatar.factory", factory); + return ( + + + + ); +}; + +storiesOf("Changesets", module) + .addDecorator(story => {story()}) + .addDecorator(storyFn => {storyFn()}) + .add("Default", () => ( + + )) + .add("With Committer", () => ( + + )) + .add("With Committer and Co-Author", () => ( + + )) + .add("With multiple Co-Authors", () => ( + + )) + .add("With avatar", () => { + return withAvatarFactory(person => hitchhiker, three); + }) + .add("Commiter and Co-Authors with avatar", () => { + return withAvatarFactory(robohash, one); + }) + .add("Co-Authors with avatar", () => { + return withAvatarFactory(robohash, four); + }); diff --git a/scm-ui/ui-components/src/repos/changesets/ContributorAvatar.tsx b/scm-ui/ui-components/src/repos/changesets/ContributorAvatar.tsx new file mode 100644 index 0000000000..f9ec7695e0 --- /dev/null +++ b/scm-ui/ui-components/src/repos/changesets/ContributorAvatar.tsx @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import styled from "styled-components"; +import Image from "../../Image"; + +const ContributorAvatar = styled(Image)` + width: 1em; + height: 1em; + vertical-align: middle; + border-radius: 0.25em; + margin-bottom: 0.2em; +`; + +export default ContributorAvatar; diff --git a/scm-ui/ui-components/src/repos/changesets/index.ts b/scm-ui/ui-components/src/repos/changesets/index.ts index 271dbde2f8..b4c83dd4c3 100644 --- a/scm-ui/ui-components/src/repos/changesets/index.ts +++ b/scm-ui/ui-components/src/repos/changesets/index.ts @@ -25,7 +25,7 @@ import * as changesets from "./changesets"; export { changesets }; -export { default as ChangesetAuthor } from "./ChangesetAuthor"; +export { default as ChangesetAuthor, SingleContributor } from "./ChangesetAuthor"; export { default as ChangesetButtonGroup } from "./ChangesetButtonGroup"; export { default as ChangesetDiff } from "./ChangesetDiff"; export { default as ChangesetId } from "./ChangesetId"; @@ -34,3 +34,4 @@ export { default as ChangesetRow } from "./ChangesetRow"; export { default as ChangesetTag } from "./ChangesetTag"; export { default as ChangesetTags } from "./ChangesetTags"; export { default as ChangesetTagsCollapsed } from "./ChangesetTagsCollapsed"; +export { default as ContributorAvatar } from "./ContributorAvatar"; diff --git a/scm-ui/ui-components/src/repos/index.ts b/scm-ui/ui-components/src/repos/index.ts index 01f6c97daa..a3551ed6b5 100644 --- a/scm-ui/ui-components/src/repos/index.ts +++ b/scm-ui/ui-components/src/repos/index.ts @@ -39,6 +39,7 @@ import { export { diffs }; +export * from "./annotate"; export * from "./changesets"; export { default as Diff } from "./Diff"; diff --git a/scm-ui/ui-components/src/DateFromNow.test.ts b/scm-ui/ui-components/src/useDateFormatter.test.ts similarity index 96% rename from scm-ui/ui-components/src/DateFromNow.test.ts rename to scm-ui/ui-components/src/useDateFormatter.test.ts index 19acc08fda..4f0238332c 100644 --- a/scm-ui/ui-components/src/DateFromNow.test.ts +++ b/scm-ui/ui-components/src/useDateFormatter.test.ts @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { chooseLocale, supportedLocales } from "./DateFromNow"; +import { chooseLocale, supportedLocales } from "./useDateFormatter"; describe("test choose locale", () => { it("should choose de", () => { diff --git a/scm-ui/ui-components/src/useDateFormatter.ts b/scm-ui/ui-components/src/useDateFormatter.ts new file mode 100644 index 0000000000..798b2d5d39 --- /dev/null +++ b/scm-ui/ui-components/src/useDateFormatter.ts @@ -0,0 +1,126 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useTranslation } from "react-i18next"; +import { enUS, de, es } from "date-fns/locale"; +import { formatDistance, format, Locale, parseISO } from "date-fns"; + +type LocaleMap = { + [key: string]: Locale; +}; + +export const supportedLocales: LocaleMap = { + enUS, + en: enUS, + de, + es +}; + +type Options = { + addSuffix: boolean; + locale: Locale; + timeZone?: string; +}; + +export const chooseLocale = (language: string, languages?: string[]) => { + for (const lng of languages || []) { + const locale = supportedLocales[lng]; + if (locale) { + return locale; + } + } + + const locale = supportedLocales[language]; + if (locale) { + return locale; + } + + return enUS; +}; + +export type DateInput = Date | string; + +export type DateProps = { + date?: DateInput; + timeZone?: string; + + /** + * baseDate is the date from which the distance is calculated, + * default is the current time (new Date()). This property + * is required to keep snapshots tests green over the time on + * ci server. + */ + baseDate?: DateInput; +}; + +const createOptions = (locale: Locale, timeZone?: string) => { + const options: Options = { + addSuffix: true, + locale + }; + if (timeZone) { + options.timeZone = timeZone; + } + return options; +}; + +const createBaseDate = (baseDate?: DateInput) => { + if (baseDate) { + return toDate(baseDate); + } + return new Date(); +}; + +const toDate = (value: DateInput): Date => { + if (value instanceof Date) { + return value; + } + return parseISO(value); +}; + +const useDateFormatter = ({ date, baseDate, timeZone }: DateProps) => { + const { i18n } = useTranslation(); + if (!date) { + return null; + } + + const isoDate = toDate(date); + const base = createBaseDate(baseDate); + + const locale = chooseLocale(i18n.language, i18n.languages); + const options = createOptions(locale, timeZone); + return { + formatShort() { + return format(isoDate, "yyyy-MM-dd", options); + }, + formatFull() { + return format(isoDate, "yyyy-MM-dd HH:mm:ss", options); + }, + formatDistance() { + return formatDistance(isoDate, base, options); + } + }; +}; + +export default useDateFormatter; diff --git a/scm-ui/ui-components/src/validation.ts b/scm-ui/ui-components/src/validation.ts index e6502a7ed9..fcdb1a6dfd 100644 --- a/scm-ui/ui-components/src/validation.ts +++ b/scm-ui/ui-components/src/validation.ts @@ -22,7 +22,7 @@ * SOFTWARE. */ -const nameRegex = /^[A-Za-z0-9\.\-_][A-Za-z0-9\.\-_@]*$/; +export const nameRegex = /^[A-Za-z0-9\.\-_][A-Za-z0-9\.\-_@]*$/; export const isNameValid = (name: string) => { return nameRegex.test(name); diff --git a/scm-ui/ui-extensions/package.json b/scm-ui/ui-extensions/package.json index a652c703f7..398b6c6f6f 100644 --- a/scm-ui/ui-extensions/package.json +++ b/scm-ui/ui-extensions/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-extensions", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "main": "src/index.ts", "license": "MIT", "private": false, diff --git a/scm-ui/ui-plugins/package.json b/scm-ui/ui-plugins/package.json index b88637e3f5..e48e8258b3 100644 --- a/scm-ui/ui-plugins/package.json +++ b/scm-ui/ui-plugins/package.json @@ -1,13 +1,13 @@ { "name": "@scm-manager/ui-plugins", - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "license": "MIT", "bin": { "ui-plugins": "./bin/ui-plugins.js" }, "dependencies": { - "@scm-manager/ui-components": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-components": "^2.2.0-SNAPSHOT", + "@scm-manager/ui-extensions": "^2.1.0", "classnames": "^2.2.6", "query-string": "^5.0.1", "react": "^16.10.2", @@ -15,17 +15,17 @@ "react-redux": "^5.0.7", "react-router-dom": "^5.1.2", "redux": "^4.0.0", - "styled-components": "^4.4.0" + "styled-components": "^5.1.0" }, "devDependencies": { - "@scm-manager/babel-preset": "^2.0.0-SNAPSHOT", - "@scm-manager/eslint-config": "^2.0.0-SNAPSHOT", - "@scm-manager/jest-preset": "^2.0.0-SNAPSHOT", - "@scm-manager/prettier-config": "^2.0.0-SNAPSHOT", - "@scm-manager/tsconfig": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-scripts": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", + "@scm-manager/babel-preset": "^2.1.0", + "@scm-manager/eslint-config": "^2.1.0", + "@scm-manager/jest-preset": "^2.1.0", + "@scm-manager/prettier-config": "^2.1.0", + "@scm-manager/tsconfig": "^2.1.0", + "@scm-manager/ui-scripts": "^2.1.0", + "@scm-manager/ui-tests": "^2.1.0", + "@scm-manager/ui-types": "^2.1.0", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", "@types/fetch-mock": "^7.3.1", @@ -35,7 +35,7 @@ "@types/react": "^16.9.9", "@types/react-redux": "5.0.7", "@types/react-router-dom": "^5.1.0", - "@types/styled-components": "^4.1.19", + "@types/styled-components": "^5.1.0", "jest": "^24.9.0" }, "publishConfig": { diff --git a/scm-ui/ui-polyfill/package.json b/scm-ui/ui-polyfill/package.json index 8beb8d5836..b7a3c65bf8 100644 --- a/scm-ui/ui-polyfill/package.json +++ b/scm-ui/ui-polyfill/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-polyfill", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "Polyfills for SCM-Manager UI", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-scripts/package.json b/scm-ui/ui-scripts/package.json index f7865ce5b5..814c434dd4 100644 --- a/scm-ui/ui-scripts/package.json +++ b/scm-ui/ui-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-scripts", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "Build scripts for SCM-Manager", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-styles/package.json b/scm-ui/ui-styles/package.json index 15ca4c1ec4..49256f8a26 100644 --- a/scm-ui/ui-styles/package.json +++ b/scm-ui/ui-styles/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-styles", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "Styles for SCM-Manager", "main": "src/scm.scss", "license": "MIT", diff --git a/scm-ui/ui-tests/package.json b/scm-ui/ui-tests/package.json index be53c4164d..f01de13f97 100644 --- a/scm-ui/ui-tests/package.json +++ b/scm-ui/ui-tests/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-tests", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "UI-Tests helpers", "author": "Sebastian Sdorra ", "license": "MIT", diff --git a/scm-ui/ui-types/package.json b/scm-ui/ui-types/package.json index a366ad3a1b..96d9a14a97 100644 --- a/scm-ui/ui-types/package.json +++ b/scm-ui/ui-types/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-types", - "version": "2.0.0-SNAPSHOT", + "version": "2.1.0", "description": "Flow types for SCM-Manager related Objects", "main": "src/index.ts", "files": [ diff --git a/scm-ui/ui-types/src/Annotate.ts b/scm-ui/ui-types/src/Annotate.ts new file mode 100644 index 0000000000..b7d64e45da --- /dev/null +++ b/scm-ui/ui-types/src/Annotate.ts @@ -0,0 +1,39 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { Person } from "./Person"; + +export type AnnotatedSource = { + lines: AnnotatedLine[]; + language?: string; +}; + +export type AnnotatedLine = { + author: Person; + code: string; + description: string; + lineNumber: number; + revision: string; + when: Date; +}; diff --git a/scm-ui/ui-types/src/Changesets.ts b/scm-ui/ui-types/src/Changesets.ts index 7eeb9eb27e..f182da296c 100644 --- a/scm-ui/ui-types/src/Changesets.ts +++ b/scm-ui/ui-types/src/Changesets.ts @@ -25,15 +25,14 @@ import { Collection, Links } from "./hal"; import { Tag } from "./Tags"; import { Branch } from "./Branches"; +import { Person } from "./Person"; export type Changeset = Collection & { id: string; date: Date; - author: { - name: string; - mail?: string; - }; + author: Person; description: string; + contributors?: Contributor[]; _links: Links; _embedded: { tags?: Tag[]; @@ -42,6 +41,11 @@ export type Changeset = Collection & { }; }; +export type Contributor = { + person: Person; + type: string; +}; + export type ParentChangeset = { id: string; _links: Links; diff --git a/scm-ui/ui-types/src/Person.ts b/scm-ui/ui-types/src/Person.ts new file mode 100644 index 0000000000..6560407a68 --- /dev/null +++ b/scm-ui/ui-types/src/Person.ts @@ -0,0 +1,28 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export type Person = { + name: string; + mail?: string; +}; diff --git a/scm-ui/ui-types/src/index.ts b/scm-ui/ui-types/src/index.ts index e98b81f4f2..a0ce96dac9 100644 --- a/scm-ui/ui-types/src/index.ts +++ b/scm-ui/ui-types/src/index.ts @@ -29,16 +29,16 @@ export { Me } from "./Me"; export { DisplayedUser, User } from "./User"; export { Group, Member } from "./Group"; -export { - Repository, - RepositoryCollection, - RepositoryGroup -} from "./Repositories"; +export { Repository, RepositoryCollection, RepositoryGroup } from "./Repositories"; export { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes"; export { Branch, BranchRequest } from "./Branches"; -export { Changeset } from "./Changesets"; +export { Person } from "./Person"; + +export { Changeset, Contributor, ParentChangeset } from "./Changesets"; + +export { AnnotatedSource, AnnotatedLine } from "./Annotate"; export { Tag } from "./Tags"; @@ -46,22 +46,13 @@ export { Config } from "./Config"; export { IndexResources } from "./IndexResources"; -export { - Permission, - PermissionCreateEntry, - PermissionCollection -} from "./RepositoryPermissions"; +export { Permission, PermissionCreateEntry, PermissionCollection } from "./RepositoryPermissions"; export { SubRepository, File } from "./Sources"; export { SelectValue, AutocompleteObject } from "./Autocomplete"; -export { - Plugin, - PluginCollection, - PluginGroup, - PendingPlugins -} from "./Plugin"; +export { Plugin, PluginCollection, PluginGroup, PendingPlugins } from "./Plugin"; export { RepositoryRole } from "./RepositoryRole"; diff --git a/scm-ui/ui-webapp/package.json b/scm-ui/ui-webapp/package.json index ba54fb7689..ab7a419cd8 100644 --- a/scm-ui/ui-webapp/package.json +++ b/scm-ui/ui-webapp/package.json @@ -1,10 +1,10 @@ { "name": "@scm-manager/ui-webapp", - "version": "2.0.0-SNAPSHOT", + "version": "2.2.0-SNAPSHOT", "private": true, "dependencies": { - "@scm-manager/ui-components": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-components": "^2.2.0-SNAPSHOT", + "@scm-manager/ui-extensions": "^2.1.0", "classnames": "^2.2.5", "history": "^4.10.1", "i18next": "^17.3.0", @@ -22,14 +22,14 @@ "redux-devtools-extension": "^2.13.5", "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", - "styled-components": "^4.4.0", + "styled-components": "^5.1.0", "systemjs": "0.21.6" }, "scripts": { "test": "jest" }, "devDependencies": { - "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-tests": "^2.1.0", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", "@types/fetch-mock": "^7.3.1", @@ -39,7 +39,7 @@ "@types/react-dom": "^16.9.2", "@types/react-redux": "5.0.7", "@types/react-router-dom": "^5.1.0", - "@types/styled-components": "^4.1.19", + "@types/styled-components": "^5.1.0", "@types/systemjs": "^0.20.6", "fetch-mock": "^7.5.1", "react-test-renderer": "^16.10.2", diff --git a/scm-ui/ui-webapp/public/locales/de/commons.json b/scm-ui/ui-webapp/public/locales/de/commons.json index d88b77b850..158d5e6420 100644 --- a/scm-ui/ui-webapp/public/locales/de/commons.json +++ b/scm-ui/ui-webapp/public/locales/de/commons.json @@ -104,5 +104,8 @@ "community": "Community", "enterprise": "Enterprise" } + }, + "commaSeparatedList": { + "lastDivider": "und" } } diff --git a/scm-ui/ui-webapp/public/locales/de/groups.json b/scm-ui/ui-webapp/public/locales/de/groups.json index 43cdd51f7e..65035781b6 100644 --- a/scm-ui/ui-webapp/public/locales/de/groups.json +++ b/scm-ui/ui-webapp/public/locales/de/groups.json @@ -18,7 +18,7 @@ "errorTitle": "Fehler", "errorSubtitle": "Unbekannter Gruppen Fehler", "menu": { - "navigationLabel": "Gruppen", + "navigationLabel": "Gruppe", "informationNavLink": "Informationen", "settingsNavLink": "Einstellungen", "generalNavLink": "Generell", diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index a0fce5cbed..808bf431e9 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -82,14 +82,28 @@ "collapseDiffs": "Auf-/Zuklappen" }, "changeset": { + "label": "Changeset", "description": "Beschreibung", "summary": "Changeset <0/> wurde <1/> committet", "shortSummary": "Committet <0/> <1/>", "tags": "Tags", "diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt", - "author": { - "prefix": "Verfasst von", - "mailto": "Mail senden an" + "shortlink": { + "title": "Changeset {{id}} aus {{namespace}}/{{name}}" + }, + "parents": { + "label" : "Parent", + "label_plural": "Parents" + }, + "contributors": { + "mailto": "Mail senden an", + "list": "Liste der Mitwirkenden", + "authoredBy": "Verfasst von", + "committedBy": "Committed von", + "coAuthoredBy": "Co-Autoren", + "more": "{{count}} mehr", + "count": "{{count}} Mitwirkender", + "count_plural": "{{count}} Mitwirkende" }, "buttons": { "details": "Details", @@ -114,6 +128,7 @@ "content": { "historyButton": "History", "sourcesButton": "Sources", + "annotateButton": "Annotate", "downloadButton": "Download", "toggleButton": { "showMarkdown": "Markdown rendern", @@ -191,7 +206,15 @@ "diff": { "sideBySide": "Zur zweispaltigen Ansicht wechseln", "combined": "Zur kombinierten Ansicht wechseln", - "noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden." + "noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden.", + "expandByLines": "{{count}} weitere Zeile laden", + "expandByLines_plural": "{{count}} weitere Zeilen laden", + "expandComplete": "{{count}} verbleibende Zeile laden", + "expandComplete_plural": "Alle {{count}} verbleibenden Zeilen laden", + "expandLastBottomByLines": "Bis zu {{count}} weitere Zeilen laden", + "expandLastBottomComplete": "Alle verbleibenden Zeilen laden", + "expanding": "Zeilen werden geladen ...", + "expansionFailed": "Fehler beim Laden der zusätzlichen Zeilen" }, "fileUpload": { "clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.", diff --git a/scm-ui/ui-webapp/public/locales/en/commons.json b/scm-ui/ui-webapp/public/locales/en/commons.json index 3d5af75f78..444de0bea5 100644 --- a/scm-ui/ui-webapp/public/locales/en/commons.json +++ b/scm-ui/ui-webapp/public/locales/en/commons.json @@ -105,5 +105,8 @@ "community": "Community", "enterprise": "Enterprise" } + }, + "commaSeparatedList": { + "lastDivider": "and" } } diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index 2abf63eb47..e98052aba3 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -82,18 +82,32 @@ "collapseDiffs": "Collapse" }, "changeset": { + "label": "Changeset", "description": "Description", "summary": "Changeset <0/> was committed <1/>", "shortSummary": "Committed <0/> <1/>", "tags": "Tags", - "diffNotSupported": "Diff of changesets is not supported by the type of repository", - "author": { - "prefix": "Authored by", - "mailto": "Send mail to" + "shortlink": { + "title": "Changeset {{id}} of {{namespace}}/{{name}}" }, + "diffNotSupported": "Diff of changesets is not supported by the type of repository", "buttons": { "details": "Details", "sources": "Sources" + }, + "parents": { + "label" : "Parent", + "label_plural": "Parents" + }, + "contributors": { + "mailto": "Send mail to", + "list": "List of contributors", + "authoredBy": "Authored by", + "committedBy": "committed by", + "coAuthoredBy": "co authored by", + "more": "{{count}} more", + "count": "{{count}} Contributor", + "count_plural": "{{count}} Contributors" } }, "repositoryForm": { @@ -114,6 +128,7 @@ "content": { "historyButton": "History", "sourcesButton": "Sources", + "annotateButton": "Annotate", "downloadButton": "Download", "toggleButton": { "showMarkdown": "Render markdown", @@ -198,7 +213,15 @@ }, "sideBySide": "Switch to side-by-side view", "combined": "Switch to combined view", - "noDiffFound": "No Diff between the selected branches found." + "noDiffFound": "No Diff between the selected branches found.", + "expandByLines": "load {{count}} more line", + "expandByLines_plural": "load {{count}} more lines", + "expandComplete": "load {{count}} remaining line", + "expandComplete_plural": "load all {{count}} remaining lines", + "expandLastBottomByLines": "load up to {{count}} more lines", + "expandLastBottomComplete": "load all remaining lines", + "expanding": "loading lines ...", + "expansionFailed": "Error while loading additional lines" }, "fileUpload": { "clickHere": "Click here to select your file", diff --git a/scm-ui/ui-webapp/public/locales/es/commons.json b/scm-ui/ui-webapp/public/locales/es/commons.json index 47638c1d6e..33c8b70dd5 100644 --- a/scm-ui/ui-webapp/public/locales/es/commons.json +++ b/scm-ui/ui-webapp/public/locales/es/commons.json @@ -89,5 +89,8 @@ "passwordConfirmFailed": "Las contraseñas deben ser identicas", "submit": "Guardar", "changedSuccessfully": "Contraseña cambiada correctamente" + }, + "commaSeparatedList": { + "lastDivider": "y" } } diff --git a/scm-ui/ui-webapp/public/locales/es/repos.json b/scm-ui/ui-webapp/public/locales/es/repos.json index 6758a4841c..ddada3f8c3 100644 --- a/scm-ui/ui-webapp/public/locales/es/repos.json +++ b/scm-ui/ui-webapp/public/locales/es/repos.json @@ -79,7 +79,8 @@ "errorSubtitle": "No se han podido recuperar los changesets", "noChangesets": "No se han encontrado changesets para esta rama branch. Los commits podrían haber sido eliminados.", "branchSelectorLabel": "Ramas", - "collapseDiffs": "Colapso" + "collapseDiffs": "Colapso", + "contributors": "Lista de contribuyentes" }, "changeset": { "description": "Descripción", diff --git a/scm-ui/ui-webapp/src/i18n.ts b/scm-ui/ui-webapp/src/i18n.ts index 81b842f639..a3b6c2eba6 100644 --- a/scm-ui/ui-webapp/src/i18n.ts +++ b/scm-ui/ui-webapp/src/i18n.ts @@ -61,6 +61,15 @@ i18n init: { credentials: "same-origin" } + }, + + // configure LanguageDetector + // see https://github.com/i18next/i18next-browser-languageDetector#detector-options + detection: { + // we only use browser configuration + order: ["navigator"], + // we do not cache the detected language + caches: [] } }); diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx index cbfe3ae067..6ba44c0c1d 100644 --- a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx +++ b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx @@ -21,12 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; -import { Trans, WithTranslation, withTranslation } from "react-i18next"; +import React, { FC, useState } from "react"; +import { Trans, useTranslation, WithTranslation, withTranslation } from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import { Changeset, Repository, Tag } from "@scm-manager/ui-types"; +import { Changeset, Repository, Tag, ParentChangeset } from "@scm-manager/ui-types"; import { AvatarImage, AvatarWrapper, @@ -37,8 +37,11 @@ import { changesets, ChangesetTag, DateFromNow, - Level + Level, + Icon } from "@scm-manager/ui-components"; +import ContributorTable from "./ContributorTable"; +import { Link as ReactLink } from "react-router-dom"; type Props = WithTranslation & { changeset: Changeset; @@ -63,6 +66,87 @@ const BottomMarginLevel = styled(Level)` margin-bottom: 1rem !important; `; +const countContributors = (changeset: Changeset) => { + if (changeset.contributors) { + return changeset.contributors.length + 1; + } + return 1; +}; + +const ContributorLine = styled.div` + display: flex; + cursor: pointer; +`; + +const ContributorColumn = styled.p` + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +`; + +const CountColumn = styled.p` + text-align: right; + white-space: nowrap; +`; + +const ContributorDetails = styled.div` + display: flex; + flex-direction: column; + margin-bottom: 1rem; +`; + +const ContributorToggleLine = styled.p` + cursor: pointer; + /** maring-bottom is inherit from content p **/ + margin-bottom: 0.5rem !important; +`; + +const ChangesetSummary = styled.div` + display: flex; + justify-content: space-between; +` + +const SeparatedParents = styled.div` + margin-left: 1em; + a + a:before { + content: ",\\00A0"; + color: #4a4a4a; + } +`; + +const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => { + const [t] = useTranslation("repos"); + const [open, setOpen] = useState(false); + if (open) { + return ( + + setOpen(!open)}> + {t("changeset.contributors.list")} + + + + ); + } + return ( + <> + setOpen(!open)}> + + + + + ( + + {t("changeset.contributors.count", { count: countContributors(changeset) })} + + ) + + + + ); +}; + class ChangesetDetails extends React.Component { constructor(props: Props) { super(props); @@ -78,6 +162,11 @@ class ChangesetDetails extends React.Component { const description = changesets.parseDescription(changeset.description); const id = ; const date = ; + const parents = changeset._embedded.parents.map((parent: ParentChangeset) => ( + + {parent.id.substring(0, 7)} + + )); return ( <> @@ -100,17 +189,22 @@ class ChangesetDetails extends React.Component { -
    -

    - -

    -

    - -

    +
    + + +

    + +

    + {parents?.length > 0 && ( + + {t("changeset.parents.label", { count: parents?.length }) + ": "} + {parents} + + )} +
    {this.renderTags()}
    -

    {description.message.split("\n").map((item, key) => { return ( diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/ContributorTable.tsx b/scm-ui/ui-webapp/src/repos/components/changesets/ContributorTable.tsx new file mode 100644 index 0000000000..2aee44c3be --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/components/changesets/ContributorTable.tsx @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC } from "react"; +import { Changeset, Person } from "@scm-manager/ui-types"; +import styled from "styled-components"; +import { useTranslation } from "react-i18next"; +import { useBinder } from "@scm-manager/ui-extensions"; +import { ContributorAvatar, CommaSeparatedList } from "@scm-manager/ui-components"; + +type Props = { + changeset: Changeset; +}; + +const SizedTd = styled.td` + width: 10rem; +`; + +const Contributor: FC<{ person: Person }> = ({ person }) => { + const [t] = useTranslation("repos"); + const binder = useBinder(); + const avatarFactory = binder.getExtension("avatar.factory"); + let prefix = null; + if (avatarFactory) { + const avatar = avatarFactory(person); + if (avatar) { + prefix = ( + <> + {" "} + + ); + } + } + if (person.mail) { + return ( + + {prefix} + {person.name} + + ); + } + return <>{person.name}; +}; + +const ContributorTable: FC = ({ changeset }) => { + const [t] = useTranslation("plugins"); + + const collectAvailableContributorTypes = () => { + if (!changeset.contributors) { + return []; + } + // @ts-ignore + return [...new Set(changeset.contributors.map(contributor => contributor.type))]; + }; + + const getPersonsByContributorType = (type: string) => { + return changeset.contributors?.filter(contributor => contributor.type === type).map(t => t.person); + }; + + const getContributorsByType = () => { + const availableContributorTypes: string[] = collectAvailableContributorTypes(); + + const personsByContributorType = []; + for (const type of availableContributorTypes) { + personsByContributorType.push({ type, persons: getPersonsByContributorType(type) }); + } + return personsByContributorType; + }; + + return ( +

  • Name Description
    + + {t("changeset.contributor.type.author")}: + + + {getContributorsByType().map(contributor => ( + + {t("changeset.contributor.type." + contributor.type)}: + + + ))} +
    + +
    + + {contributor.persons!.map(person => ( + + ))} + +
    + ); +}; + +export default ContributorTable; diff --git a/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx b/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx index d04bd144b6..a57f2dfd22 100644 --- a/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/ChangesetView.tsx @@ -48,11 +48,17 @@ type Props = WithTranslation & { class ChangesetView extends React.Component { componentDidMount() { - const { fetchChangesetIfNeeded, repository } = this.props; - const id = this.props.match.params.id; + const { fetchChangesetIfNeeded, repository, id } = this.props; fetchChangesetIfNeeded(repository, id); } + componentDidUpdate(prevProps: Props) { + const { fetchChangesetIfNeeded, repository, id } = this.props; + if (prevProps.id !== id) { + fetchChangesetIfNeeded(repository, id); + } + } + render() { const { changeset, loading, error, t, repository } = this.props; @@ -73,6 +79,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { const loading = isFetchChangesetPending(state, repository, id); const error = getFetchChangesetFailure(state, repository, id); return { + id, changeset, error, loading diff --git a/scm-ui/ui-webapp/src/repos/modules/changesets.ts b/scm-ui/ui-webapp/src/repos/modules/changesets.ts index 3b1380ee45..4cf2cd249f 100644 --- a/scm-ui/ui-webapp/src/repos/modules/changesets.ts +++ b/scm-ui/ui-webapp/src/repos/modules/changesets.ts @@ -204,7 +204,7 @@ export default function reducer( ...state[_key], byId: { ..._oldByIds, - [changeset.id]: changeset + [payload.id]: changeset } } }; diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx index 38f803e958..f2dfe8a7b5 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx @@ -24,20 +24,27 @@ import React from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { Button, ButtonAddons } from "@scm-manager/ui-components"; +import { SourceViewSelection } from "../../containers/Content"; type Props = WithTranslation & { className?: string; - historyIsSelected: boolean; - showHistory: (p: boolean) => void; + selected: SourceViewSelection; + showSources: () => void; + showHistory: () => void; + showAnnotations: () => void; }; class FileButtonAddons extends React.Component { showHistory = () => { - this.props.showHistory(true); + this.props.showHistory(); }; showSources = () => { - this.props.showHistory(false); + this.props.showSources(); + }; + + showAnnotations = () => { + this.props.showAnnotations(); }; color = (selected: boolean) => { @@ -45,19 +52,26 @@ class FileButtonAddons extends React.Component { }; render() { - const { className, t, historyIsSelected } = this.props; + const { className, t, selected, showSources, showHistory, showAnnotations } = this.props; return (
    -
    +
    + +
    -