diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000000..b16683a0ba --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,159 @@ +# Architecture + +This document provides an overview over the architecture of RDMO and provides information about the different modules. + +## Core dependencies + +RDMO is a [Django](https://www.djangoproject.com/) application with an integrated [React](https://react.dev/) backend. + +On the backend, it makes heavy use of: + +* [Django Rest Framework](https://www.django-rest-framework.org/) (DRF) for the REST API, +* [rules](https://github.com/dfunckt/django-rules) for object based permissions. + +The frontend code relies on: + +* [webpack](https://webpack.js.org) for bundling, +* [Redux](https://redux.js.org/) for state management, +* [Redux Thunk](https://github.com/reduxjs/redux-thunk) for asynchronous Redux actions, +* [Bootstrap](https://getbootstrap.com/) as CSS framework, +* [lodash](https://lodash.com/) for various utilities. + +Testing is done with: + +* [pytest](https://docs.pytest.org) for the backend, +* [Playwright](https://playwright.dev) for the frontend. + +## File layout + +The main `rdmo` package consists of the following modules: + +``` +rdmo/ +├── core/ ← Core functionality +├── accounts/ ← Authentication & user profiles +├── domain/ ← Domain model +├── questions/ ← Structure of the questionnaire +├── conditions/ ← Conditional display of questions or answers +├── options/ ← Controlled vocabularies for answers +├── tasks/ ← Follow up actions based on answers +├── views/ ← Templating for output and export +├── projects/ ← User projects, snapshots and answers +├── management/ ← Management editing backend +└── services/ ← OAuth / external integrations +``` + +Each module (an *app* in Django terms) tries to follow the conventional layout and naming conventions: + +* `admin.py` → Django admin interface configuration +* `apps.py` → Django app configuration +* `assets/` → Source files for the frontend (JavaScript, CSS, ...) +* `constants.py` → Definition of constant values +* `exports.py` → Export plugin functionality +* `filters.py` → Filters for DRF viewsets +* `forms.py` → Django forms +* `handlers/` or `handlers.py` → Handlers for Django signals +* `imports.py` → Helper functionality for the XML import +* `managers.py` → Managers for Django models +* `migrations/` → Django database migrations +* `mixins.py` → Mixins for different classes +* `models/` or `models.py` → Django database models +* `permissions.py` → DRF permission classes +* `providers.py` → Optionset provider plugins +* `renderers/` or `renderers.py` → Render functionality for the XML export +* `rules.py` → Object based permissions +* `serializers/` or `serializers.py` → DRF serializers +* `signals.py` → Signals for Django signals +* `static/` → Build front end assets, ignored by Git +* `templates/` → Django templates +* `templatetags/` → Django template tags and filters +* `tests/` → Tests +* `urls/` or `urls.py` → Django URL mapping +* `utils.py` → Utility functions +* `validators.py` → Additional validators for DRF +* `views/` or `views.py` → Django views +* `viewsets.py` → DRF viewsets for the REST API + +In addition, the `rdmo` repository contains the following notable files or directories: + +* `pyproject.toml` → Python package configuration +* `rdmo/locale` → Translation files +* `rdmo/share` → Supplemental files +* `testing` → Test configuration & fixtures +* `conftest.py` → pytest setup +* `webpack.config.js` → Frontend build configuration +* `package.json` and `package-lock.json` → Frontend dependencies + +The `assets` directories in the modules use the following structure: + +* `assets/js/` → JavaScript front-end code, separated by React app + * `actions/` → Actions for the Redux store + * `api/` → API classes with methods mapping the endpoints of the REST API + * `components/` → React components + * `factories/` → Factory functions for front end objects + * `hooks/` → React hooks + * `reducers/` → Reducers for the Redux store + * `store/` → Configuration and initialization of the Redux store + * `utils/` → Utility functions +* `assets/scss/` → Sass files, separated by React app +* `assets/fonts/`, `assets/img/` → Additional, static assets + +## Internal dependencies + +```plain +┌────────────┐ ┌────────────┐ +│ core │◀───┬────┤ accounts │ +└────────────┘ │ └────────────┘ + │ ┌────────────┐ ┌────────────┐ + ├────┤ domain │◀───┬────┤ projects │ + │ └────────────┘ │ └────────────┘ + │ ┌────────────┐ │ ┌────────────┐ + ├────┤ conditions │◀───┼────┤ management │ + │ └────────────┘ │ └────────────┘ + │ ┌────────────┐ │ + ├────┤ options │◀───┤ + │ └────────────┘ │ + │ ┌────────────┐ │ + ├────┤ questions │◀───┤ + │ └────────────┘ │ + │ ┌────────────┐ │ + ├────┤ tasks │◀───┤ + │ └────────────┘ │ + │ ┌────────────┐ │ + └────┤ views │◀───┘ + └────────────┘ +``` + +The modules depend on each other in the following way: + +* `core` does not depend on the other modules. +* `accounts` does only depend on `core`. +* `conditions`, `domain`, `options`, `questions`, `tasks`, `views` depend only on `core` (with the exception that the `options.Optionset` model and the `conditions.Condition` depend on each other). +* `project` and `management` depend on `conditions`, `domain`, `options`, `questions`, `tasks`, `views` and `core`. + +Besides those dependencies: + +* `utils.py` and `managers.py` must not depend on anything inside the module. +* `models.py` must only depend on `utils.py` and `managers.py`. + +If utility functions, which depend on the models are needed, they are put in special files, e.g. `process.py`. Utility functions for tests are placed in `tests/helpers.py`. + +Only after careful consideration, functions can use local imports (in the function body) to circumvent the described dependency rules. + +## Backend considerations + +RDMO tries to follow the conventional style of Django projects. It should work with all database backends and with all common web server setups. The aim is to limit the dependencies and the effort to maintain an instance to a minimum. For the same reason, RDMO does not depend on a caching solution or an infrastructure for asynchronous tasks. + +While some parts of RDMO use the common Django MVC-pattern using models, (class-based) views and templates, other parts use the Django Rest Service pattern using viewsets, serializers and renderers. The latter is used by the interactive frontend (see below), but also as scriptable API. + +## Frontend considerations + +As already mentioned, major parts of RDMO are implemented as separate interactive *single page applications*. In particular: + +* the projects table located at `/projects/`, +* the project dashboard located at `/projects//`, +* the management interface located at `/management/`. + +The Django template of these pages contain just an empty element, and the functionality is implemented with JavaScript, React and Redux, and makes heavy use of the REST API. + +In order to keep the deployment effort low, no node dependencies need to be handled by the maintainers of the instances. Instead, the frontend is build when creating the release and is then shipped as part of the `rdmo` Python package. Frontend source files reside in `rdmo//assets/` and the build is stored in `rdmo//static/`, from where Django handles them as regular static files. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 395cd039a2..25233403f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,94 +1,104 @@ # Contributing to RDMO -You are welcome to contribute to RDMO by improving and changing it! However, we want to provide a stable software for the community and therefore ask you to follow the following workflow. +RDMO is the work of a community of people committed to improving research data management and the tools that support it. Over the last decade, contributors from different fields and institutions have come together to build something that genuinely serves researchers. -Here is a list of important resources for new contributors: +We are always happy to welcome new contributors! Whether you are reporting a bug, improving the documentation, or working on a new feature, every contribution, no matter how small, is a meaningful part of that effort. We are glad you are here. -- [Source Code](https://github.com/rdmorganiser/rdmo) -- [Documentation](https://rdmo.readthedocs.io) -- [Issue Tracker](https://github.com/rdmorganiser/rdmo/issues) -- [Code of Conduct](https://github.com/rdmorganiser/rdmo/blob/main/CODE_OF_CONDUCT.md) +To make sure contributions can be reviewed and integrated smoothly, we ask that you take a few minutes to read through this guide. It covers how the RDMO community is organized, how we work as a distributed open source project, our development process, and the coding styles and tools we use. -## How to report a bug +While this document describes the work on the main [RDMO repository](https://github.com/rdmorganiser/rdmo), these principles also apply to the other tools and plugins maintained by the RDMO community. Please note the separate documents regarding the [architecture](https://github.com/rdmorganiser/rdmo/blob/main/ARCHITECTURE.md) and [coding style](https://github.com/rdmorganiser/rdmo/blob/main/STYLE.md) of RDMO. -If you found a bug or want a feature to be added, look at the existing [issues](https://github.com/rdmorganiser/rdmo/issues) first. If you find a corresponding issue, please comment on it. If no issue matches, create one (select "Bug report"). +## Code of conduct -When filing an issue, make sure to answer these questions: +To keep our community open, respectful, and welcoming for everyone, we ask all contributors and maintainers to follow our [Code of Conduct](https://github.com/rdmorganiser/rdmo/blob/main/CODE_OF_CONDUCT.md). -- Which operating system and Python version are you using? -- Which version of this project are you using? -- What did you do? -- What did you expect to see? -- What did you see instead? +## Governance + +Since 2024, the **RDMO. Research Data Management Organiser e.V.** acts as governance for the development and maintenance of RDMO, is point of contact for developers and users, organizes release management and manages external communications. All of this is carried out in collaboration with the entire RDMO community. + +A **Release Manager** has been appointed to oversee the technical development of the software, whilst following the instructions of the Board and reporting annually to the General Meeting. This includes the coordination of releases as well as coordinating development activities, ensuring consistent code quality and maintaining the necessary tools for bug tracking, documentation, and source code management. + +The community maintains the **Software Group**, which supports all software development around RDMO, and the **Content Groups**, which works on questionnaires, views and other RDMO content. Both groups meet regularly. If you aim to contribute to RDMO, it is best to join one of those meetings. Please join our mailing list to stay updated on these meetings and all other activities around RDMO: rdmo@listserv.dfn.de. -The best way to get your bug fixed is to provide a test case, and/or steps to reproduce the issue. +You can contact us via email at contact@rdmo.org or join our Matrix space at [rdmo:matrix.org](https://matrix.to/#/#rdmo:matrix.org). -## How to request a feature +## Development process -If you want a feature to be added, look at the existing [issues](https://github.com/rdmorganiser/rdmo/issues) first. If you find a corresponding issue, please comment on it. If no issue matches, create one (select "Feature request"). +RDMO uses GitHub as development platform. The main [RDMO repository](https://github.com/rdmorganiser/rdmo), as well as all other software and content, which is maintained by the community, is stored in the [RDMO GitHub organisation](https://github.com/rdmorganiser). If you consider yourself part of the community, we kindly encourage you to join this organisation as a public member, so others can see you are part of the community. + +### Issues + +If you found a bug in RDMO or want a feature to be added, look at the existing [issues](https://github.com/rdmorganiser/rdmo/issues) first. If you find a corresponding issue, please comment on it. If no issue matches, create one (select *Bug report* or *Feature request*). When reporting a bug, make sure to answer these questions: + +- Which version of RDMO are you using? +- Which RDMO instance are you working with? +- Which operating system, browser and Python version are you using? +- What did you do? +- What did you expect to happen? +- What happened instead? + +The best way to get your bug fixed is to provide precise steps to reproduce the issue. If you decide to work on the issue yourself, please wait until you received some feedback from us. Maybe we are already working on it (and forgot to comment on the issue), or we have other plans for the affected code. -## How to set up your development environment +As a general rule, **all software bugs should be reported as GitHub issues**. An exception are security-critical bugs, which may also be reported confidentially to the release management, to be made public only once they have been fixed. + +In any case, to avoid unintentional duplication of effort, **always create an issue before you start working on the code**. + +### Pull requests + +All changes to RDMO must be submitted as [pull request](https://github.com/rdmorganiser/rdmo/pulls) (PR) on GitHub. As stated above, an issue should be reported beforehand. Each pull request undergoes a review process involving the release manager and/or other experienced developers before it can be merged. Reviewers are expected to assess the changes for correctness, code quality, compatibility, and alignment with the conventions described in this document. + +Pull requests may only be merged once all required reviews have been approved and any raised concerns have been addressed. The release manager holds final responsibility for approving and coordinating the merge of contributions into the main code base. Before merge, PR need to be rebased to the target branch by the creator of the PR. + +The review by the release manager and the other developers **does not replace thorough testing of functionality and performance** by the contributor. Please make the process as easy as possible for the reviewers. + +### Milestones -You need [Python 3.10+](https://www.python.org/downloads). +In order to improve the transparency of the development process, all issues and pull requests are assigned to a [milestone](https://github.com/rdmorganiser/rdmo/milestones), which correspond to the new RDMO version in development. -Install the package with development requirements: +### Releases -```console -$ pip install -e ".[dev]" -$ pip show rdmo -Name: rdmo -Version: 2.0.0 -[...] -``` +Once all issues relating to the milestone have been resolved and the release has been reviewed in depth, the release manager will merge the release branch into the `main` branch and create a new release on [GitHub](https://github.com/rdmorganiser/rdmo/releases) and [PyPI](https://pypi.org/project/rdmo/). Following the release, the community will be notified via email and social media. -See also: [Development docs](https://rdmo.readthedocs.io/en/latest/development/setup.html). +RDMO uses [semantic versioning](https://semver.org). While the major version is only increased for major changes in the data model or the user interface, the minor version is incremented for changes that potentially need instance maintainers to update their local setup or theme. The patch version is increased for non-breaking bug fixes and minor changes to functionality. -## How to test the project +For major and minor releases we prepare a release candidate to be tested by the community. The period between the release candidate and the actual release should be at least 4 weeks. -Run the full test suite with pytest: +### Commits and Branches -```console -$ pytest -``` +Ideally, commits should made often, ideally after every small, logical unit of work. Keep the later use in e.g. `git blame` in mind. Commit messages should be clear and concise and should use the imperative mood, be capitalized, not end in a period, and not exceed 72 characters. If a longer messages is needed, separate subject and body by an empty line (see also [How to Write a Git Commit Message](https://cbea.ms/git-commit/)). -See also: [Testing docs](https://rdmo.readthedocs.io/en/latest/development/testing.html). +The `main` branch of RDMO should always be in sync with the latest release. It is protected on GitHub and must not be updated without consulting the release manager. Usually it is only updated immediately before the release. Release branches are named `/release` and collect all contributions leading to the release. When working on the code, please name your branches according to the following pattern: `//`, e.g. `2.4.1/fix/date-picker` or `2.5.0/feature/nh3`. Please use hyphens (`-`) instead of underscores (`_`). -## How to submit changes +## Coding style -It is recommended to open an issue before starting work on anything. This will allow a chance to talk it over with the owners and validate your approach. +In general, we prioritize readable, maintainable code over clever or overly concise solutions. A key part of this is adhering to the [Locality of Behaviour](https://htmx.org/essays/locality-of-behaviour/) principle: the behaviour of a unit of code should be as obvious as possible by looking only at that unit of code. The logic of a single feature should not be split across distant files or layers when it can reasonably live together. Separate functions should only be created if a certain functionality is used on several occasions, if the separation follows a common pattern or if the function is tested independently. -Please fork our repository and create a new branch named according to what you want to do (e.g. `fix_login_form` or `fancy_feature`). +When submitting pull requests, please keep it small and focused. A good PR addresses a single feature, bug fix, or concern. Avoid bundling unrelated changes, such as refactors, formatting cleanups, or dependency updates, alongside functional changes, as this makes review slower and harder to reason about. If you notice something that needs fixing while working on a PR, open a separate issue or PR for it rather than folding it in. Smaller, well-scoped PRs are easier to review and faster to merge. -Open a [pull request](https://github.com/rdmorganiser/rdmo/pulls) to submit changes to this project. Afterwards, check if your branch is still up to date. If not perform a rebase. The project team will review your pull request. +In RDMO, we try to follow common conventions from the Python, Django and React communities. By now, RDMO has developed its own coding style, which we ask you to respect. For Python code, we use [ruff](https://github.com/astral-sh/ruff) for automatic linting and follow its suggestions. Unlike many Python projects, we do not enforce [black](https://github.com/psf/black) or [ruff format](https://github.com/astral-sh/ruff). Contributors are trusted to follow the style guidelines without automated formatting. -Your pull request needs to meet the following guidelines for acceptance: +For a full breakdown of naming conventions, style, patterns, API and UX considerations see the separate [Code Style Reference](https://github.com/rdmorganiser/rdmo/blob/main/STYLE.md). -- The pytest suite must pass without errors and warnings. -- Include unit tests. -- If your changes add functionality, update the documentation accordingly. +## AI contributions -Feel free to submit early, though—we can always iterate on this. +AI-assisted coding is permitted in this project, though contributors bear full responsibility for any AI-generated code they submit. All contributions, regardless of how they were written, must meet the same standards of quality, correctness, and style. Contributors are expected to review, test, and understand every line of code they submit. **Submitting AI output without verification is not acceptable.** When AI tooling plays a significant role in a contribution, please explain the used models and tooling in the PR description. This helps maintainers give useful feedback and keeps our review process transparent. -To run linting and code formatting checks before committing your change, you can install pre-commit as a Git hook by running the following command: +## Development environment -```console -$ pre-commit install -``` +While not mandatory, we suggest to use the development setup as described in the [RDMO documentation](https://rdmo.readthedocs.io/en/latest/development/index.html). -To run the linting and code formatting checks (e.g. ruff) on the entire code base, use the command: +## Testing -```console -$ pre-commit run --all-files --color=always -``` +Automatic tests are very important to us and we require tests for each new feature implemented. Usually we implement and run integration tests, which perform single requests against a URL or endpoint. Most crucial are tests, which perform requests as different users with different permissions. If you intend to add tests for your contributed code, we recommend looking at the existing tests to get a better understanding of our testing approach. -These checks will run as a CI job as well. +How to run the tests is described in the [Testing docs of the RDMO documentation](https://rdmo.readthedocs.io/en/latest/development/index.html) -## Code style +## Continuous integration -Please use the [coding standards from the Django project](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/) and try to follow our conventions as close as possible. +The [RDMO repository](https://github.com/rdmorganiser/rdmo) on GitHub uses GitHub actions to run a series of test and build jobs on each push to a branch with an active pull request. Passing these checks is mandatory for all contributions. ---- +## Pre-commit hooks -*This contributor guide is adapted from [cookiecutter-hypermodern-python 2022.6.3 (MIT License)](https://github.com/cjolowicz/cookiecutter-hypermodern-python/blob/2022.6.3/%7B%7Bcookiecutter.project_name%7D%7D/CONTRIBUTING.md).* +In order to improve code quality and consistency before every commit, we use [pre-commit](https://pre-commit.com). To install the hook in your copy of the repository, install pre-commit (`pip install pre-commit`) and run `pre-commit install`. From that point on, the configured hooks will run automatically on any staged files when you run commit. If a hook fails or modifies a file, the commit will be aborted and you can review the changes, re-stage the files, and commit again. You can also run all hooks manually against the entire code base at any time with `pre-commit run --all`, which is recommended before opening a pull request. diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000000..d9e31f251e --- /dev/null +++ b/STYLE.md @@ -0,0 +1,102 @@ +# Coding Style + +This document outlines the naming conventions and code patterns used in RDMO, as well as guidelines for the user interface. There may be cases where deviating from these conventions is necessary. In such cases, the reasoning should either be documented in a comment or be clear from the surrounding context of the code. + +## Naming conventions + +Consistent and descriptive naming is one of the key factors for readable and maintainable code. Giving good names to variables, functions, and classes not only makes the code easier to understand and debug, it also helps when reviewing code and onboarding new team members. + +In general, we want to follow established naming conventions used in [Django](https://www.djangoproject.com), [Django Rest Framework](https://www.django-rest-framework.org) (DRF), [React](https://react.dev) and [Bootstrap](https://getbootstrap.com). In addition, please follow the guidelines below when naming new variables, functions or methods: + +* The prefixes `set` and `get` should be used for functions which perform little to none operations. In Python classes, we prefer `@property` instead of `get_egg(self)`. For more complex functions we use `compute` as prefix. An exception are well established Django/DRF patterns like `get_queryset`. +* If functions access the network (e.g. in the front end) or the database, `fetch` and `store` are good prefixes. +* The words `list`, `retrieve`, `create`, `update`, `delete` correspond to *actions* in DRF. The boolean `detail` describes whether the API works with one object or a list. +* The Django permission system uses `view`, `add`, `change`, `delete` to describe the operations it checks. The same words are used in the admin interface. +* The data model of RDMO introduces its own vocabulary, which can lead to confusion. Terms like `catalog`, `section`, `page`, `view`, `attribute`, or `value` should only be used when the context is clear. +* For function props, we use names like `onClick` and `onSubmit`. When handling events in the body of a component, we use functions like `handleClick` or `handleSubmit`. +* Custom hooks should be prefixed with `use`. +* In the front end, `location` describes the current URL visible to the user, usually in the form of a JavaScript state object containing the different parameters. + +Please note [ARCHITECTURE.md](https://github.com/rdmorganiser/rdmo/blob/main/ARCHITECTURE.md) for the file naming conventions used in RDMO. + +## Language-Specific Patterns + +### Python and Django + +For Python code, we use [ruff](https://github.com/astral-sh/ruff) as linter (as configured in `pyproject.toml`) and follow its suggestions. Unlike many Python projects, we do not enforce [black](https://github.com/psf/black) or [ruff format](https://github.com/astral-sh/ruff). Contributors are trusted to follow the style guidelines without automated formatting. + +The maximum line length is 120 characters. When breaking longer statements, we avoid `\`. We use **single quotes for strings**. Double-quotes are used in favor of escaping quotes in strings or when using dicts in f-strings. For constant iterables we use tuples `()` instead of lists `[]`. + +Examples: + +```python +# use parenthesis and indent nicely +last_changed_subquery = Subquery( + Value.objects.filter(project=OuterRef('pk')) + .order_by('-updated') + .values('updated')[:1] +) + +# multi line strings have the space at the end +Error( + "When ACCOUNT_TERMS_OF_USE is enabled, " + "The TermsAndConditionsRedirectMiddleware is missing from the middlewares.", + ... +) + +# we prefer line breaks after { or [ +get_kwargs = { + 'attribute': data.get('attribute'), + 'set_prefix': data.get('set_prefix'), + 'set_index': data.get('set_index') +} +``` + +RDMO uses [Django settings](https://docs.djangoproject.com/en/6.0/ref/settings/) extensively. All settings have a default value set in `rdmo/core/settings.py`. For readability, settings must not check if they exists (e.g. using `try/except` or `hasattr`). + +Complex database requests can have a big impact on the performance of the application. Queries using multiple models / tables need to make use of [select_related](https://docs.djangoproject.com/en/stable/ref/models/querysets/#select-related) and [prefetch_related](https://docs.djangoproject.com/en/stable/ref/models/querysets/#prefetch-related). [Django Debug Toolbar](https://django-debug-toolbar.readthedocs.io) can be used to access database performance. + +### JavaScript and React + +In the JavaScript code, we use [ESlint](https://eslint.org). Like for the Python code, we use a maximum line length of 120 characters and single quotes for strings. Semicolons are omitted. Indentation uses 2 spaces, with switch case bodies indented one level inside the switch and the branches of ternary expressions indented when they wrap. Multiline ternaries must place each arm on its own line whenever the expression spans multiple lines. + +In JSX, multiline expressions must be wrapped in parentheses with the opening parenthesis on a new line. Inside JSX curly braces, newlines are required when the expression is multiline, while single-line expressions must be spaced consistently within the same block. JSX attribute quotes use double quotes (e.g., `className="foo"`), as opposed to the single quotes used elsewhere in JS. + +Examples: + +```javascript +// conditional rendering +{ + attribute.id && ( +
+ {info} +
+ ) +} + +// multiline tertiary operators +{ + page.id ? ( + <> + {gettext('Page')}{': '} + {page.uri} + + ) : ( + {gettext('Create page')} + ) +} +``` + +We only use functional components and only one component should be in a single file. + +## UI/UI considerations + +All front end logic needs to be implemented with a consistent user experience in mind. Generic components reside in `rdmo/core/assets/js/components` and should be used where ever possible. + +The styling should align with Bootstrap and it should be possible to adjust the visual style in a local theme using CSS variables alone and **without re-building the front end**. + +Custom CSS code should be kept to a minimum. For CSS classes hyphens `-` should be used instead of underscores (`_`). + +In the interactive front end, `a` should be used when there is a front end route or a backend URL which users might want to copy or bookmark. In all other cases, a `button` is preferred. The CSS class `link` can be used to make it look like a link. Usually `button` has `type="button"`, unless used as submit button in a form. + +Form input fields should should apply or save automatically. This save operation needs a visual indicator to communicate success to the user. An exception are fields in models modals are only saved on the submit button of the modal.