From 19de155f50429aaf4c1fb5193842b07cf159163e Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Tue, 26 Sep 2023 09:28:31 +0200 Subject: [PATCH] Fix broken documentation links Also make some links more self-descriptive for accessibility reasons. Remove trailing white spaces because my editor removes them automatically (and yours should too). Broken links were found with markdown-link-check with the following command: ``` find . -name tmp -prune -o -name node_modules -prune -o -name \*.md -print0 | xargs -0 -n1 markdown-link-check ``` --- CONTRIBUTING.md | 4 +- docs/api/apiv3/example/README.md | 115 +++++++++--------- .../code-review-guidelines/README.md | 8 +- .../concepts/inline-editing/README.md | 38 +++--- docs/development/concepts/queries/README.md | 11 +- .../concepts/translations/README.md | 4 +- docs/development/running-tests/README.md | 4 +- docs/development/security/README.md | 3 +- docs/faq/README.md | 6 +- .../installation/manual/README.md | 2 +- .../oauth-applications/README.md | 32 ++--- docs/user-guide/team-planner/README.md | 8 +- docs/user-guide/time-and-costs/README.md | 5 +- .../time-and-costs-faq/README.md | 6 +- frontend/README.md | 2 +- frontend/src/locales/README.md | 2 +- 16 files changed, 124 insertions(+), 126 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fb5f00b6bf..7fd0d31d195 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Please get in touch with us using our [develompment forum](https://community.ope We eat our own ice cream so we use OpenProject for roadmap planning and team collaboration. Please have a look at the following pages: -- [Product roadmap](https://community.openproject.org/projects/openproject/work_packages?query_id=1993) +- [Product roadmap](https://community.openproject.org/projects/openproject/roadmap) - [Wish list](https://community.openproject.org/projects/openproject/work_packages?query_id=180) - [Bug backlog board](https://community.openproject.org/projects/openproject/boards/2905) - [Report a bug](https://www.openproject.org/docs/development/report-a-bug/) @@ -110,7 +110,7 @@ which are not labelled as `work in progress` by us. ## Security -If you notice a security issue in OpenProject, please send us a gpg encrypted email to security@openproject.com and describe the issue you found. Download our public gpg key [here](https://pgp.mit.edu/pks/lookup?op=get&search=0x7D669C6D47533958). +If you notice a security issue in OpenProject, please send us a gpg encrypted email to security@openproject.com and describe the issue you found. [Download OpenProject security team public gpg key](https://keys.openpgp.org/vks/v1/by-fingerprint/BDCFE01EDE84EA199AE172CE7D669C6D47533958). Please include a description on how to reproduce the issue if possible. Our security team will get your email and will attempt to reproduce and fix the issue as soon as possible. diff --git a/docs/api/apiv3/example/README.md b/docs/api/apiv3/example/README.md index 628b620656c..b43d7ec87d9 100644 --- a/docs/api/apiv3/example/README.md +++ b/docs/api/apiv3/example/README.md @@ -1,7 +1,7 @@ # API v3 usage example -The following guide is meant to be read as an hands on example on how to use the API v3 as a client. -It should enable a reader to understand how authentication and authorization are handled and demonstrates creating +The following guide is meant to be read as an hands on example on how to use the API v3 as a client. +It should enable a reader to understand how authentication and authorization are handled and demonstrates creating a work package, filtering for it, updating it and finally the deletion of it. The guide itself is technology agnostic. You will find no ready to use code in it. However, given that an api client, in this case @@ -12,15 +12,15 @@ While being limited to the work package resource, the same principles apply thro ## Fetching work packages from community.openproject.com -Because it is readily at hand, we will first fetch a list of work packages from [community.openproject.com](https://community.openproject.com). +Because it is readily at hand, we will first fetch a list of work packages from [community.openproject.com](https://community.openproject.com). In its simplest form, fetching work packages looks like this: ![get work packages from community](./get-work-packages-from-community.png) -The GET request returns a `WorkPackageCollection`, which is a list of `WorkPackage` resources. Although not explicitly required by the HAL standard, every embedded `WorkPackage` is complete, meaning it will contain every property. +The GET request returns a `WorkPackageCollection`, which is a list of `WorkPackage` resources. Although not explicitly required by the HAL standard, every embedded `WorkPackage` is complete, meaning it will contain every property. -Please notice that no headers and no credentials need to be provided. Because the community instance of OpenProject is configured to be available to the public, no authentication is required. +Please notice that no headers and no credentials need to be provided. Because the community instance of OpenProject is configured to be available to the public, no authentication is required. ## Authentication @@ -34,36 +34,36 @@ identical to the one above against a locally run installation: In case such an error is returned, the client has a couple of possibilities to authenticate. Of the available possibilities, the guide will demonstrate two: Basic Auth because it is the most commonly used mechanism and OAuth 2 because it should be the most commonly used mechanism. -Regardless of the authentication mechanism used, a client will always authenticate as a user within OpenProject. Even for the OAuth2 [Client credentials](https://oauth.net/2/grant-types/client-credentials/) flow, which is aimed to not involve user interaction, every interaction on the server will be carried out in the name of a specific user to regulate authorization as you will see below. +Regardless of the authentication mechanism used, a client will always authenticate as a user within OpenProject. Even for the OAuth2 [Client credentials](https://oauth.net/2/grant-types/client-credentials/) flow, which is aimed to not involve user interaction, every interaction on the server will be carried out in the name of a specific user to regulate authorization as you will see below. ### Basic auth -Basic auth is simple to setup and understand but should be avoided in -the long run due to security reasons but also because it limits a client +Basic auth is simple to setup and understand but should be avoided in +the long run due to security reasons but also because it limits a client and is harder to manage within an organization. -In order to authenticate with basic auth, a user first has to login -into OpenProject and generate an API key. After that he can use the -API key to authenticate API calls. The normal login credentials will +In order to authenticate with basic auth, a user first has to login +into OpenProject and generate an API key. After that he can use the +API key to authenticate API calls. The normal login credentials will not work when communicating via the API. -An API key can be generated on the "Access token" page within the -"My account" section by clicking on the "Generate" or "Reset" -(depending on whether a key already exists) link within the "API" row. +An API key can be generated on the "Access token" page within the +"My account" section by clicking on the "Generate" or "Reset" +(depending on whether a key already exists) link within the "API" row. ![get basic auth key](./basic-auth-key-generation.png) -Only one API key can exist for a user at any given time. +Only one API key can exist for a user at any given time. Generating a new key will invalidate the former key, so please -make sure to note down the access token. -This is one of the limitations that do apply to basic auth +make sure to note down the access token. +This is one of the limitations that do apply to basic auth but do not apply to OAuth2. -Postman offers to correctly encode the key and set the -correct `Authorization` header via a form. So using Postman, -having chosen "Basic Auth" as the authorization type, -we can authenticate by pasting in the key into the -"Password" field and by setting 'apikey' for "Username". +Postman offers to correctly encode the key and set the +correct `Authorization` header via a form. So using Postman, +having chosen "Basic Auth" as the authorization type, +we can authenticate by pasting in the key into the +"Password" field and by setting 'apikey' for "Username". When using basic auth, the user's login is never used. T he whole of the information is already encoded in the generated key. @@ -78,15 +78,16 @@ We could just as well have generated the header ourselves by Base64 encoding the ### OAuth2 OAuth2 based authentication requires an administrator to create -and configure an OAuth2 application as described in the -[Administration Guide](../../system-admin-guide/authentication/oauth-applications/). +and configure an OAuth2 application as described in the [OAuth applications +documentation](../../system-admin-guide/authentication/oauth-applications/) of +the Administration Guide. The guide also explains how to use the Postman application to test the OAuth2 flow and to obtain a Bearer token. -Once available, we chose to use the resulting Bearer token +Once available, we chose to use the resulting Bearer token in Postman by pressing "Use token". -![OAuth2 postman token](./oauth2-postman-token.png) +![OAuth2 postman token](./oauth2-postman-token.png) Please note that an OAuth token expires after two hours, so the client has to request a new token then. This will require @@ -95,26 +96,26 @@ of the Postman "Authorization" tab. ### Being authenticated -With the Bearer token included in the request, +With the Bearer token included in the request, we should see a list of work packages in the work packages page above. -However, the collection of work packages returned might still -not contain the work packages the client is looking for. -This might be, because the user in whose name the client -accesses the application is lacking permissions. +However, the collection of work packages returned might still +not contain the work packages the client is looking for. +This might be, because the user in whose name the client +accesses the application is lacking permissions. We need to ensure that the user also has the necessary authorization. ## Authorization -As the client accesses OpenProject on behalf of a user, -that user needs to have authorization to do the desired actions. -So while the user might be authenticated, she/he might lack the +As the client accesses OpenProject on behalf of a user, +that user needs to have authorization to do the desired actions. +So while the user might be authenticated, she/he might lack the necessary permissions to do anything. -In OpenProject, permissions are mostly granted by creating a -connection between a user, a project and a role in the form of a -membership. So for every project the user is supposed to have access, +In OpenProject, permissions are mostly granted by creating a +connection between a user, a project and a role in the form of a +membership. So for every project the user is supposed to have access, a role needs to be assigned to him/her. This can be done in the members administration of each project: @@ -125,10 +126,10 @@ In case permissions are lacking, the client returns a 403 response so if you enc ## Creating a work package -When having the necessary authentication and authorization, a client may create work packages via the API. +When having the necessary authentication and authorization, a client may create work packages via the API. To aid in creating work packages, a work package form resource is available - + ### Fetching the work package form A form: @@ -145,12 +146,12 @@ We will first fetch the empty form: We have send a POST request to the application with an empty body that is configured to be json. This will set a `ContentType: application/json` header which is a necessary header whenever sending a state altering request, so whenever a POST, PATCH or DELETE request is send. -The response informs us within the `validationErrors` section (nested in `_embedded`) of the errors the send body has which is of no surprise, given that it was empty. The currently stated errors are that `type`, `project` and `subject` are empty. +The response informs us within the `validationErrors` section (nested in `_embedded`) of the errors the send body has which is of no surprise, given that it was empty. The currently stated errors are that `type`, `project` and `subject` are empty. Given that `subject` just takes any string (up to a length of 255 characters), fixing this is rather easy. We know that, because the `Schema` embedded in the `Form` tells the client: ![work package empty create schema subject](./wp-create-form-schema-subject.png) - -The schema also describes the `type` and `project` properties which other than the `subject` cannot be freely chosen. Both properties need to reference already existing resources. + +The schema also describes the `type` and `project` properties which other than the `subject` cannot be freely chosen. Both properties need to reference already existing resources. For `type`, the available values are directly listed in the schema: @@ -186,15 +187,15 @@ All referencing properties (`project` and `type` in our case) are noted in the ` The value taken for the `href` in a resource value will always be the `self` link of a resource: ![work package create schema self link](./wp-create-form-schema-self-link.png) - + *The correct combination of `project` and `type` needs to be chosen. Sometimes a type is not available in every project. In that case, the type either can be added to the project in the UI or a different type needs to be chosen for the project. This is why it sometimes makes sense to first send the project within the form body as this will update the `availableValues` listed for `type`* -### Sending the create request +### Sending the create request Once no more validation errors are displayed, we can create the work package. For this, a client either uses the payload embedded in the form, or use the body of the create form request. -If no validation errors exist, a `commit` link will be provided in the form following the idea of HATEOAS to allow a client to navigate through the API via links provided in the response. +If no validation errors exist, a `commit` link will be provided in the form following the idea of HATEOAS to allow a client to navigate through the API via links provided in the response. We send a POST request to the url listed for the `commit` link and with the `payload` as the request body. Again, we have the `ContentType: application/json` header set. ![work package created](./wp-create.png) @@ -203,13 +204,13 @@ Now the client has created a work package with the properties provided. The serv ### Custom fields -Especially setting values for custom field properties is helped by the work package form prior to creation. -As the existence of custom field and their available values by their very nature is different between OpenProject instances, a client that is build to be of used in combination with multiple OpenProject instances cannot have a hard coded set of custom fields and custom field values. +Especially setting values for custom field properties is helped by the work package form prior to creation. +As the existence of custom field and their available values by their very nature is different between OpenProject instances, a client that is build to be of used in combination with multiple OpenProject instances cannot have a hard coded set of custom fields and custom field values. Additionally, the availability of custom fields depend on the `project` and the `type` of a work package resource. Custom fields can be configured to be available only for certain types and certain projects. The schema will list all available custom fields in the schema, with their name being provided in a human readable form and the available values being listed same as for `project`: -![work package empty create schema list custom field](./wp-create-form-schema-list-cf.png) +![work package empty create schema list custom field](./wp-create-form-schema-list-cf.png) The set of available custom fields might change depending on the values provided for `project` and `type`. I.e. if no `project` and no `type` is provided at first, custom fields will not be listed in the schema. @@ -237,7 +238,7 @@ Adapting the request we issued against the community installation, the client ca ![get all work packages](./get-work-packages-all.png) -This however will return all work packages the authenticated user employing the client is eligible to see, which might potentially be thousands of work packages. +This however will return all work packages the authenticated user employing the client is eligible to see, which might potentially be thousands of work packages. The server will always limit the amount of work packages actually returned (and will indicate the total amount by the `total` attribute that is part of the `WorkPackageColletion` resource) but using this method to find an individual work packages is laborious. That is why the [API supports filters](../filters). The filter that is applied most easily, is the filter for the project. This filter can be applied requesting via a project scoped url: @@ -250,24 +251,24 @@ There are however a whole lot of additional filters for work packages, e.g. the ![get work packages filter subject](./get-work-packages-filter-subject.png) -By providing the filters as a query parameter (`[{"subject": { "operator": "~", "values": ["A new work package"] }}]`) the client only receives work packages whose subject contains the provided string. +By providing the filters as a query parameter (`[{"subject": { "operator": "~", "values": ["A new work package"] }}]`) the client only receives work packages whose subject contains the provided string. The client can just as well filter for most of the other properties and can combine those filters: ![get work packages filter type and priority](./get-work-packages-filter-type-and-priority.png) -In the example displayed above, the clients filters for all work packages having their type set to the types with the id 2, 3 or 4 **and** whose priority is not the priority with the id 4. +In the example displayed above, the clients filters for all work packages having their type set to the types with the id 2, 3 or 4 **and** whose priority is not the priority with the id 4. Once again, this displays a difference between scalar and resource values. While the client is expected to provide scalar value directly, in needs to provide the id of resources in case a resource property is filtered for. -Apart from filtering, the work packages can also be sorted (e.g. `sortyBy=[["assignee", "asc"], ["createdAt", "desc"]`), +Apart from filtering, the work packages can also be sorted (e.g. `sortyBy=[["assignee", "asc"], ["createdAt", "desc"]`), the number of work packages included in the response can be adapted (e.g. `pageSize=50`) and the page offset the result set -starts from can be specified (e.g. `offset=5`). +starts from can be specified (e.g. `offset=5`). In total, this provides a lot of capabilities for retrieving the set of work packages the client needs. Because those capabilities might be overwhelming at first, it is a good idea to use the OpenProject UI to configure the filters, order, etc. desired and -take a look at the request the UI sends to the backend (e.g. via the developer tool's network tab). As the OpenProject UI is an API client as well, +take a look at the request the UI sends to the backend (e.g. via the developer tool's network tab). As the OpenProject UI is an API client as well, it can guide other potential clients to correctly communicate with the OpenProject backend. -## Updating a work package +## Updating a work package Once the correct work package is found, it can also be updated. Once again, a form can support the client in that endeavour. @@ -289,7 +290,7 @@ actual change is performed: ![work package filled update form](./wp-update-form-filled.png) -Please keep in mind that available values and even the applicability of properties might change if the `type` or the `project` property is altered. Custom fields might not be configured for the chose combination, +Please keep in mind that available values and even the applicability of properties might change if the `type` or the `project` property is altered. Custom fields might not be configured for the chose combination, and the switch to a new project might lead to different users being available to become assignees. Switching projects might also lead to additional or less properties being available depending on the modules activated in the projects (e.g. the `budget` property depends on the budgets module being active). The permissions the requesting user has can also influence what properties can be altered. The `version` property is e.g. only writable for users with the "Assign versions" permission. @@ -300,7 +301,7 @@ Once all the desired changes are prepared and no validation errors are displayed ![work package update](./wp-update.png) -In this case, a PATCH request is issued. This means that the client does not have to provide all of the properties. Only the provided properties are altered +In this case, a PATCH request is issued. This means that the client does not have to provide all of the properties. Only the provided properties are altered (with them being sent to null in case that is specified, e.g. `"dueDate": null`). After the update is done, the `lockVersion` will increase by one so a subsequent request will have to take this into account. ## Deleting a work package diff --git a/docs/development/code-review-guidelines/README.md b/docs/development/code-review-guidelines/README.md index 44dea60a287..54c83b64f27 100644 --- a/docs/development/code-review-guidelines/README.md +++ b/docs/development/code-review-guidelines/README.md @@ -18,7 +18,7 @@ The same is true for eslint. Your editor will likely have support for eslint che **Lefthook** -For automatically linting your files on commiting them, please have a look at lefthook. https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md. You can install these rules by using `bundle exec lefthook install`. +For automatically linting your files on commiting them, please have a look at [Lefthook](https://github.com/evilmartians/lefthook). You can install these rules by using `bundle exec lefthook install`. @@ -145,7 +145,7 @@ When testing a feature or change, check out the code test at least the happy pat - Adding Gems: When adding gems, make sure not only the Gemfile is updated, but also the Gemfile.lock. - + ## Readability @@ -166,9 +166,9 @@ Once you've completed the review, you might have left feedback for the developer -If there are no change requests, perform these steps: +If there are no change requests, perform these steps: -- approve the pull request +- approve the pull request - merge it using the `Merge pull request` button. - If there is a linked ticket, set it to merged (or closed for an Implementation ticket) and unassign yourself or the developer. diff --git a/docs/development/concepts/inline-editing/README.md b/docs/development/concepts/inline-editing/README.md index e675e0e444b..88e775ec286 100644 --- a/docs/development/concepts/inline-editing/README.md +++ b/docs/development/concepts/inline-editing/README.md @@ -7,7 +7,7 @@ keywords: development concepts, inline editing, edit forms # Development concept: Inline editing -Inline editing is a core functionality of work packages and other attributes. +Inline editing is a core functionality of work packages and other attributes. ![Inline editing overview in the single view of a work package](single-view-inline-editing.gif) @@ -33,7 +33,7 @@ In order to understand Inline Editing, you will need the following concepts: - [Changesets](../resource-changesets) - + ## Components overview @@ -81,7 +81,7 @@ With a resource and its schema present, there are multiple ways to render a disp #### Rendering in plain JavaScript: `DisplayFieldRenderer` -Since parts of the application are rendered in plain JavaScript (such as the work package table), most display fields are actually rendered explicitly to a DOM element through the [`DisplayFieldRenderer#render`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/fields/display/display-field-renderer.ts) method. You will only need the resource with its schema loaded and the attribute name. +Since parts of the application are rendered in plain JavaScript (such as the work package table), most display fields are actually rendered explicitly to a DOM element through the [`DisplayFieldRenderer#render`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/fields/display/display-field-renderer.ts) method. You will only need the resource with its schema loaded and the attribute name. The `DisplayFieldRenderer` requires the Angular injector for injecting services such as the `DisplayFieldService`. It's instance will cache field types instantiated for performance reasons in large rendering contexts, such as the work package table. @@ -92,22 +92,22 @@ Minimal example, rendering the status attribute of a work package to the element export class ExampleComponent implements OnInit { // The work package to render @Input() workPackageId:string; - + constructor (private elementRef:ElementRef, private injector:Injector, private apiV3Service:ApiV3Service) {} - + ngOnInit() { this - .apiV3Service + .apiV3Service .work_packages .id(this.workPackageId) .get() .subscribe(workPackage => { - + const fieldRenderer = new DisplayFieldRenderer(injector, 'table'); const displayElement = fieldRenderer.render(workPackage, 'status', null); - this.elementRef.nativeElement.appendChild(displayElement); + this.elementRef.nativeElement.appendChild(displayElement); }); } } @@ -149,7 +149,7 @@ The `DisplayFieldComponent` will internally use the `DisplayFieldService` to fin The editable counterpart to a display field that renders the actual HTML form elements (A text or number input field, a boolean checkbox, or a WYSIWYG editor area). -Edit fields are also working on a single attribute of a resource. The schema property `Type` will again determine the component type to render. +Edit fields are also working on a single attribute of a resource. The schema property `Type` will again determine the component type to render. @@ -163,7 +163,7 @@ It is never directly used from within a template, but through a service that pas ### EditForm -Inline-editing is usually connected to not only a single, but multiple fields of a resource. Each inline-editable field resides within a container that we call an `EditForm`. +Inline-editing is usually connected to not only a single, but multiple fields of a resource. Each inline-editable field resides within a container that we call an `EditForm`. The `EditForm` logically groups together multiple field elements very similar to how a `
` tag encapsulates a set of inputs. It is tied to a (HAL) `resource` input. @@ -192,7 +192,7 @@ The EditableAttributeField basically contains only two HTML elements that it wra OpenProject often renders Angular components in manually rendered DOM, prominently so in the work package table for improved rendering time. This is from the time the project was still using AngularJS and large scale rendering components was quite slow. -To easily mount an edit field over a manually rendered `DisplayField` (such as from the `DisplayFieldRenderer` above), OpenProject uses the `EditingPortalService` to create an [Angular CDK portal](https://material.angular.io/cdk/portal/overview). +To easily mount an edit field over a manually rendered `DisplayField` (such as from the `DisplayFieldRenderer` above), OpenProject uses the `EditingPortalService` to create an [Angular CDK portal](https://material.angular.io/cdk/portal/overview). The `EditingPortalService` will render a `EditFormPortalComponent` with some HTML form wrapping for correct handling of submit events and labels. This portal will in turn render the actual `EditFieldComponent`. The service will wire up these components automatically. @@ -203,22 +203,22 @@ If you were to explicitly render an edit field, this would look as follows. Note export class ExampleComponent implements OnInit { // The work package to render @Input() workPackageId:string; - + constructor (private elementRef:ElementRef, private injector:Injector, // Parent EditForm required private editForm:EditFormComponent, private apiV3Service:ApiV3Service) {} - + ngOnInit() { this - .apiV3Service + .apiV3Service .work_packages .id(this.workPackageId) .get() .subscribe(workPackage => { - - + + return this.editingPortalService.create( this.elementRef.nativeElement, this.injector, @@ -238,7 +238,7 @@ export class ExampleComponent implements OnInit { There is one more class involved in this stack, the `EditFieldHandler`. It implements an adapter pattern to break the connection between the input-only characteristics of the `EditFieldComponent` and the handling of events towards an outer wrapper such as the `EditForm`. They are regular classes that handle events from the `EditFieldComponent` to make them reusable in cases where, for example, an `EditForm` does not exist. -Any user event that should trigger saving or resetting of the field is being handled by the `EditFieldHandler`, hence its name. For example, pressing ESC on a `TextEditFieldComponent` will trigger the `EditFieldHandler#handleUserCancel` method. The same is true for submit events on the field or form (e.g., pressing ENTER on the field), which trigger the `EditFieldHandler#handleUserSubmit` method. +Any user event that should trigger saving or resetting of the field is being handled by the `EditFieldHandler`, hence its name. For example, pressing ESC on a `TextEditFieldComponent` will trigger the `EditFieldHandler#handleUserCancel` method. The same is true for submit events on the field or form (e.g., pressing ENTER on the field), which trigger the `EditFieldHandler#handleUserSubmit` method. An example where this comes into play is the [`CustomText`](https://github.com/opf/openproject/tree/dev/frontend/src/app/shared/components/grids/widgets/custom-text/custom-text.component.ts) widget of the dashboards and my page, which use the `` manually and pass in a handler that handles saving of these widgets without access to an edit form. @@ -247,14 +247,14 @@ An example where this comes into play is the [`CustomText`](https://github.com/o ### 🔗 Code references - [`EditForm`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.ts) base class -- [`EditFormComponent`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.component.ts#L28-L27) Angular `` component +- [`EditFormComponent`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.component.ts#L28-L27) Angular `` component - [`EditableAttributeFieldComponent`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/fields/edit/field/editable-attribute-field.component.ts) Angular `` component for attributes within the edit form - [`DisplayField`](https://github.com/opf/openproject/tree/dev/frontend/src/app/shared/components/fields/display) definitions containing all display fields and the service to instantiate them. - [`DisplayFieldRenderer`](https://github.com/opf/openproject/tree/dev/frontend/src/app/shared/components/fields/display/display-field-renderer.ts) to manually render display fields from JavaScript - [`DisplayFieldComponent`](https://github.com/opf/openproject/tree/dev/frontend/src/app/shared/components/fields/display/display-field.component.ts) an Angular component to render display fields - [`EditFieldComponent`](https://github.com/opf/openproject/tree/dev/frontend/src/app/shared/components/fields/edit/field-types) definitions containing all display fields and the service to instantiate them - [`EditingPortalService`](https://github.com/opf/openproject/tree/dev/frontend/src/app/shared/components/fields/edit/editing-portal/editing-portal-service.ts) service to create an edit field with event handling in code -- [`WorkPackageFullViewComponent`](https://github.com/opf/openproject/blob/dev/frontend/src/app/features/work_packages/routing/wp-full-view/wp-full-view.html) Work package full view template that uses the `edit-form` attribute to create a form for the work package full view (as seen in the Gif above) +- [`WorkPackageFullViewComponent`](https://github.com/opf/openproject/blob/dev/frontend/src/app/features/work-packages/routing/wp-full-view/wp-full-view.html) Work package full view template that uses the `edit-form` attribute to create a form for the work package full view (as seen in the Gif above) - [`ProjectDetailsComponent`](https://github.com/opf/openproject/blob/dev/frontend/src/app/shared/components/grids/widgets/project-details/project-details.component.html) Exemplary widget template that uses the form for project attributes diff --git a/docs/development/concepts/queries/README.md b/docs/development/concepts/queries/README.md index 1479865b74a..a1c775250a9 100644 --- a/docs/development/concepts/queries/README.md +++ b/docs/development/concepts/queries/README.md @@ -9,7 +9,7 @@ keywords: queries, query space, work package views # Query -The Query object is the concept of holding the configuration for a specific work package view as well as computing and outputting its results. They are fundamental building blocks in OpenProject and used in many modules (Work packages, BIM, boards, timeline, embedded tables). Their flexibility allows for building complex features with relatively little effort on the backend side. For an OpenProject developer who wants to improve or create new features it is fundamental to understand how queries work. +The Query object is the concept of holding the configuration for a specific work package view as well as computing and outputting its results. They are fundamental building blocks in OpenProject and used in many modules (Work packages, BIM, boards, timeline, embedded tables). Their flexibility allows for building complex features with relatively little effort on the backend side. For an OpenProject developer who wants to improve or create new features it is fundamental to understand how queries work. Most of the communication in OpenProject is organized in work packages. Work packages are managed and displayed in many different places, such as the work package table in the *Work packages* module, or as cards the *Boards* module, within the *Calendar* or in *My page* widgets, such as charts for instance. Even the list of child work packages within a work package is a query. @@ -63,7 +63,7 @@ Queries are regular APIv3 grape endpoints that can be accessed through the `/api The default query `/api/v3/queries/default` and `/api/v3/:project_id/queries/default` contains a default set of configuration (back-end and front-end) global and for the given project, respectively. They can only be modified administrators through some global settings. -A number of parameters can be passed to the Query through parameters as elaborated on in [the respective APIv3 documentation](../../../api/endpoints/queries/). +A number of parameters can be passed to the Query through parameters as elaborated on in [the APIv3 Queries documentation](../../../api/endpoints/queries/). Clients can define a query once, save it and use it later on to load the same set of filters, columns, and so on. When retrieved from the database (a query id is passed), the query has been previously stored. Saved properties may be overridden through URL parameters, which override the existing saved query. @@ -71,7 +71,7 @@ Clients can define a query once, save it and use it later on to load the same se ### Query collections responses -Since queries can be saved and should be listed to the user such as in the work package sidebar, they can also be requested as a collection of resources through `/api/v3/queries`. This endpoint can also be filtered. For more details on that, see the [respective APIv3 section](../../../api/endpoints/queries/). +Since queries can be saved and should be listed to the user such as in the work package sidebar, they can also be requested as a collection of resources through `/api/v3/queries`. This endpoint can also be filtered. For more details on that, see the [respective APIv3 Queries filtering section](../../../api/endpoints/queries/#filtering). This response will end up representing the available queries on the `work packages` module sidebar as shown below. @@ -94,7 +94,7 @@ When accessing a singular query resource, the response will always contain the s - `filters` selected filters array - `columns` embedded array of selected `columns` - `sortBy` array of one or multiple sort criteria. - - `groupBy` (optional) information on whether results are aggregated into groups + - `groupBy` (optional) information on whether results are aggregated into groups - **Results** of work packages embedded in `_embedded.results`, showing the total number of matched results, as well as including the requested _page_ of results. The results will contain work package resources and the schema objects necessary to render them to reduce requests. ### Filtering @@ -105,7 +105,7 @@ A major, but complex functionality of the query is the `filters` object to deter -These filters are also saved within the queries. If you would like to read more about how filters and their syntax work, you will find [their own documentation guide here](../../../api/filters/). +These filters are also saved within the queries. Read the [APIv3 filters documentation guide](../../../api/filters/) to know more about how filters and their syntax work. @@ -178,4 +178,3 @@ The `WorkPackagesListService` can also update and save existing queries passed t `PartitionedQuerySpaceComponent` instances will be instantiated by the router and listen to URL params to load the corresponding query object. The most prominent example of such a page is the work packages module such as [community.openproject.com/work_packages](https://community.openproject.com/work_packages). The partitioning comes from showing a work package table (or cards view) on one side, and a details view of a single work package on another side, splitting the page in two. The width of the split areas can be customized by the user through a drag-handle. - diff --git a/docs/development/concepts/translations/README.md b/docs/development/concepts/translations/README.md index 8edd4c4cef7..f32ca3e7a1c 100644 --- a/docs/development/concepts/translations/README.md +++ b/docs/development/concepts/translations/README.md @@ -28,9 +28,9 @@ Not all translations are fully translated, and they will fallback to english str The OpenProject localizable strings are stored in the [Rails-standard I18n YAML files](https://guides.rubyonrails.org/i18n.html). The core only manages english source strings in `en.yml` and `js-en.yml` files. The can be found at `config/locales/en.yml` and `config/locales/js-en.yml`, respectively. -Additionally, modules can define their own translations, such as `modules/budgets/config/locales/en.yml`. +Additionally, modules can define their own translations, such as `modules/budgets/config/locales/en.yml`. -The `js-en.yml` are not special on their own, but are simply prefixed with the `js:` key at the beginning of the file. This means all translations within are prefixed with the `js.` key. This is picked up by [`I18n.js`](https://github.com/fnando/i18n-js), a Ruby gem and frontend library that helps outputting javascript objects for the frontend. Only strings that are prefixed with `js.` and some internals will end up in the frontend due to the config we applied in [`config/i18n-js.yml`](https://github.com/opf/openproject/blob/dev/config/i18n-js.yml). The translations are output by the take task `./bin/rails assets:export_locales` and are output to `frontend/src/locales/{language}.js`. +The `js-en.yml` are not special on their own, but are simply prefixed with the `js:` key at the beginning of the file. This means all translations within are prefixed with the `js.` key. This is picked up by [`I18n.js`](https://github.com/fnando/i18n-js), a Ruby gem and frontend library that helps outputting javascript objects for the frontend. Only strings that are prefixed with `js.` and some internals will end up in the frontend due to the config we applied in [`config/i18n.yml`](https://github.com/opf/openproject/blob/dev/config/i18n.yml). The translations are output by the rake task `./bin/rails assets:export_locales` to `frontend/src/locales/{language}.js`. diff --git a/docs/development/running-tests/README.md b/docs/development/running-tests/README.md index 7ad6a1de914..2779e4ca034 100644 --- a/docs/development/running-tests/README.md +++ b/docs/development/running-tests/README.md @@ -448,7 +448,7 @@ Possible reasons are: * Parts of the OpenProject code are using memoization and caching for performance, and some tests can do weird things like prepending a module or other meta programming. Without proper clean up of the global state, subsequent tests may fail. It can go unnoticed depending on the test execution order. * RSpec tests order is different on each run. The order is defined by the random seed which can be set with `--seed` option. When running rspec, the random seed is displayed like this: `Randomized with seed 18352`. * Try running tests locally with the same random seed as the one used on CI. - * Once you determined that the failure is order dependant, use [`--bisect`](https://relishapp.com/rspec/rspec-core/docs/command-line/bisect) to isolate the minimal set of examples that reproduce the same failures. + * Once you determined that the failure is order dependant, use [`--bisect`](https://rspec.info/features/3-12/rspec-core/command-line/bisect/) to isolate the minimal set of examples that reproduce the same failures. * Faster / slower machine and race conditions * Some system tests using browser and performing ajax requests may not be synchronized with the test execution: the test is testing something that has not happened yet. Sometimes the ajax runs at the right time and the test passes, sometimes it runs at the wrong time and the test fails. * Use `script/bulk_run_rspec` to run the same test multiple times. If it has both failing and passing results, it means it is a flickering test. @@ -758,7 +758,7 @@ alias wrspec='watchexec --exts rb,erb -- bin/rspec' wrspec spec/some/path/to/a_particular_spec.rb ``` -To easily change the RSpec examples being run without relaunching `watchexec` every time, you can focus a particular example or example group with `focus: true`, `fit`, `fdescribe`, and `fcontext`. More details available on [RSpec documentation](https://relishapp.com/rspec/rspec-core/docs/filtering/filter-run-when-matching). +To easily change the RSpec examples being run without relaunching `watchexec` every time, you can focus a particular example or example group with `focus: true`, `fit`, `fdescribe`, and `fcontext`. More details available on [RSpec documentation](https://rspec.info/features/3-12/rspec-core/filtering/filter-run-when-matching/). ## Manual acceptance tests diff --git a/docs/development/security/README.md b/docs/development/security/README.md index 7a95accb29f..27ec1066c6e 100644 --- a/docs/development/security/README.md +++ b/docs/development/security/README.md @@ -22,7 +22,7 @@ We take all facets of security seriously at OpenProject. If you want to report a If you can, please send us a PGP-encrypted email using the following key: -- Key ID: [0x7D669C6D47533958](https://pgp.mit.edu/pks/lookup?op=get&search=0x7D669C6D47533958) , +- Key ID: [0x7D669C6D47533958](https://keys.openpgp.org/vks/v1/by-fingerprint/BDCFE01EDE84EA199AE172CE7D669C6D47533958), - Fingerprint BDCF E01E DE84 EA19 9AE1 72CE 7D66 9C6D 4753 3958 - You may also find the key [attached in our OpenProject repository.](https://github.com/opf/openproject/blob/dev/docs/development/security/security-at-openproject.com.asc) @@ -65,4 +65,3 @@ With the single sign-on feature you can securely access OpenProject. Control and Find out more about our [GDPR compliance](../../enterprise-guide/enterprise-cloud-guide/gdpr-compliance/). - diff --git a/docs/faq/README.md b/docs/faq/README.md index 586a74d1659..68c98af852d 100644 --- a/docs/faq/README.md +++ b/docs/faq/README.md @@ -49,7 +49,7 @@ Here are resources to get to know OpenProject: - Our [English demo video](https://www.youtube.com/watch?v=un6zCm8_FT4) or [German demo video](https://www.youtube.com/watch?v=doVtVArSSvk) to get an overview of Openproject. There are additional videos explaining certain features to be found on our [YouTube channel](https://www.youtube.com/c/OpenProjectCommunity/videos), too. - The [Getting started guide](../getting-started) and the [User guide](../user-guide) - Our free trial: Click the green button [here](https://www.openproject.org/enterprise-edition) for Enterprise on-premises or go to [start.openproject.com](https://start.openproject.com) for the Enterprise cloud. -- Our [development roadmap](https://community.openproject.com/projects/openproject/work_packages?query_id=1993) (to get to know future features) +- Our [development roadmap](https://community.openproject.com/projects/openproject/roadmap) (to get to know future features) - Our [training and consulting offers](https://www.openproject.org/training-and-consulting) ### Can I run OpenProject as a single user? @@ -181,7 +181,7 @@ Apart from that, you can use [placeholder users](../system-admin-guide/users-per ### Can I get a notification when a deadline approaches? -Yes, you can. Starting with OpenProject 12.4 we implemented date alerts and email reminders about upcoming deadlines and overdue tasks. Please refer to [this guide](../user-guide/notifications/) for more details. +Yes, you can. Starting with OpenProject 12.4 we implemented date alerts and email reminders about upcoming deadlines and overdue tasks. Please refer to [this guide](../user-guide/notifications/) for more details. ### Does OpenProject offer resource management? @@ -191,7 +191,7 @@ More infomation regarding resource management in OpenProject can be found in the ### Does OpenProject offer portfolio management? -For portfolio management or custom reporting, you can use either the project list, or the global work package table. Both views can be used to create optimal reports via filtering, sorting and other configuration options. +For portfolio management or custom reporting, you can use either the project list, or the global work package table. Both views can be used to create optimal reports via filtering, sorting and other configuration options. For more information on portfolio management options in OpenProject please refer to this [Use Case](../use-cases/portfolio-management). diff --git a/docs/installation-and-operations/installation/manual/README.md b/docs/installation-and-operations/installation/manual/README.md index 4d5ad03c8e8..55b6e1d786b 100644 --- a/docs/installation-and-operations/installation/manual/README.md +++ b/docs/installation-and-operations/installation/manual/README.md @@ -410,7 +410,7 @@ Restart the OpenProject server afterwards: The next web-request to the server will take longer (as the application is restarted). All subsequent request should be as fast as always. -We encourage you to extend OpenProject yourself by writing a plug-in. Please, read the [plugin-contributions](https://community.openproject.org/projects/openproject/wiki/Developing_Plugins) guide for more information. +We encourage you to extend OpenProject yourself by writing a plug-in. Please, read the [plugin creation guide](https://www.openproject.org/docs/development/create-openproject-plugin/) for more information. ## Troubleshooting diff --git a/docs/system-admin-guide/authentication/oauth-applications/README.md b/docs/system-admin-guide/authentication/oauth-applications/README.md index 54e0a3ea873..4116cbbe2b0 100644 --- a/docs/system-admin-guide/authentication/oauth-applications/README.md +++ b/docs/system-admin-guide/authentication/oauth-applications/README.md @@ -21,28 +21,28 @@ You can configure the following options to add your OAuth application: 1. Enter the **Name** of your OAuth application. 2. Define a **Redirect URL** where users are redirected to after having authorized OAuth2 access in OpenProject. - This URL is provided by your client application. + This URL is provided by your client application. In the case of locally running Postman you can use the special string: `urn:ietf:wg:oauth:2.0:oob` Postman 9.6 and higher also provides a Web URL that you can use: `https://oauth.pstmn.io/v1/callback`. -3. Set the **Scopes** that the OAuth clien application will be - able to access. Multiple selection is possible. +3. Set the **Scopes** that the OAuth clien application will be + able to access. Multiple selection is possible. If no scope is checked, per default **api_v3** is assumed. api_v3 is the standard OpenProject API, while - [bcf_v2_1](../../../api/bcf-rest-api/) is specific to the BIM edition of + [bcf_v2_1](../../../api/bcf-rest-api) is specific to the BIM edition of OpenProject. 4. Check if the application will be **Confidential**. This is typically the case for client applications running on a server or desktop but requires the client application to ensure it. -5. (Optional) Choose **Client Credentials User** and define a +5. (Optional) Choose **Client Credentials User** and define a user on whose behalf requests will be performed. 6. Press **Create** to add your OAuth application. ![add-new-oauth-application](add-new-oauth-application.png) -Don't forget to note down your `Client ID` and your `Client secret` +Don't forget to note down your `Client ID` and your `Client secret` in a safe space. You will need them later. ## OAuth endpoints @@ -65,20 +65,20 @@ Request an authorization code. Please adopt the following URL replacing: `https://example.com/oauth/authorize?response_type=code&client_id=&redirect_uri=&scope=api_v3&prompt=consent` -This requests redirects you to a URL that holds a `code` parameter +This requests redirects you to a URL that holds a `code` parameter which is the authentication code. -Typically the server that was requested with that redirect will save -the authorization code and use it to obtain API token, with which -the server can then act on behalf of the current user. +Typically the server that was requested with that redirect will save +the authorization code and use it to obtain API token, with which +the server can then act on behalf of the current user. -In this example we skip that server side implementation and just -copy the value of the `code` parameter from the URL that you see +In this example we skip that server side implementation and just +copy the value of the `code` parameter from the URL that you see in your browser. ### Request API token -With the authorization code that you obtained above you can now +With the authorization code that you obtained above you can now request an API token. We do this manually in the command line using cURL. Please replace: @@ -113,7 +113,7 @@ The response will look like this: } ``` -The response contains the bearer token ("access_token") and a refresh token that you will need when working with the API. +The response contains the bearer token ("access_token") and a refresh token that you will need when working with the API. Please copy the tokens for reference. @@ -122,7 +122,7 @@ Please copy the tokens for reference. With the token that you obtained above you can now make API calls to the OpenProject instance on behalf of the current user. For example, the following cURL command fetches all projects from the API V3. Please replace: - + * `example.com` with the IP/host name of your OpenProject instance, and * `` with the bearer token you obtained above. @@ -133,7 +133,7 @@ $ curl --request GET 'https://example.com/api/v3/projects' \ ## Using Postman with OAuth2 -You can exercise the authentication flow above using Postman. +You can exercise the authentication flow above using Postman. Just create a new request: ``` diff --git a/docs/user-guide/team-planner/README.md b/docs/user-guide/team-planner/README.md index e4e4564b96b..90fb7380641 100644 --- a/docs/user-guide/team-planner/README.md +++ b/docs/user-guide/team-planner/README.md @@ -15,7 +15,7 @@ Essentially, the team planner is a calendar view with an assignee column on the > **Note**: Team planner is an Enterprise add-on and can only be used with [Enterprise cloud](../../enterprise-guide/enterprise-cloud-guide/) or [Enterprise on-premises](../../enterprise-guide/enterprise-on-premises-guide/). An upgrade from the free community edition is easy and helps support OpenProject. -To use this module, you must have the work packages module enabled. +To use this module, you must have the work packages module enabled. | Topic | Content | |------------------------------------------------------------------------------|:---------------------------------------------------------------------| @@ -52,7 +52,7 @@ A team planner has a number of features numbered 1 to 8 in the above screenshot: 2. Use the **+ Add existing** button to add an existing work package to the team planner. You do this by searching for work package and dragging its card to an assignee, at a certain time. This will then update the *assignee*, *start date* and *finish date* attributes of that work package. 3. Add a new team member to the assignee column by Clicking on the **Add assignee** button. 4. By default, the team planner will only show assigned work packages belonging to the current project. However, it is possible to also add assigned work packages belonging to other projects. You can make these work packages from other projects visible by using **Include projects** feature and selecting additional projects to be included in this view. -5. Use the **Filter** feature (same as in the [work packages](#) module) to display only work packages that meet certain filter criteria. You could, for example, filter such that only work packages of certain types, certain status or certain custom field values are visible. +5. Use the **Filter** feature (same as in the [work packages](../work-packages/work-package-table-configuration/#filter-work-packages) module) to display only work packages that meet certain filter criteria. You could, for example, filter such that only work packages of certain types, certain status or certain custom field values are visible. 6. The **Fullscreen** button lets you view the team planner in fullscreen mode. 7. The **[⋮]** (more) button gives you additional options, such as saving, renaming and saving a copy (saving as), or deleting the team planner. This is also where you can modify the visibility options. 8. By default the team planner only shows the [work week](../../system-admin-guide/calendars-and-dates/#working-days) (week excluding the weekend and non-working days). Use the drop down to toggle between work week, 1-week and 2-week views. With the arrows you can navigate the weeks back and forth. The Today button brings you to the current week. @@ -65,7 +65,7 @@ A team planner has a number of features numbered 1 to 8 in the above screenshot: When you create a new team planner, it will be empty, like so: -![An example of a newly-created empty team planner](TeamPlanner-12.4-emptyNew.png) +![An example of a newly-created empty team planner](TeamPlanner-12.4-emptyNew.png) The first step in setting up your team planning calendar is to add team members. To do so, click on the **+ Add assignee** button then search for the team member you would like to add from the the drop-down list (1). This will add a new row to the calendar view for that team member. @@ -100,7 +100,7 @@ If you would like to take an existing work package and assign it to your team me Start by pressing the **+ Add existing** button below the name of the team planner and search for the work package you would like to add. Once you find it, drag and drop the work package card to the calendar, depending to whom you would like to assign it, and to which start date. The finish date will automatically be derived based on the duration of the work package. -> **Info:** Scheduling tasks by adding existing work packages is easier in the one or two week view, especially if you have work packages that can span a weekend. +> **Info:** Scheduling tasks by adding existing work packages is easier in the one or two week view, especially if you have work packages that can span a weekend. ![Searching for existing work packages to add to the team planner](TeamPlanner-12.4-addExisting.png) diff --git a/docs/user-guide/time-and-costs/README.md b/docs/user-guide/time-and-costs/README.md index 842e55737f6..21d5923a700 100644 --- a/docs/user-guide/time-and-costs/README.md +++ b/docs/user-guide/time-and-costs/README.md @@ -10,7 +10,7 @@ keywords: Time tracking, cost reporting Time and costs functionality in OpenProject allows keeping track of the resources, both in terms of labor and budgets. With OpenProject you can always keep control of the time and costs planned for and spent on the projects. -Create budgets, log time and costs on specific work packages and create time and cost reports based on your needs. +Create budgets, log time and costs on specific work packages and create time and cost reports based on your needs. | Topic | Content | |--------------------------------------|:--------------------------------------------| @@ -27,5 +27,4 @@ Watch this short video to get a first overview about time and cost reporting in ### Does OpenProject provide resource management? -Please note that OpenProject currently does not yet support resource management. However, this is scheduled as one of our main topics. You can check out our roadmap [here](https://community.openproject.com/projects/openproject/work_packages?query_id=1993). Our [use cases](../../use-cases/resource-management/) provide examples for workarounds to support resource management within OpenProject. - +Please note that OpenProject currently does not yet support resource management. However, this is scheduled in our [roadmap](https://community.openproject.org/projects/openproject/roadmap) as one of our main topics. Our [use cases](../../use-cases/resource-management/) provide examples for workarounds to support resource management within OpenProject. diff --git a/docs/user-guide/time-and-costs/time-and-costs-faq/README.md b/docs/user-guide/time-and-costs/time-and-costs-faq/README.md index b54d06b2e7a..4452803be96 100644 --- a/docs/user-guide/time-and-costs/time-and-costs-faq/README.md +++ b/docs/user-guide/time-and-costs/time-and-costs-faq/README.md @@ -8,7 +8,7 @@ keywords: time and costs FAQ, time tracking, time logging, booking costs # Frequently asked questions (FAQ) for Time and costs -## Is there a way to prevent logging hours for Phases (or other work package types)? +## Is there a way to prevent logging hours for Phases (or other work package types)? It is not possible to prevent time logging on phases or restrict it to certain work package types. You could deactivate the fields "Estimated time" and "Spent time" for type Phase (using the [work package form configuration](../../../system-admin-guide/manage-work-packages/work-package-types/#work-package-form-configuration-enterprise-add-on)) but it would still be possible to log time for Phases. @@ -32,5 +32,5 @@ Yes, you can use the "My spent time" widget on My Page and use the filters there ## Does OpenProject offer resource management? You can [set up budgets](../../budgets), [set an Estimated time](../../work-packages/edit-work-package/) for a work package and use the [Assignee board](../../agile-boards/#choose-between-board-types) to find out how many work packages are assigned to a person at the moment. -Additional resource management features will be added within the next years. You can find the road-map for future releases [here](https://community.openproject.com/projects/openproject/work_packages?query_id=1993). -More infomation regarding resource management in OpenProject can be found in the [Use Cases](../../../use-cases/resource-management) section. +Additional resource management features will be added within the next years, as shown in the [roadmap for future releases](https://community.openproject.org/projects/openproject/roadmap). +More information regarding resource management in OpenProject can be found in the [Use Cases](../../../use-cases/resource-management) section. diff --git a/frontend/README.md b/frontend/README.md index f8a9035a61f..d1ef931ee28 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -17,4 +17,4 @@ The [OpenProject Foundation (OPF)](https://community.openproject.org/projects/op ## License OpenProject is licensed under the terms of the GNU General Public License version 3. -See [../doc/COPYRIGHT.rdoc](../doc/COPYRIGHT.rdoc) for details. +See [COPYRIGHT](../COPYRIGHT) for details. diff --git a/frontend/src/locales/README.md b/frontend/src/locales/README.md index a71e648e4fc..b3305a96eb2 100644 --- a/frontend/src/locales/README.md +++ b/frontend/src/locales/README.md @@ -1,4 +1,4 @@ This folder will contain generated translations in your development or production OpenProject environment. To find out more how to contribute to translations, -please see [our translation guide](^https://docs.openproject.org/development/#translations). \ No newline at end of file +please see [our translation guide](https://docs.openproject.org/development/#translations).