33 | Woops, look like that link doesn't work.
34 |
35 | These pages are just for quick reference for the main 3 READMES.
36 |
37 | Github renders links to project files correctly.
38 |
39 |
86 | );
87 | };
88 |
89 | DashboardPage.propTypes = {
90 | fetchProjects: PropTypes.func.isRequired,
91 | isLoading: PropTypes.bool.isRequired,
92 | projects: PropTypes.arrayOf(projectType).isRequired,
93 | };
94 |
95 | const mapStateToProps = state => ({
96 | projects: getProjects(state),
97 | isLoading: getIsLoading(state),
98 | });
99 |
100 | const mapDispatchToProps = dispatch => ({
101 | fetchProjects: () => dispatch(fetchProjects()),
102 | });
103 |
104 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);
105 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BLACK ?= \033[0;30m
2 | RED ?= \033[0;31m
3 | GREEN ?= \033[0;32m
4 | YELLOW ?= \033[0;33m
5 | BLUE ?= \033[0;34m
6 | PURPLE ?= \033[0;35m
7 | CYAN ?= \033[0;36m
8 | GRAY ?= \033[0;37m
9 | WHITE ?= \033[1;37m
10 | COFF ?= \033[0m
11 |
12 | .PHONY: all shell build build-node coverage-node coverage-django coverage docker help load_initial_data migrate quality setup superuser test-node test-django test install-npm-dependencies runserver makemigrations eslint eslint-fix lint-django lint-django-fix format
13 |
14 | all: help
15 |
16 | help:
17 | @echo -e "\n$(WHITE)Available commands:$(COFF)"
18 | @echo -e "$(CYAN)make setup$(COFF) - Sets up the project in your local machine."
19 | @echo -e " This includes building Docker containers and running migrations."
20 | @echo -e "$(CYAN)make runserver$(COFF) - Runs the django app in the container, available at http://127.0.0.1:8000"
21 | @echo -e "$(CYAN)make migrate$(COFF) - Runs django's migrate command in the container"
22 | @echo -e "$(CYAN)make makemigrations$(COFF) - Runs django's makemigrations command in the container"
23 | @echo -e "$(CYAN)make superuser$(COFF) - Runs django's createsuperuser command in the container"
24 | @echo -e "$(CYAN)make shell$(COFF) - Starts a Linux shell (bash) in the django container"
25 | @echo -e "$(CYAN)make test$(COFF) - Runs automatic tests on your python code"
26 | @echo -e "$(CYAN)make coverage$(COFF) - Runs code test coverage calculation"
27 | @echo -e "$(CYAN)make quality$(COFF) - Runs code quality tests on your code"
28 | @echo -e "$(CYAN)make format$(COFF) - Runs code formatters on your code"
29 |
30 |
31 | shell:
32 | @echo -e "$(CYAN)Starting Bash in the django container:$(COFF)"
33 | @docker-compose run --rm django bash
34 |
35 | build:
36 | @echo -e "$(CYAN)Creating Docker images:$(COFF)"
37 | @docker-compose build
38 |
39 | build-node:
40 | @echo -e "$(CYAN)Building JavaScript files:$(COFF)"
41 | @docker-compose run --rm node npm run build
42 |
43 | install-npm-dependencies:
44 | @echo -e "$(CYAN)Installing Node dependencies:$(COFF)"
45 | @docker-compose run --rm node npm install
46 |
47 | runserver:
48 | @echo -e "$(CYAN)Starting Docker container with the app.$(COFF)"
49 | @docker-compose up
50 | @echo -e "$(CYAN)App ready and listening at http://127.0.0.1:8000.$(COFF)"
51 |
52 | setup: build install-npm-dependencies build-node migrate
53 | @echo -e "$(GREEN)===================================================================="
54 | @echo "SETUP SUCCEEDED"
55 | @echo -e "Run 'make runserver' to start the Django development server and the node server.$(COFF)"
56 |
57 | test-node:
58 | @echo -e "$(CYAN)Running automatic node.js tests:$(COFF)"
59 | @docker-compose run --rm node npm run test $(cmd)
60 |
61 | test-django:
62 | @echo -e "$(CYAN)Running automatic django tests:$(COFF)"
63 | @docker-compose run --rm django py.test
64 |
65 | test: test-node test-django
66 | @echo -e "$(GREEN)All tests passed.$(COFF)"
67 |
68 | coverage-node:
69 | @echo -e "$(CYAN)Running automatic code coverage check for JavaScript:$(COFF)"
70 | @docker-compose run --rm node npm run test --coverage
71 |
72 | coverage-django:
73 | @echo -e "$(CYAN)Running automatic code coverage check for Python:$(COFF)"
74 | @docker-compose run --rm django sh -c "coverage run -m py.test && coverage html && coverage report"
75 |
76 | coverage: coverage-node coverage-django
77 | @echo -e "$(GREEN)Coverage reports generated:"
78 | @echo "- Python coverage: projement/coverage_html/"
79 | @echo -e "- JavaScript coverage: projement/coverage/$(COFF)"
80 |
81 | makemigrations:
82 | @echo -e "$(CYAN)Running django makemigrations:$(COFF)"
83 | @docker-compose run --rm django ./manage.py makemigrations $(cmd)
84 |
85 | migrate:
86 | @echo -e "$(CYAN)Running django migrations:$(COFF)"
87 | @docker-compose run --rm django ./manage.py migrate $(cmd)
88 |
89 | load_initial_data:
90 | @echo -e "$(CYAN)Loading django fixture:$(COFF)"
91 | @docker-compose run --rm django ./manage.py loaddata projects/fixtures/initial.json
92 |
93 | loadmanyprojects:
94 | @echo -e "$(CYAN)Loading lots of projects:$(COFF)"
95 | @docker-compose run --rm django ./manage.py loadmanyprojects $(cmd)
96 |
97 | superuser:
98 | @echo -e "$(CYAN)Creating Docker images:$(COFF)"
99 | @docker-compose run --rm django ./manage.py createsuperuser
100 |
101 | eslint:
102 | @echo -e "$(CYAN)Running ESLint:$(COFF)"
103 | @docker-compose run --rm node npm run lint
104 |
105 | eslint-fix:
106 | @echo -e "$(CYAN)Running ESLint fix:$(COFF)"
107 | @docker-compose run --rm node npm run lint-fix
108 |
109 | lint-django:
110 | @echo -e "$(CYAN)Running Black check:$(COFF)"
111 | @docker-compose run --rm django black --check .
112 |
113 | lint-django-fix:
114 | @echo -e "$(CYAN)Running Black formatting:$(COFF)"
115 | @docker-compose run --rm django black .
116 |
117 | quality: eslint lint-django
118 | @echo -e "$(GREEN)No code style issues detected.$(COFF)"
119 |
120 | format: eslint-fix lint-django-fix
121 |
--------------------------------------------------------------------------------
/projement/projement/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for projement project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.11.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.11/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.11/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 | SITE_ROOT = os.path.dirname(os.path.dirname(__file__))
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = "pu10i_p%efuvr*cyys_f(g%4xlr1$c*-6dvl^!*@bsywku_b&b"
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = []
30 | INTERNAL_IPS = ["127.0.0.1"]
31 |
32 | STATICFILES_DIRS = (
33 | os.path.join(SITE_ROOT, "static"),
34 | os.path.join(SITE_ROOT, "app", "build"),
35 | )
36 |
37 | WEBPACK_LOADER = {
38 | "DEFAULT": {
39 | "BUNDLE_DIR_NAME": "",
40 | "STATS_FILE": os.path.join(SITE_ROOT, "app", "webpack-stats.json"),
41 | }
42 | }
43 |
44 |
45 | # Application definition
46 |
47 | INSTALLED_APPS = [
48 | "projects",
49 | "projement",
50 | "crispy_forms",
51 | "webpack_loader",
52 | "rest_framework",
53 | "debug_toolbar",
54 | "django.contrib.admin",
55 | "django.contrib.auth",
56 | "django.contrib.contenttypes",
57 | "django.contrib.sessions",
58 | "django.contrib.messages",
59 | "django.contrib.staticfiles",
60 | ]
61 |
62 | MIDDLEWARE = [
63 | "debug_toolbar.middleware.DebugToolbarMiddleware",
64 | "django.middleware.security.SecurityMiddleware",
65 | "django.contrib.sessions.middleware.SessionMiddleware",
66 | "django.middleware.common.CommonMiddleware",
67 | "django.middleware.csrf.CsrfViewMiddleware",
68 | "django.contrib.auth.middleware.AuthenticationMiddleware",
69 | "django.contrib.messages.middleware.MessageMiddleware",
70 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
71 | ]
72 |
73 | ROOT_URLCONF = "projement.urls"
74 |
75 | TEMPLATES = [
76 | {
77 | "BACKEND": "django.template.backends.django.DjangoTemplates",
78 | "DIRS": ["templates"],
79 | "APP_DIRS": True,
80 | "OPTIONS": {
81 | "context_processors": [
82 | "django.template.context_processors.debug",
83 | "django.template.context_processors.request",
84 | "django.contrib.auth.context_processors.auth",
85 | "django.contrib.messages.context_processors.messages",
86 | "django_settings_export.settings_export",
87 | ],
88 | },
89 | },
90 | ]
91 |
92 | WSGI_APPLICATION = "projement.wsgi.application"
93 |
94 |
95 | # Database
96 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
97 |
98 | DATABASES = {
99 | "default": {
100 | "ENGINE": "django.db.backends.sqlite3",
101 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
102 | }
103 | }
104 |
105 |
106 | # Password validation
107 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
108 |
109 | AUTH_PASSWORD_VALIDATORS = [
110 | {
111 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
112 | },
113 | {
114 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
115 | },
116 | {
117 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
118 | },
119 | {
120 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
121 | },
122 | ]
123 |
124 |
125 | # Static site url, used when we need absolute url but lack request object, e.g. in email sending.
126 | SITE_URL = "http://127.0.0.1:8000"
127 |
128 | # Authentication URLs
129 | # https://docs.djangoproject.com/en/1.11/ref/settings/#login-redirect-url
130 |
131 | LOGIN_REDIRECT_URL = "app"
132 | LOGIN_URL = "login"
133 |
134 |
135 | # Internationalization
136 | # https://docs.djangoproject.com/en/1.11/topics/i18n/
137 |
138 | LANGUAGE_CODE = "en-us"
139 |
140 | TIME_ZONE = "UTC"
141 |
142 | USE_I18N = True
143 |
144 |
145 | USE_TZ = True
146 |
147 |
148 | # Static files (CSS, JavaScript, Images)
149 | # https://docs.djangoproject.com/en/1.11/howto/static-files/
150 |
151 | STATIC_URL = "/static/"
152 |
153 |
154 | # Crispy forms
155 | # http://django-crispy-forms.readthedocs.io/
156 |
157 | CRISPY_TEMPLATE_PACK = "bootstrap4"
158 |
159 |
160 | # All these settings will be made available to javascript app
161 | SETTINGS_EXPORT = [
162 | "DEBUG",
163 | "SITE_URL",
164 | "STATIC_URL",
165 | ]
166 |
167 | if DEBUG:
168 | # Trick to have debug toolbar when developing with docker
169 | import socket
170 |
171 | hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
172 | INTERNAL_IPS += [".".join(ip.split(".")[:-1]) + ".1" for ip in ips]
173 |
--------------------------------------------------------------------------------
/projement/app/webpack/config.base.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const path = require('path');
3 | const webpack = require('webpack');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const BundleTracker = require('webpack-bundle-tracker');
6 | const autoprefixer = require('autoprefixer');
7 |
8 |
9 | // The app/ dir
10 | const app_root = path.resolve(__dirname, '..');
11 | // The django app's dir
12 | const project_root = path.resolve(app_root, '..');
13 |
14 | // Enable deprecation warnings
15 | process.traceDeprecation = true;
16 |
17 |
18 | function makeConfig(options) {
19 | const mode = options.mode || 'none';
20 | const output = {
21 | path: path.resolve(app_root, 'build'),
22 | filename: options.filenameTemplate + '.js',
23 | chunkFilename: options.filenameTemplate + '.chunk.js',
24 | publicPath: options.publicPath,
25 | library: 'projement',
26 | hashFunction: 'sha512',
27 | hashDigestLength: 32,
28 | };
29 |
30 | return {
31 | entry: {
32 | app: options.prependSources.concat(['@babel/polyfill', 'main.js']),
33 | styles: options.prependSources.concat([path.resolve(project_root, 'static', 'styles-src', 'main.js')]),
34 | },
35 |
36 | mode,
37 |
38 | output,
39 |
40 | module: {
41 | rules: [{
42 | test: /\.js$/, // Transform all .js files required somewhere with Babel
43 | exclude: /node_modules/,
44 | use: 'babel-loader',
45 | }, {
46 | test: /\.(css|scss)$/,
47 | include: [
48 | // CSS modules should only be generated from css/scss files within the src directory
49 | path.resolve(app_root, 'src'),
50 |
51 | // Global stylesheets in the static directory do not generate modules
52 | path.resolve(project_root, 'static'),
53 | path.resolve(project_root, 'node_modules')
54 | ],
55 | use: [
56 | // When MiniCssExtractPlugin becomes stable and supports all options, convert if needed
57 | MiniCssExtractPlugin.loader,
58 | {
59 | loader: 'css-loader',
60 | options: {
61 | sourceMap: true,
62 | importLoaders: 1,
63 | modules: {
64 | localIdentName: '[path][name]__[local]--[hash:base64:5]',
65 | getLocalIdent: (loaderContext, localIdentName, localName, options) => {
66 | // Everything that comes from our global style folder and node_modules will be in global scope
67 | if (/styles-src|node_modules/.test(loaderContext.resourcePath)) {
68 | return localName;
69 | }
70 | // Everything listed under vendorCss will be in global scope
71 | if (vendorCss.includes(loaderContext.resourcePath)) {
72 | return localName;
73 | }
74 |
75 | return null;
76 | },
77 | },
78 | },
79 | }, {
80 | loader: "postcss-loader",
81 | options: {
82 | postcssOptions: {
83 | plugins: function () {
84 | return [autoprefixer];
85 | },
86 | },
87 | },
88 | }, {
89 | loader: "resolve-url-loader",
90 | }, {
91 | loader: "sass-loader",
92 | options: {
93 | sourceMap: true,
94 | sassOptions: {
95 | includePaths: [
96 | path.resolve(project_root, 'static', 'styles-src'),
97 | path.resolve(project_root, 'node_modules', 'bootstrap-sass', 'assets', 'stylesheets'),
98 | ],
99 | outputStyle: 'expanded',
100 | },
101 | },
102 | },
103 | ],
104 | }, {
105 | test: /\.(jpe?g|png|gif|svg|woff2?|eot|ttf)$/,
106 | type: 'asset/inline',
107 | }, {
108 | test: /\.md$/i,
109 | use: 'raw-loader',
110 | },
111 | {
112 | test: /\.(html)$/,
113 | use:[{loader: 'html-loader',}],
114 | }],
115 | },
116 |
117 | plugins: [
118 | new MiniCssExtractPlugin({
119 | filename: options.filenameTemplate + '.css',
120 | chunkFilename: options.filenameTemplate + '.chunk.css',
121 | }),
122 | new BundleTracker({
123 | path: __dirname,
124 | filename: 'webpack-stats.json',
125 | indent: 2,
126 | logTime: true,
127 | }),
128 | new webpack.DefinePlugin({
129 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), // Inject environmental variables to client side
130 | }),
131 | ].concat(options.plugins),
132 |
133 | optimization: {
134 | minimize: options.minimize,
135 |
136 | splitChunks: {
137 | chunks: 'all',
138 |
139 | cacheGroups: {
140 | default: false,
141 | defaultVendors: {
142 | idHint: 'vendors',
143 | test: /node_modules/, // Include all assets in node_modules directory
144 | reuseExistingChunk: true,
145 | enforce: true,
146 | chunks: 'initial',
147 | minChunks: 1,
148 | },
149 | },
150 | },
151 | runtimeChunk: 'single',
152 | },
153 |
154 | resolve: {
155 | modules: ['app/src', 'node_modules'],
156 | extensions: ['.js'],
157 | },
158 |
159 | devtool: options.devtool,
160 | target: 'web', // Make web variables accessible to webpack, e.g. window
161 | // stats: false, // Don't show stats in the console
162 |
163 | performance: options.performance,
164 | };
165 | }
166 |
167 |
168 | module.exports = makeConfig;
169 |
--------------------------------------------------------------------------------
/projement/app/README.adoc:
--------------------------------------------------------------------------------
1 | :toc:
2 | = Projement front-end
3 |
4 | The front-end of this application is a React app that's rendered inside a Django
5 | template (link:../templates/app.html[`app.html`]) and that fetches required data
6 | from the REST API that the back-end exposes.
7 |
8 | == Folder structure
9 |
10 | The source code for the front-end is in the link:src[`src/`] folder. The webpack
11 | configuration is in the link:webpack[`webpack/`] folder and the built JavaScript
12 | files that Django will serve are built to the link:build[`build/`] folder (You most
13 | likely don't need to touch either of these directories).
14 |
15 | The `src/` folder is split into folders by "domain", so the folder structure
16 | looks similar to the Django project. Instead of having a few folders at the top
17 | level like `src/components/` and `src/pages/`, the first folders should be
18 | similar to the Django `apps`. Each front-end `app` can include a few different
19 | kinds of files and folders. Let's take the `projects` example:
20 |
21 | [source,text]
22 | ----
23 | projects
24 | ├── ducks // Redux ducks related to this project (check out the Redux section in this readme)
25 | ├── forms // Forms built using Formik
26 | │ ├── EditProjectForm // The form for editing the time spent on a project
27 | │ │ ├── EditProjectForm.js // The component is declared here
28 | │ │ ├── EditProjectForm.test.js // And the tests for that component
29 | │ │ └── index.js // This exports the component so that it can be imported like so:
30 | │ │ // `import EditProjectForm from 'projects/forms/EditProjectForm'`
31 | │ └── index.js // This exports all forms so that it's even more convenient to
32 | │ // import: `import { EditProjectForm } from 'projects/forms'`
33 | ├── pages // Pages related to projects, just normal react components
34 | │ ├── DashboardPage // Follows the same structure as a component
35 | │ ├── EditProjectPage
36 | │ └── index.js
37 | ├── propTypes.js // React Prop Types related to projects
38 | └── testUtils.js // Some test utilities (for example, generating a mock project object)
39 |
40 | ----
41 |
42 | The other `app` in the front-end is link:src/core[`core`]. This includes things
43 | that are not really related to any specific `app` but are related to the
44 | application as a whole. So, things like the link:src/core/Navbar[`Navbar`
45 | component], utility components like
46 | link:src/core/PrivateRoute[`PrivateRoute`], and utilities like
47 | link:src/core/testUtils.js[`testUtils`] go here. Also, the `core` app includes
48 | generic "set up" and configuration files. For example, setting up the Redux
49 | store happens in link:src/core/store.js[`store.js`].
50 |
51 | == Testing
52 |
53 | https://testing-library.com/docs/react-testing-library/intro[React Testing
54 | Library] is used
55 | for testing React components in this project. It features a limited API to test
56 | React components exactly like how they are used by the users of the application.
57 | This means that there's no testing of internal states, props or method
58 | invocations, the main things that should be tested are the things that the user
59 | can see and interact with.
60 |
61 | Test files are located next to the thing that they are testing. For example, the
62 | tests for `Navbar.js` are located in the `Navbar.test.js` file next to
63 | `Navbar.js`.
64 |
65 | You can run front-end tests with the `make test-node` command.
66 |
67 | If you want to test things related to routing, Redux or HTTP requests, check out
68 | the sections for those (<>, <> and
69 | <>).
70 |
71 | == Routing
72 |
73 | https://reacttraining.com/react-router/[React Router] is used for routing in
74 | the front-end. The routes are declared in the link:src/core/App.js[`App`
75 | component]. There is also a utility component
76 | link:src/core/PrivateRoute[`PrivateRoute`] that redirects the user to the login
77 | page if they are not logged in.
78 |
79 | An example on how to test components that depend on the router can be seen in
80 | the
81 | link:src/projects/forms/EditProjectPage/EditProjectPage.test.js[`EditProjectPage.test.js`].
82 | In a nutshell, the component needs to be rendered inside a React Router `Router`
83 | component and it might be necessary to wrap it in a `Route` component as well.
84 | The utility for this is the link:src/core/testUtils.js[`renderWithContext` function in the core test
85 | utilities].
86 |
87 | _Note_ that this is generally not how we do routing in our projects but this
88 | solution is quite simple and works well enough for a test assignment.
89 |
90 | == Redux store
91 |
92 | Most of our projects use Redux for state management. There's not a whole lot
93 | there, but it's similar to how it's set up in most of our projects. We use Ducks
94 | as a way to organize our Redux store. In a nutshell, this means that all related
95 | reducers/actions/selectors/etc. are located next to each other in one file. You
96 | can read more about the Ducks idea here:
97 | https://github.com/erikras/ducks-modular-redux/[`ducks-modular-redux`].
98 |
99 | Generally, Ducks can include the following logic:
100 |
101 | * Actions
102 | * Reducers
103 | * Action creators
104 | * Asynchronous action creators for making HTTP requests, for example (using
105 | https://github.com/reduxjs/redux-thunk[Redux Thunk])
106 | * Selectors
107 |
108 | In this application, the only Duck we have is the link:src/projects/ducks/projects.js[`projects`
109 | duck]. It includes the following logic:
110 |
111 | * Fetching the projects - `fetchProjects` function
112 | * Update a project - `updateProject` function
113 | * Store the projects in the Redux store - action creators and reducers
114 | * Keep track of the loading state for projects
115 | * Selectors for selecting data from the Redux store
116 |
117 | The whole Redux store for the application is quite small:
118 |
119 | [source,js]
120 | ----
121 | {
122 | projects: {
123 | projects: [ // List of projects as they are received from the API
124 | {id: 1, name: 'GateMe', company: {...}, ...}
125 | ],
126 | isLoading: false, // If anything related to projects is loading
127 | },
128 | }
129 | ----
130 |
131 | If you want to test a component that wants to use the Redux store, you can use
132 | the `renderWithContext` utility function from
133 | link:src/core/testUtils.js[`testUtils`] to wrap the component in a Redux provider.
134 | See an example in
135 | link:src/projects/pages/DashboardPage/DashboardPage.test.js[`DashboardPage.test.js`].
136 |
137 | == Communication with the back-end
138 |
139 | The communication with the back-end is done via HTTP requests to various API
140 | endpoints to `/api/*` URLs.
141 |
142 | As briefly mentioned in the Redux store section, this project uses Redux Thunk
143 | to make HTTP requests. This means that a component can conveniently dispatch an
144 | action to start any kind of HTTP request. For example, the link:src/projects/pages/DashboardPage/DashboardPage.js[`DashboardPage`
145 | component] calls
146 | `fetchProjects()` if the component wants to trigger the fetching of projects.
147 |
148 | If you want to test a component that makes some HTTP requests, you can use
149 | http://www.wheresrhys.co.uk/fetch-mock/[`fetch-mock`] to mock the HTTP
150 | request. See an example in
151 | link:src/projects/pages/DashboardPage/DashboardPage.test.js[`DashboardPage.test.js`].
152 |
153 | == Forms
154 |
155 | We generally use https://github.com/jaredpalmer/formik[Formik] to build forms.
156 | An example of this can be seen in the
157 | link:src/projects/forms/EditProjectForm/EditProjectForm.js[`EditProjectForm`].
158 |
159 | == Linting
160 |
161 | ESLint and Prettier are set up and should work out of the box. You can run `make
162 | eslint` to check your code for linting errors.
163 |
164 | === Formatting
165 |
166 | `make eslint-fix` will have eslint try to fix any errors which it is able to.
167 |
168 | == Notes
169 |
170 | Depending on your development environment set up, you might need to install the
171 | `npm` packages locally (it can help your editor with autocompletion and
172 | linting):
173 |
174 | [source,bash]
175 | ----
176 | # From the project root
177 | cd projement
178 | npm install
179 | ----
180 |
181 | You should now have the packages installed locally to `projement/node_modules`
182 | and you should be able to run `npm` scripts locally (not through Docker):
183 |
184 | [source,bash]
185 | ----
186 | npm run test
187 | npm run lint
188 | ----
189 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | :toc:
2 |
3 | = Projement - simple project management tool
4 |
5 | ----
6 | All rights reserved by Thorgate Management OÜ.
7 | The contents of this repository can be reproduced, copied, quoted or
8 | distributed only with written permission of Thorgate Management OÜ.
9 | ----
10 |
11 | == Project overview
12 |
13 | Projement is a simplified tool for project managers. Project managers can have
14 | an overview of all the projects in a company. This includes estimated and actual
15 | hours spent on _design_, _development_ and _testing_.
16 |
17 | [IMPORTANT]
18 | ===========
19 | This README just outlines the test assignment tasks and basic project setup.
20 | Make sure to check out the `README` files for the front-end and back-end.
21 | As they document how to run tests as well as linters.
22 |
23 | * Back-end readme: link:projement/README.adoc[projement/README.adoc]
24 | * Front-end readme: link:projement/app/README.adoc[projement/app/README.adoc]
25 | ===========
26 |
27 |
28 | === Structure overview
29 |
30 | NOTE: It is best to follow the structure section of the README in GitHub or
31 | GitLab since the links work best there.
32 | The assignment view of the web app does not handle linking to other files or folders.
33 |
34 | The application is split into two parts – the back-end (written in Python &
35 | Django), and the front-end (written in JavaScript & React).
36 |
37 | The general folder structure for the project can be seen below:
38 |
39 | ----
40 | ├── docker # Includes Docker files for both front and back-end
41 | ├── Makefile # A bunch of utility commands to help developers
42 | ├── projement # The Django app, back-end of the project
43 | │ ├── requirements.txt # Python dependencies
44 | │ ├── app # The React app, front-end of the project
45 | │ │ └── README.md # Useful information about the front-end
46 | │ └── README.md # Useful information about the back-end
47 | └── README.md # General overview of the project & the assignments (this file)
48 |
49 | ----
50 |
51 | To perform some routine operations, a link:Makefile[Makefile] with a set of `make`
52 | commands is provided. In order to run these commands, the GNU `make` utility
53 | needs to be installed. Some of the commands are listed below.
54 |
55 | TIP: To see exactly what a make command is doing, you can run it with the `-n` argument. +
56 | `make migrate -n` will output: +
57 | ```echo -e "\033[0;36mRunning django migrations:\033[0m" +
58 | docker-compose run --rm django ./manage.py migrate ``` +
59 | This that you can see how to run arbitrary django or node commands.
60 |
61 |
62 |
63 |
64 | === Setup
65 |
66 |
67 | ==== System Prerequisites
68 |
69 | To be able to run the project in Docker environment, it's necessary to have
70 | https://docs.docker.com/[`docker`] and
71 | https://docs.docker.com/compose/[`docker-compose`] installed.\
72 |
73 | TIP: Please refer to
74 | https://docs.docker.com/install/[Docker installation docs] and +
75 | https://docs.docker.com/compose/install/[Docker Compose
76 | installation docs] to install them.
77 |
78 |
79 | === QuickStart
80 |
81 | To build and setup the application from the ground up, just type:
82 |
83 | [source,bash]
84 | ----
85 | make setup
86 | ----
87 |
88 | This will create the necessary Docker containers and install the required
89 | Python and NPM packages.
90 |
91 | ==== Database
92 |
93 | To start the database will be empty.
94 |
95 | `make setup` also runs migrations automatically.
96 |
97 | To manually migrate the database, run:
98 |
99 | ----
100 | make migrate
101 | ----
102 |
103 |
104 | ===== Application data
105 |
106 | At the start the project has no data in the database. No users, or projects.
107 |
108 | .To create a superuser:
109 | ----
110 | make superuser
111 | ----
112 |
113 | .To load initial data for projects:
114 | ----
115 | make load_initial_data
116 | ----
117 |
118 |
119 |
120 | ==== Running the application
121 |
122 | ----
123 | make runserver
124 | ----
125 |
126 | After a successful startup, the application should be accessible at
127 | http://127.0.0.1:8000.
128 |
129 | [TIP]
130 | =====
131 | There are a number of other useful `make` commands which you can check out with
132 | `make help` or by looking at the link:Makefile[Makefile].
133 |
134 | Also make commands can be chained together: `make setup superuser load_initial_data` will run all the above commands in order.
135 | =====
136 |
137 |
138 | == The Assignment
139 |
140 | === Read before you start.
141 |
142 | *Make sure to read through the whole assignment before you start writing your
143 | solutions. The last tasks might be more complicated than the first ones and,
144 | depending on the implementation, they might be related to each other.*
145 |
146 | * Please use the best practices known to you to make the commits and manage
147 | branches in the repository.
148 | * We've set up formatters and linters for both back-end and front-end to help enforce best
149 | practices. You can run the linters with `make quality`.
150 | * We expect our engineers to provide high quality features and therefore to test
151 | the parts of their code that they feel should be tested. The same applies to
152 | you.
153 | ** There are some example tests for both back-end and front-end, feel free to
154 | use them as a reference.
155 | ** You can run tests for both back-end and front-end with `make test`.
156 | ** If you're running out of time or are not sure how to test a specific
157 | thing, add a comment where you describe what you would test and which
158 | scenarios you would test.
159 | * If you have any ideas on how to improve Projement - either on the
160 | architectural side, back-end implementation, code quality or developer
161 | experience - please write them down inside your Merge request.
162 | ** This project is a simplified example of our project structure. Which is why
163 | some of the tools used here are not current best practice, partly so they can be pointed out in this assigment.
164 | ** Imagine if this project came onto your table and the client wanted to improve it, what would be the first things you would do / offer to the client?
165 |
166 | ==== If you have any issues or questions about the tasks
167 |
168 | * If minor issue document them as TODOs in code comments and work around them to figure out the best solution.
169 | * For bigger issues you can also ask us via e-mail or phone, but it might take
170 | some time until we respond.
171 |
172 | ==== Finishing the Assignment
173 |
174 | We expect an experienced full-stack developer to complete the assignment in *4-6 hours*. +
175 | Taking longer is not a problem, but you still shouldn't exceed a total of 8-10 hours.
176 |
177 | We value your own time as well, so just us know what you'd have wanted to complete, if you were to spend more time. +
178 | Please do so in a code-comment in your MR in the most relevant places.
179 |
180 |
181 | IMPORTANT: When you have finished, create a Pull Request in GitHub containing the entire solution, and request for a review from the owner of the repository.
182 |
183 | === Tasks
184 |
185 | ==== 1. Fix project ordering on the dashboard
186 |
187 | Currently, the projects on the dashboard are ordered by start date. The project managers want to see them in a different order.
188 |
189 | *As a result of this task:*
190 |
191 | * Projects on the dashboard must be ordered by end date descendingly.
192 | * Projects that have not ended yet, must be shown first.
193 | * Make sure that the projects' list in the Django admin has the same ordering.
194 |
195 | ==== 2. Actual hours need to be decimals
196 |
197 | Currently, all the actual hours (design, development, testing) for the `Project` model are integers.
198 | Project managers want to have more precision - they need to be changed to decimals.
199 |
200 | *As a result of this task:*
201 |
202 | * The actual hours fields must be `DecimalField`s.
203 | * The actual hours must be in the range of `0 <= x < 10000` and have 2 decimal places.
204 | * All other changes necessary to keep the application running, must be made (e.g. migrations).
205 | * Make sure that it's possible to save the decimal values through the front-end
206 | as well.
207 |
208 | ==== 3. Incremental changes
209 |
210 | When two people edit the same project at the same time, and both want to increase actual hours by 10, they end up with faulty results.
211 |
212 | For example, if the actual development hours are currently 25 in a project, and two developers begin
213 | editing the form simultaneously, then both have an initial value of 25 in the form.
214 | They both did 10 hours of work, and thus insert 35 as the development hours.
215 |
216 | After both have submitted the form, the actual development hours stored in the database are 35,
217 | even though both developers did 10 hours of work and the resulting value should be 45 (25+10+10).
218 |
219 | This issue applies for all actual hours: development, design and testing.
220 |
221 | *As a result of this task:*
222 |
223 | * Instead of entering the total amount of actual hours, the user only has to enter the additional amount of development, design and testing hours that they have spent since last update.
224 | * It must be possible for two users to enter their additional hours simultaneously, with both entries taken into account.
225 |
226 | ==== 4. Weird results for "project has ended"
227 |
228 | There are some weird results for the "project has ended" indicator in the
229 | Dashboard (when a project's name has been crossed out). We're not sure what
230 | exactly the problem is. The crossing out of projects seems to be pretty random
231 | at the moment.
232 |
233 | *As a result of this task:*
234 |
235 | * The projects should be correctly crossed out if they have actually ended.
236 |
237 | ==== 5. Slow dashboard
238 |
239 | The project managers have noticed that the dashboard gets slower and slower when
240 | more projects have been added. We think that it might be because the database
241 | queries are not optimized.
242 |
243 | *As a result of this task:*
244 |
245 | * The dashboard performance issues should be solved.
246 |
247 | *Note:* You can use a management command to generate a lot of projects:
248 |
249 | [source,bash]
250 | ----
251 | # Creates 300 projects by default
252 | make loadmanyprojects
253 | # You can also specify the number of projects to create:
254 | make loadmanyprojects cmd="--nr-of-projects 100"
255 | ----
256 |
257 | *Note:* This task should be done before pagination (Task 6) has been
258 | implemented as pagination can help a bit with performance. We would like a
259 | different solution from pagination in this task.
260 |
261 | ==== 6. Add pagination to the dashboard
262 |
263 | There are quite a lot of projects in the application and the project managers
264 | have noticed that the dashboard can get a bit slow and they would prefer not to
265 | scroll through a hundred projects. It would help if the list of projects in the
266 | dashboard was paginated.
267 |
268 | *As a result of this task:*
269 |
270 | * The dashboard has a paginated list of projects.
271 | * The pagination should happen without a full reload of the page.
272 |
273 | Describe how you would make the pagination and its user experience better if you
274 | don't manage to implement everything. For example, write these as TODO-s in the
275 | README.md.
276 |
277 | ==== 7. (Bonus) Replace SQLite with a better database management system
278 |
279 | The project currently uses https://sqlite.org/[SQLite] as its database engine.
280 | SQLite is great, but it's not very well suited for large web applications (like
281 | Projement). It makes sense to move to something more scalable like
282 | https://www.postgresql.org/[PostgreSQL] or https://www.mysql.com/[MySQL].
283 |
284 | *As a result of this task:*
285 |
286 | * The project should use PostgreSQL, MySQL, or some other more advanced database
287 | management system.
288 | * The database should run inside Docker and be started along with the rest of
289 | the application when running `docker-compose up` or `make runserver`.
290 | * The database should not lose any data between Docker restarts. For example, if
291 | the Docker containers are stopped (`docker-compose down`) and started again
292 | (`docker-compose up`).
293 |
--------------------------------------------------------------------------------