diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command new file mode 100644 index 0000000000..054f2d23e5 --- /dev/null +++ b/.buildkite/hooks/pre-command @@ -0,0 +1,10 @@ +#!/bin/bash + +set -eu + +export PATH="$HOME/.rbenv/bin:$PATH" + +eval "$(rbenv init -)" + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" \ No newline at end of file diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 0000000000..b546375f11 --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,3 @@ +steps: + - name: ":rspec:" + command: "bundle install && bundle exec rspec --color specs" \ No newline at end of file diff --git a/.buildkite/template.yml b/.buildkite/template.yml new file mode 100644 index 0000000000..eefa71ed75 --- /dev/null +++ b/.buildkite/template.yml @@ -0,0 +1,5 @@ +# Used by the 'Add to Buildkite' button in the readme +name: "Ruby rbenv Example" +steps: + - label: ":pipeline:" + command: "buildkite-agent pipeline upload" diff --git a/.erb-lint.yml b/.erb-lint.yml new file mode 100644 index 0000000000..0524feab8b --- /dev/null +++ b/.erb-lint.yml @@ -0,0 +1,20 @@ +--- +glob: "app/views/**/*.{html}{+*,}.erb" +exclude: + - "**/vendor/**/*" + - "**/node_modules/**/*" +EnableDefaultLinters: true +linters: + PartialInstanceVariable: + enabled: true + ErbSafety: + enabled: true + Rubocop: + enabled: true + rubocop_config: + inherit_from: + - .rubocop.yml + Style/FrozenStringLiteralComment: + Enabled: false + Layout/TrailingEmptyLines: + Enabled: false diff --git a/.eslint-rules/custom.js b/.eslint-rules/custom.js new file mode 100644 index 0000000000..f053ebf797 --- /dev/null +++ b/.eslint-rules/custom.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/.eslint-rules/globals.js b/.eslint-rules/globals.js new file mode 100644 index 0000000000..cb0884a705 --- /dev/null +++ b/.eslint-rules/globals.js @@ -0,0 +1,13 @@ +module.exports = { + // Globals can be disabled with the string "off" + // "writable" to allow the variable to be overwritten or "readonly" to disallow overwriting. + globals: { + Atomics: "readonly", + SharedArrayBuffer: "readonly", + // Makes logger function available everywhere. Else eslint will complaint of undef-var. + logger: "readonly", + module: "writable", + // Makes props obtained from Rails backend available everywhere in this project. + globalProps: "readonly", + }, +}; diff --git a/.eslint-rules/helpers/index.js b/.eslint-rules/helpers/index.js new file mode 100644 index 0000000000..d3b487285e --- /dev/null +++ b/.eslint-rules/helpers/index.js @@ -0,0 +1,49 @@ +const fs = require("fs"); + +const buildPathGroupsBasedOnWebpackAliases = ({ + customJSRoot = "app/javascript/", + customAliasPath = "config/webpack/alias.js", +}) => { + const rootOfProject = __dirname + `/../../`; + + const isFile = filePath => + fs.existsSync(filePath) && fs.lstatSync(filePath).isFile(); + + const webpackAliasPath = rootOfProject + customAliasPath; + + const hasWebpackAliasConfig = isFile(webpackAliasPath); + + const isRailsProject = isFile(rootOfProject + "Gemfile"); + + const emptyPathGroups = []; + + if (!hasWebpackAliasConfig || !isRailsProject) return emptyPathGroups; + + const { + resolve: { alias }, + } = require(webpackAliasPath); + + const railsJSFilesRoot = rootOfProject + customJSRoot; + + const pathGroups = Object.entries(alias).map(([name, path]) => { + // sometimes alias might be already resolved to full absolute path + const isAleadyAnAbsolutePath = + path.includes("cypress-tests/") || path.includes("app/"); + + const absolutePath = isAleadyAnAbsolutePath + ? path + : `${railsJSFilesRoot}${path}`; + const wildCard = + isFile(absolutePath + ".js") || isFile(absolutePath + ".jsx") + ? "" + : "/**"; + + let group = "internal"; + + return { pattern: `${name}${wildCard}`, group }; + }); + + return pathGroups; +}; + +module.exports = { buildPathGroupsBasedOnWebpackAliases }; diff --git a/.eslint-rules/imports/enforced.js b/.eslint-rules/imports/enforced.js new file mode 100644 index 0000000000..1071f16ba0 --- /dev/null +++ b/.eslint-rules/imports/enforced.js @@ -0,0 +1,34 @@ +module.exports = { + rules: { + // not-auto-fixable: Prefer a default export if module exports a single name. + "import/prefer-default-export": "off", + // not-auto-fixable: Forbid a module from importing a module with a dependency path back to itself. + "import/no-cycle": ["error", { maxDepth: 1, ignoreExternal: true }], + // not-auto-fixable: Prevent unnecessary path segments in import and require statements. + "import/no-useless-path-segments": ["error", { noUselessIndex: true }], + // not-auto-fixable: Report any invalid exports, i.e. re-export of the same name. + "import/export": "error", + // not-auto-fixable: Forbid the use of mutable exports with var or let. + "import/no-mutable-exports": "error", + // not-auto-fixable: Ensure all imports appear before other statements. + "import/first": "error", + // not-auto-fixable: Ensure all exports appear after other statements. + "import/exports-last": "error", + // auto-fixable: Enforce a newline after import statements. + "import/newline-after-import": ["error", { count: 1 }], + // auto-fixable: Remove file extensions for import statements. + "import/extensions": [ + "error", + "never", + { + ignorePackages: true, + pattern: { + json: "always", + mp3: "always", + svg: "always", + mapper: "always", + }, + }, + ], + }, +}; diff --git a/.eslint-rules/imports/order.js b/.eslint-rules/imports/order.js new file mode 100644 index 0000000000..4561d595fb --- /dev/null +++ b/.eslint-rules/imports/order.js @@ -0,0 +1,64 @@ +const { buildPathGroupsBasedOnWebpackAliases } = require(__dirname + + "/../helpers"); +const pathGroups = buildPathGroupsBasedOnWebpackAliases({}); + +const pathGroupForKeepingReactImportsAtTop = { + pattern: "react+(-native|)", + group: "external", + position: "before", +}; + +/* +Example pathGroups structure. Adding this here +so that if anyone wants to add custom config, +they can make use of this: +[ + { pattern: 'apis/**', group: 'internal' }, + { pattern: 'common/**', group: 'internal' }, + { pattern: 'components/**', group: 'internal' }, + { pattern: 'constants/**', group: 'internal' }, + { pattern: 'contexts/**', group: 'internal' }, + { pattern: 'reducers/**', group: 'internal' }, + { pattern: 'Constants', group: 'internal' }, + { + pattern: 'react+(-native|)', + group: 'external', + position: 'before' + } +] +*/ +pathGroups.push(pathGroupForKeepingReactImportsAtTop); + +module.exports = { + rules: { + // auto-fixable: Enforce a convention in module import order + "import/order": [ + "error", + { + "newlines-between": "always", + alphabetize: { order: "asc", caseInsensitive: true }, + warnOnUnassignedImports: true, + groups: [ + "builtin", + "external", + "internal", + "index", + "sibling", + "parent", + "object", + "type", + ], + /* + * Currently we check for existence of webpack alias + * config and then iterate over the aliases and create + * these pathGroups. Only caveat with this mechanism + * is that in VSCode eslint plugin won't dynamically + * read it. But eslint cli would! + */ + pathGroups, + // Ignore react imports so that they're always ordered to the top of the file. + pathGroupsExcludedImportTypes: ["react", "react-native"], + }, + ], + }, +}; diff --git a/.eslint-rules/overrides.js b/.eslint-rules/overrides.js new file mode 100644 index 0000000000..4399b54d61 --- /dev/null +++ b/.eslint-rules/overrides.js @@ -0,0 +1,24 @@ +module.exports = { + // Currently we are using this section for excluding certain files from certain rules. + overrides: [ + { + files: [ + ".eslintrc.js", + ".prettierrc.js", + "app/assets/**/*", + "app/javascript/packs/**/*", + "*.json", + ], + rules: { + "import/order": "off", + "react-hooks/rules-of-hooks": "off", + }, + }, + { + files: ["app/javascript/packs/**/*.{js,jsx}"], + rules: { + "no-redeclare": "off", + }, + }, + ], +}; diff --git a/.eslint-rules/promise.js b/.eslint-rules/promise.js new file mode 100644 index 0000000000..c9d59d9904 --- /dev/null +++ b/.eslint-rules/promise.js @@ -0,0 +1,8 @@ +module.exports = { + rules: { + // not-auto-fixable: ensure people use async/await promising chaining rather than using "then-catch-finally" statements + "promise/prefer-await-to-then": "error", + // auto-fixable: avoid calling "new" on a Promise static method like reject, resolve etc + "promise/no-new-statics": "error", + }, +}; diff --git a/.eslint-rules/react.js b/.eslint-rules/react.js new file mode 100644 index 0000000000..35a5b30fc8 --- /dev/null +++ b/.eslint-rules/react.js @@ -0,0 +1,92 @@ +module.exports = { + rules: { + // not-auto-fixable: Prevent missing props validation in a React component definition. + "react/prop-types": "off", + // not-auto-fixable: Detect unescaped HTML entities, which might represent malformed tags. + "react/no-unescaped-entities": "off", + // not-auto-fixable: Prevent missing displayName in a React component definition. Useful when using React extensions in browser and checking for component name. + "react/display-name": "error", + // not-auto-fixable: Reports when this.state is accessed within setState. + "react/no-access-state-in-setstate": "error", + // not-auto-fixable: Prevent usage of dangerous JSX props. Currently jam3 plugin will take care of handling this. + "react/no-danger": "off", + // not-auto-fixable: Report when a DOM element is using both children and dangerouslySetInnerHTML. + "react/no-danger-with-children": "warn", + // not-auto-fixable: Prevent definitions of unused prop types. + "react/no-unused-prop-types": "error", + // not-auto-fixable: Report missing key props in iterators/collection literals. Important rule! + "react/jsx-key": "error", + // not-auto-fixable: Enforce no duplicate props. + "react/jsx-no-duplicate-props": "error", + // not-auto-fixable: Disallow undeclared variables in JSX. + "react/jsx-no-undef": "error", + // not-auto-fixable: Enforce PascalCase for user-defined JSX components. + "react/jsx-pascal-case": ["error", { allowNamespace: true }], + // not-auto-fixable: Prevent React to be incorrectly marked as unused. + "react/jsx-uses-react": "error", + // not-auto-fixable: Prevent variables used in JSX to be marked as unused. + "react/jsx-uses-vars": "error", + // not-auto-fixable: Ensures https://reactjs.org/docs/hooks-rules.html. + "react-hooks/rules-of-hooks": "error", + // not-auto-fixable: Ensures https://reactjs.org/docs/hooks-rules.html - Checks effect dependencies. + "react-hooks/exhaustive-deps": "warn", + // auto-fixable: A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment. + "react/jsx-no-useless-fragment": ["error", { allowExpressions: true }], + // auto-fixable: Prefer arrow function expressions for component declaration. + "react/function-component-definition": [ + "error", + { + namedComponents: "arrow-function", + unnamedComponents: "arrow-function", + }, + ], + // auto-fixable: Components without children can be self-closed to avoid unnecessary extra closing tag. + "react/self-closing-comp": [ + "error", + { + component: true, + html: true, + }, + ], + // auto-fixable: Wrapping multiline JSX in parentheses can improve readability and/or convenience. + "react/jsx-wrap-multilines": [ + "error", + { + declaration: "parens-new-line", + assignment: "parens-new-line", + return: "parens-new-line", + arrow: "parens-new-line", + condition: "parens-new-line", + logical: "parens-new-line", + prop: "ignore", + }, + ], + // not-auto-fixable: Make sure files containing JSX is having .jsx extension. + "react/jsx-filename-extension": ["error", { allow: "as-needed" }], + // auto-fixable: Omit mentioning the "true" value if it can be implicitly understood in props. + "react/jsx-boolean-value": "error", + // auto-fixable: Partially fixable. Make sure the state and setter have symmertic naming. + "react/hook-use-state": "error", + // auto-fixable: Shorthand notations should always be at the top and also enforce props alphabetical sorting. + "react/jsx-sort-props": [ + "error", + { + callbacksLast: true, + shorthandFirst: true, + multiline: "last", + reservedFirst: false, + locale: "auto", + }, + ], + // auto-fixable: Disallow unnecessary curly braces in JSX props and/or children. + "react/jsx-curly-brace-presence": [ + "error", + { + props: "never", + children: "never", + // JSX prop values that are JSX elements should be enclosed in braces. + propElementValues: "always", + }, + ], + }, +}; diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..11279c58fc --- /dev/null +++ b/.eslintignore @@ -0,0 +1,16 @@ +node_modules +build +.eslintrc +public +coverage +db +docs +log +.scripts +test +tmp +.vscode +babel.config.js +app/javascript/packs +jsconfig.json +package.json diff --git a/.eslintrc b/.eslintrc index ff67d35757..01f8deb807 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,106 +1,163 @@ { - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "sourceType": "module" + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "sourceType": "module" + }, + "env": { + "es6": true, + "node": true, + "jest": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "./.eslint-rules/globals", + "./.eslint-rules/imports/order", + "./.eslint-rules/overrides", + // ensure that you don't add custom rules + // without taking permission from team leads. + "./.eslint-rules/custom", + // custom rules cannot override the following rules. + "./.eslint-rules/imports/enforced", + "./.eslint-rules/react", + "./.eslint-rules/promise", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "settings": { + "import/extensions": [".js", ".jsx", ".ts", ".tsx"], + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] }, - "env": { - "es6": true, - "node": true, - "jest": true + "import/resolver": { + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx", ".svg", ".json", ".mp3"] + } }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" + "react": { + "pragma": "React", + "fragment": "Fragment", + "version": "detect" + } + }, + "plugins": [ + "react", + "prettier", + "import", + "@typescript-eslint", + "react-hooks", + "promise", + "jam3", + "unused-imports" + ], + "globals": { + "fetch": false, + "document": false, + "Promise": true, + "log": true, + "sessionStorage": true, + "localStorage": true, + "FileReader": true, + "window": true + }, + "rules": { + "@typescript-eslint/no-explicit-any": "off", + // auto-fixable: Respect all Prettier rules and apply it. + "prettier/prettier": "error", + "react/jsx-filename-extension": [ + "error", + { "extensions": [".js",".jsx",".ts",".tsx"] } + ], + // not-auto-fixable: No unused variables allowed. + "no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "caughtErrors": "all" + } ], - "settings": { - "import/extensions": [ - ".js", - ".jsx", - ".ts", - ".tsx" - ], - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] + // not-auto-fixable: No undefined variables allowed. + "no-undef": "error", + // not-auto-fixable: Dont use console statements. Use logger which babel will remove during bundling. + "no-console": "error", + // auto-fixable: sadly this doesn't support guard clauses yet. + "padding-line-between-statements": [ + "error", + { "blankLine": "always", "prev": "if", "next": ["if", "return"] }, + // The newline-before-return rule is deprecated in favor of the following: + { "blankLine": "always", "prev": "*", "next": "return" }, + // Add newline between function declarations + { + "blankLine": "always", + "prev": [ + "block", + "multiline-block-like", + "function", + "iife", + "multiline-const", + "multiline-expression", + ], + "next": ["function", "iife", "multiline-const", "multiline-expression"], }, - "import/resolver": { - "node": { - "extensions": [ - ".js", - ".jsx", - ".ts", - ".tsx" - ] - } + ], + // auto-fixable: Single line statements needn't have any braces. But in all other cases enforce curly braces. + "curly": ["error", "multi-line"], + // auto-fixable: Remove the else part, if the "if" or "else-if" chain has a return statement + "no-else-return": "error", + // not-auto-fixable: Prevent un-sanitized dangerouslySetInnerHTML. + "jam3/no-sanitizer-with-danger": [ + 2, + { + "wrapperName": ["dompurify", "sanitizer", "sanitize"], }, - "react": { - "pragma": "React", - "fragment": "Fragment", - "version": "detect" - } - }, - "plugins": [ - "react", - "import", - "@typescript-eslint" ], - "globals": { - "fetch": false, - "document": false, - "Promise": true, - "log": true, - "sessionStorage": true, - "localStorage": true, - "FileReader": true, - "window": true - }, - "rules": { - "@typescript-eslint/no-explicit-any": "off", - "object-curly-newline": "off", - "no-console": ["error", { "allow": ["warn", "error"] }], - "comma-dangle": ["error", "never"], - "indent": ["warn", 2, { "SwitchCase": 1 }], - "@typescript-eslint/no-unused-vars": ["error"], - "key-spacing": 1, - "keyword-spacing": 2, - "object-curly-spacing": [1, "always"], - "semi": 2, - "array-bracket-spacing": [2, "never"], - "arrow-body-style": ["error", "as-needed"], - "func-style": ["error", "expression"], - "space-before-function-paren": 2, - "no-multiple-empty-lines": ["warn", { "max": 1, "maxEOF": 1 }], - "quotes": ["warn", "double"], - "prefer-const": ["warn", { "destructuring": "any", "ignoreReadBeforeAssign": false }], - "no-var": 1, - "no-extra-boolean-cast": 1, - "no-unneeded-ternary": 1, - "react/no-unescaped-entities": 0, - "react/prop-types": 0, - "react/jsx-key": 0, - "import/order": ["error", { - "newlines-between": "always", - "alphabetize": { "order": "asc", "caseInsensitive": true }, - "warnOnUnassignedImports": true, - "groups": ["builtin", "external", "internal", "sibling", "parent", "index", "object", "type"], - "pathGroups": [ - { "pattern": "react", "group": "builtin", "position": "before" }, - { "pattern": "common/**", "group": "internal" }, - { "pattern": "context/**", "group": "internal" }, - { "pattern": "components/**", "group": "internal" }, - { "pattern": "assets/**", "group": "internal" }, - { "pattern": "apis/**", "group": "internal" }, - { "pattern": "constants/**", "group": "internal", "position": "after" }, - { "pattern": "utils/**", "group": "internal" }, - { "pattern": "helpers/**", "group": "internal" } - ], - "pathGroupsExcludedImportTypes": ["builtin"] - }] - } + // auto-fixable: Requires trailing commas when the last element or property is in a different line than the closing ] or } + "comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never", + }, + ], + // auto-fixable: If a variable is never reassigned, using the const declaration is better. + "prefer-const": "error", + // auto-fixable: It is considered good practice to use the type-safe equality operators === and !==. + // "eqeqeq": "error", + // not-auto-fixable: Rule flags optional chaining expressions in positions where short-circuiting to undefined causes throwing a TypeError afterward. + "no-unsafe-optional-chaining": "error", + // auto-fixable: Remove all unused imports. + "unused-imports/no-unused-imports": "error", + // auto-fixable-1-level-deep: Using nested ternary operators make the code unreadable. Use if/else or switch with if/else. If it's JSX then move it out into a function or a variable. It's fine to use nestedTernary in JSX when it makes code more readable. + "no-nested-ternary": "warn", + // auto-fixable: Enforces no braces where they can be omitted. + "arrow-body-style": ["error", "as-needed"], + // auto-fixable: Suggests using template literals instead of string concatenation. + "prefer-template": "error", + // auto-fixable: Disallows ternary operators when simpler alternatives exist. + "no-unneeded-ternary": ["error", { "defaultAssignment": false }], + // auto-fixable: Partially fixable. Prefer {x} over {x: x}. + "object-shorthand": [ + "error", + "always", + { "avoidQuotes": true, "ignoreConstructors": true }, + ], + // auto-fixable: Partially fixable. Unless there's a need to the this keyword, there's no advantage of using a plain function. + "prefer-arrow-callback": ["error", { "allowUnboundThis": true }], + // not-auto-fixable: Convert multiple imports from same module into a single import. + "no-duplicate-imports": ["error", { "includeExports": true }], + // auto-fixable: Partially fixable. In JavaScript, there are a lot of different ways to convert value types. Allow only readable coercions. + "no-implicit-coercion": ["error", { "allow": ["!!"] }], + // auto-fixable: Require let or const instead of var. + "no-var": "error", + // auto-fixable: This rule conflicts with prettier rules. Thus we've NOT kept this rule in react file. This rule ensures we don't add blank lines in JSX. + "react/jsx-newline": ["error", { "prevent": true }] } +} diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6376c1e868..bed949601d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,9 @@ ## Notion card ## Summary - + ## Preview - [ ] I have manually tested all workflows - [ ] I have performed a self-review of my own code +- [ ] I have annotated changes in the PR that are relevant to the reviewer - [ ] I have added automated tests for my code diff --git a/.github/workflows/validations.yml b/.github/workflows/validations.yml index 42a8d5b35d..a649651b32 100644 --- a/.github/workflows/validations.yml +++ b/.github/workflows/validations.yml @@ -65,6 +65,8 @@ jobs: - name: Setup Env's run: | cp .env.example .env + - name: ffi pristine + run: bundle pristine ffi - name: Setup test database env: RAILS_ENV: test diff --git a/.gitignore b/.gitignore index 37715bb29a..d69f709844 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ cypress/artifacts/screenshots cypress/artifacts/videos cypress/screenshots cypress/videos +cypress/downloads .DS_Store diff --git a/.husky/helpers/prevent_pushing_to_develop.sh b/.husky/helpers/prevent_pushing_to_develop.sh new file mode 100644 index 0000000000..6c33fde144 --- /dev/null +++ b/.husky/helpers/prevent_pushing_to_develop.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +prevent_pushing_to_develop() { + current_branch=`git symbolic-ref HEAD` + current_origin=`git remote` + if [ current_origin = "origin" -o "$current_branch" = "refs/heads/develop" -o "$current_branch" = "refs/heads/main" ] + then + cat <= 0.72.0 + } + }, + "ruby.format": "rubocop", + } diff --git a/Dockerfile.local b/Dockerfile.local index 9133e689ee..4d96faf9f2 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,11 +1,7 @@ #syntax=docker/dockerfile:1 -ARG RUBY_VERSION=3.1.2 - -FROM ruby:$RUBY_VERSION-slim AS base +FROM ruby:3.1.3-slim AS base ARG NODE_VERSION=16.4.2 -ARG BUNDLER_VERSION=2.3.11 -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true RUN mkdir /app WORKDIR /app @@ -18,6 +14,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl gnupg2 && apt-get install -y --no-install-recommends nodejs yarn && \ apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* + +RUN curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ + && apt-get update && apt-get install google-chrome-stable -y --no-install-recommends postgresql-client-13 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* + + FROM base AS build-deps RUN apt-get update && apt-get install -y --no-install-recommends git build-essential libpq-dev && \ @@ -26,7 +29,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends git build-essen FROM build-deps AS gems -RUN gem install bundler -v $BUNDLER_VERSION +RUN gem install bundler -v 2.3.11 COPY Gemfile Gemfile.lock ./ @@ -40,10 +43,8 @@ RUN yarn install --check-files FROM base -RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client-13 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* COPY --from=gems /app /app COPY --from=gems /usr/local/bundle /usr/local/bundle COPY --from=node_modules /app/node_modules /app/node_modules -COPY . ./ +COPY . ./ \ No newline at end of file diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000000..fcc24d40d4 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,52 @@ +#syntax=docker/dockerfile:1 +FROM ruby:3.1.3-slim AS base + +ARG NODE_VERSION=16.4.2 + +RUN mkdir /app +WORKDIR /app +RUN mkdir -p tmp/pids + +RUN apt-get update && apt-get install -y --no-install-recommends curl gnupg2 && \ + echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list && \ + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ + curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ + apt-get install -y --no-install-recommends nodejs yarn && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* + + +RUN curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ + && apt-get update && apt-get install google-chrome-stable -y --no-install-recommends postgresql-client-13 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* + + +FROM base AS build-deps + +RUN apt-get update && apt-get install -y --no-install-recommends git build-essential libpq-dev && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* + + +FROM build-deps AS gems + +RUN gem install bundler -v 2.3.11 + +COPY Gemfile Gemfile.lock ./ + +RUN bundle install && rm -rf vendor/bundle/ruby/*/cache + +FROM base AS node_modules + +COPY package.json yarn.lock ./ + +RUN yarn install --check-files + +FROM base + + +COPY --from=gems /app /app +COPY --from=gems /usr/local/bundle /usr/local/bundle +COPY --from=node_modules /app/node_modules /app/node_modules +COPY . ./ + +USER buildkite-agent:buildkite-agent \ No newline at end of file diff --git a/Gemfile b/Gemfile index e0af73f8cc..d35812d3b4 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "3.1.2" +ruby "3.1.3" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.0.4" @@ -109,6 +109,8 @@ gem "data_migrate", "~> 8.0.0.rc2" # pagy for Pagination gem "pagy", "~> 5.10" +gem "nokogiri", ">= 1.13.10" + # Manage application specific business logic. https://github.com/AaronLasseigne/active_interaction gem "active_interaction" @@ -116,7 +118,7 @@ gem "active_interaction" gem "stripe" # Background job processing adapter -gem "sidekiq" +gem "sidekiq", "<7" # job scheduler extension for Sidekiq gem "sidekiq-scheduler" @@ -145,14 +147,16 @@ group :development, :test do # Add Rubocop to lint and format Ruby code gem "rubocop", require: false - gem "rubocop-packaging", require: false gem "rubocop-performance", require: false gem "rubocop-rails", require: false - gem "rubocop-rspec", "~> 2.8", require: false + gem "rubocop-rspec", require: false # Use RSpec as the testing framework gem "rspec-rails", "~> 5.0", ">= 5.0.2" + # For linting ERB files + gem "erb_lint", require: false, git: "https://github.com/Shopify/erb-lint.git", branch: "main" + # Simple one-liner tests for common Rails functionality gem "shoulda-callback-matchers", "~> 1.1.1" gem "shoulda-matchers", "~> 5.1" @@ -191,6 +195,9 @@ group :development do end group :test do + # BuildKite Test Collector + # gem "buildkite-test_collector" + # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem "capybara", ">= 3.26" gem "selenium-webdriver", ">= 4.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index b9e7adf8a0..be8f6f91a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,19 @@ +GIT + remote: https://github.com/Shopify/erb-lint.git + revision: c0440a7ac3503a3bbcad9cdf9b2bbdcce15f0992 + branch: main + specs: + erb_lint (0.3.1) + activesupport + better_html (>= 2.0.1) + parser (>= 2.7.1.4) + rainbow + rubocop + smart_properties + GIT remote: https://github.com/heartcombo/devise - revision: f8d1ea90bc328012f178b8a6616a89b73f2546a4 + revision: 6d32d2447cc0f3739d9732246b5a5bde98d9e032 branch: main specs: devise (4.8.1) @@ -58,9 +71,9 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_interaction (4.1.0) - activemodel (>= 5, < 8) - activesupport (>= 5, < 8) + active_interaction (5.2.0) + activemodel (>= 5.2, < 8) + activesupport (>= 5.2, < 8) activejob (7.0.4) activesupport (= 7.0.4) globalid (>= 0.3.6) @@ -69,7 +82,7 @@ GEM activerecord (7.0.4) activemodel (= 7.0.4) activesupport (= 7.0.4) - activerecord-import (1.4.0) + activerecord-import (1.4.1) activerecord (>= 4.2) activestorage (7.0.4) actionpack (= 7.0.4) @@ -83,44 +96,51 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) annotate (3.2.0) activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.594.0) - aws-sdk-core (3.131.1) + aws-partitions (1.673.0) + aws-sdk-core (3.168.3) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.57.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (1.61.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-s3 (1.117.2) + aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.0) + aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) bcrypt (3.1.18) + better_html (2.0.1) + actionview (>= 6.0) + activesupport (>= 6.0) + ast (~> 2.0) + erubi (~> 1.4) + parser (>= 2.4) + smart_properties bindex (0.8.1) - bootsnap (1.11.1) + bootsnap (1.15.0) msgpack (~> 1.2) builder (3.2.4) - bullet (7.0.1) + bullet (7.0.4) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - bundler-audit (0.9.0.1) + bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) - capybara (3.37.1) + capybara (3.38.0) addressable matrix mini_mime (>= 0.1.3) @@ -129,15 +149,13 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - childprocess (4.1.0) combine_pdf (1.0.22) matrix ruby-rc4 (>= 0.1.5) concurrent-ruby (1.1.10) - connection_pool (2.2.5) - countries (5.0.1) - i18n_data (~> 0.16.0) - sixarm_ruby_unaccent (~> 1.1) + connection_pool (2.3.0) + countries (5.2.0) + unaccent (~> 0.3) crack (0.4.5) rexml crass (1.0.6) @@ -150,17 +168,16 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - debug (1.5.0) - irb (>= 1.3.6) - reline (>= 0.2.7) + debug (1.7.0) + irb (>= 1.5.0) + reline (>= 0.3.1) diff-lcs (1.5.0) - digest (3.1.0) discard (1.2.1) activerecord (>= 4.2, < 8) docile (1.4.0) - dotenv (2.7.6) - dotenv-rails (2.7.6) - dotenv (= 2.7.6) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) railties (>= 3.2) elasticsearch (7.13.3) elasticsearch-api (= 7.13.3) @@ -170,7 +187,7 @@ GEM elasticsearch-transport (7.13.3) faraday (~> 1) multi_json - erubi (1.10.0) + erubi (1.11.0) et-orbi (1.2.7) tzinfo execjs (2.8.1) @@ -179,9 +196,9 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.21.0) + faker (3.0.0) i18n (>= 1.8.11, < 2) - faraday (1.10.0) + faraday (1.10.2) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -197,8 +214,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -206,32 +223,31 @@ GEM faraday-retry (1.0.3) ffi (1.15.5) foreman (0.87.2) - fugit (1.5.3) + fugit (1.8.0) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) globalid (1.0.0) activesupport (>= 5.0) - grover (1.1.1) + grover (1.1.2) combine_pdf (~> 1.0) nokogiri (~> 1.0) hash_dot (2.5.0) hashdiff (1.0.1) hashie (5.0.0) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) - i18n_data (0.16.0) - simple_po_parser (~> 1.1) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) io-console (0.5.11) - irb (1.4.1) + irb (1.5.1) reline (>= 0.3.0) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) - jmespath (1.6.1) - jwt (2.3.0) + jmespath (1.6.2) + json (2.6.3) + jwt (2.5.0) launchy (2.5.0) addressable (~> 2.7) letter_opener (1.8.1) @@ -241,61 +257,62 @@ GEM letter_opener (~> 1.7) railties (>= 5.2) rexml - loofah (2.18.0) + loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.1) + mail (2.8.0) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp marcel (1.0.2) matrix (0.4.2) method_source (1.0.0) - mini_magick (4.11.0) + mini_magick (4.12.0) mini_mime (1.1.2) - minitest (5.15.0) + minitest (5.16.3) money (6.16.0) i18n (>= 0.6.4, <= 2) - msgpack (1.5.2) + msgpack (1.6.0) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.1.1) - net-imap (0.2.3) - digest + multipart-post (2.2.3) + net-imap (0.3.1) net-protocol - strscan net-pop (0.1.2) net-protocol - net-protocol (0.1.3) + net-protocol (0.2.1) timeout net-smtp (0.3.3) net-protocol - newrelic_rpm (8.7.0) + newrelic_rpm (8.13.1) nio4r (2.5.8) - nokogiri (1.13.9-aarch64-linux) + nokogiri (1.13.10-arm64-darwin) racc (~> 1.4) - nokogiri (1.13.9-arm64-darwin) + nokogiri (1.13.10-x86_64-darwin) + nokogiri (1.13.10-x86_64-linux) racc (~> 1.4) - nokogiri (1.13.9-x86_64-darwin) + nokogiri (1.13.10-x86_64-linux) racc (~> 1.4) - nokogiri (1.13.9-x86_64-linux) - racc (~> 1.4) - oauth2 (1.4.9) + oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) multi_xml (~> 0.5) - rack (>= 1.2, < 3) + rack (>= 1.2, < 4) + snaky_hash (~> 2.0) + version_gem (~> 1.1) omniauth (2.1.0) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection - omniauth-google-oauth2 (1.0.1) + omniauth-google-oauth2 (1.1.1) jwt (>= 2.0) - oauth2 (~> 1.1) + oauth2 (~> 2.0.6) + omniauth (~> 2.0) + omniauth-oauth2 (~> 1.8.0) + omniauth-oauth2 (1.8.0) + oauth2 (>= 1.4, < 3) omniauth (~> 2.0) - omniauth-oauth2 (~> 1.7.1) - omniauth-oauth2 (1.7.2) - oauth2 (~> 1.4) - omniauth (>= 1.9, < 3) omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) @@ -303,25 +320,25 @@ GEM pagy (5.10.1) activesupport parallel (1.22.1) - parser (3.1.2.0) + parser (3.1.3.0) ast (~> 2.4.1) - pg (1.3.5) - public_suffix (4.0.7) - puma (5.6.4) + pg (1.4.5) + public_suffix (5.0.1) + puma (5.6.5) nio4r (~> 2.0) pundit (2.2.0) activesupport (>= 3.0.0) raabro (1.4.0) - racc (1.6.0) + racc (1.6.1) rack (2.2.4) rack-mini-profiler (3.0.0) rack (>= 1.2.0) - rack-protection (2.2.0) + rack-protection (3.0.4) rack - rack-proxy (0.7.2) + rack-proxy (0.7.4) rack - rack-test (1.1.0) - rack (>= 1.0, < 3) + rack-test (2.0.2) + rack (>= 1.3) rails (7.0.4) actioncable (= 7.0.4) actionmailbox (= 7.0.4) @@ -343,8 +360,8 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.4.3) - loofah (~> 2.3) + rails-html-sanitizer (1.4.4) + loofah (~> 2.19, >= 2.19.1) railties (7.0.4) actionpack (= 7.0.4) activesupport (= 7.0.4) @@ -364,8 +381,8 @@ GEM execjs railties (>= 3.2) tilt - redis (4.6.0) - regexp_parser (2.5.0) + redis (4.8.0) + regexp_parser (2.6.1) reline (0.3.1) io-console (~> 0.5) responders (3.0.1) @@ -373,14 +390,14 @@ GEM railties (>= 5.0) rexml (3.2.5) rolify (6.0.0) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.0) + rspec-core (3.12.0) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) + rspec-support (~> 3.12.0) rspec-rails (5.1.2) actionpack (>= 5.2) activesupport (>= 5.2) @@ -389,29 +406,28 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.11.0) - rubocop (1.30.0) + rspec-support (3.12.0) + rubocop (1.40.0) + json (~> 2.3) parallel (~> 1.10) - parser (>= 3.1.0.0) + parser (>= 3.1.2.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.18.0, < 2.0) + rubocop-ast (>= 1.23.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.18.0) + rubocop-ast (1.24.0) parser (>= 3.1.1.0) - rubocop-packaging (0.5.1) - rubocop (>= 0.89, < 2.0) - rubocop-performance (1.14.0) + rubocop-performance (1.15.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - rubocop-rails (2.14.2) + rubocop-rails (2.17.3) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-rspec (2.11.1) - rubocop (~> 1.19) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.15.0) + rubocop (~> 1.33) ruby-progressbar (1.11.0) ruby-rc4 (0.1.5) ruby-vips (2.1.4) @@ -420,7 +436,7 @@ GEM ruby_audit (2.1.0) bundler-audit (~> 0.9.0) rubyzip (2.3.2) - rufus-scheduler (3.8.1) + rufus-scheduler (3.8.2) fugit (~> 1.1, >= 1.1.6) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -432,62 +448,64 @@ GEM sprockets (> 3.0) sprockets-rails tilt - searchkick (5.0.3) + searchkick (5.1.1) activemodel (>= 5.2) hashie - selenium-webdriver (4.2.0) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.7.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) semantic_range (3.0.0) - sentry-rails (5.5.0) + sentry-rails (5.7.0) railties (>= 5.0) - sentry-ruby (~> 5.5.0) - sentry-ruby (5.5.0) + sentry-ruby (~> 5.7.0) + sentry-ruby (5.7.0) concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-sidekiq (5.5.0) - sentry-ruby (~> 5.5.0) + sentry-sidekiq (5.7.0) + sentry-ruby (~> 5.7.0) sidekiq (>= 3.0) shoulda-callback-matchers (1.1.4) activesupport (>= 3) - shoulda-matchers (5.1.0) + shoulda-matchers (5.2.0) activesupport (>= 5.2.0) - sidekiq (6.4.2) - connection_pool (>= 2.2.2) + sidekiq (6.5.8) + connection_pool (>= 2.2.5, < 3) rack (~> 2.0) - redis (>= 4.2.0) - sidekiq-scheduler (4.0.1) + redis (>= 4.5.0, < 5) + sidekiq-scheduler (4.0.2) redis (>= 4.2.0) rufus-scheduler (~> 3.2) sidekiq (>= 4) tilt (>= 1.4.0) - simple_po_parser (1.1.6) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - sixarm_ruby_unaccent (1.2.0) - spring (4.0.0) - sprockets (4.0.3) + smart_properties (1.17.0) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) + spring (4.1.0) + sprockets (4.1.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - stripe (6.2.0) - strscan (3.0.4) + stripe (8.0.0) thor (1.2.1) - tilt (2.0.10) - timeout (0.3.0) + tilt (2.0.11) + timeout (0.3.1) tzinfo (2.0.5) concurrent-ruby (~> 1.0) - unicode-display_width (2.1.0) + unaccent (0.4.0) + unicode-display_width (2.3.0) uniform_notifier (1.16.0) vcr (3.0.3) + version_gem (1.1.1) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.0) @@ -495,7 +513,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.0.0) + webdrivers (5.2.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0) @@ -514,13 +532,12 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.5.4) + zeitwerk (2.6.6) PLATFORMS - aarch64-linux - arm64-darwin-21 - x86_64-darwin-20 - x86_64-darwin-21 + ruby + arm64-darwin-22 + x86_64-darwin-22 x86_64-linux DEPENDENCIES @@ -528,7 +545,6 @@ DEPENDENCIES activerecord-import annotate aws-sdk-s3 - bcrypt (~> 3.1.7) bootsnap (>= 1.4.4) bullet bundler-audit @@ -541,18 +557,19 @@ DEPENDENCIES discard (~> 1.2) dotenv-rails elasticsearch (< 7.14) + erb_lint! factory_bot_rails faker foreman grover hash_dot - honeybadger image_processing (>= 1.2) jbuilder (~> 2.11) letter_opener letter_opener_web money newrelic_rpm (~> 8.4) + nokogiri (>= 1.13.10) omniauth-google-oauth2 (~> 1.0) omniauth-rails_csrf_protection (~> 1.0) pagy (~> 5.10) @@ -568,10 +585,9 @@ DEPENDENCIES rolify (~> 6.0) rspec-rails (~> 5.0, >= 5.0.2) rubocop - rubocop-packaging rubocop-performance rubocop-rails - rubocop-rspec (~> 2.8) + rubocop-rspec ruby_audit sass-rails searchkick @@ -581,7 +597,7 @@ DEPENDENCIES sentry-sidekiq shoulda-callback-matchers (~> 1.1.1) shoulda-matchers (~> 5.1) - sidekiq + sidekiq (< 7) sidekiq-scheduler simplecov spring @@ -594,7 +610,7 @@ DEPENDENCIES webpacker RUBY VERSION - ruby 3.1.2p20 + ruby 3.1.3p185 BUNDLED WITH - 2.3.11 + 2.3.26 diff --git a/README.md b/README.md index 7da0109cad..1c24e44e32 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,14 @@ brew install postgresql brew install elastic/tap/elasticsearch-full brew services start elasticsearch-full ``` + To run elasticsearch on latest macos(ventura) please follow the below instructions + - Install Docker Desktop ( M1 / Intel ) https://www.docker.com/products/docker-desktop/ + - Run below command in your terminal & you can check by opening `localhost:9200` + ``` + docker run -dp 127.0.0.1:9200:9200 -p 127.0.0.1:9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.17.7 + ``` + - Install Chrome Extension to browse the Cluster ( Kind of like PGAdmin for Elastic Search ) https://chrome.google.com/webstore/search/multi%20elastic%20search%20head + More information available at https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html 7. Install Redis diff --git a/app/controllers/api/v1/timesheet_entry_controller.rb b/app/controllers/api/v1/timesheet_entry_controller.rb index 5f48b2e310..9f09bf87dd 100644 --- a/app/controllers/api/v1/timesheet_entry_controller.rb +++ b/app/controllers/api/v1/timesheet_entry_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::V1::TimesheetEntryController < Api::V1::BaseController - include Timesheet - before_action :ensure_project_member def create @@ -11,7 +9,7 @@ def create timesheet_entry.save! render json: { notice: I18n.t("timesheet_entry.create.message"), - entry: timesheet_entry.formatted_entry + entry: timesheet_entry.snippet } end diff --git a/app/controllers/concerns/error_handler.rb b/app/controllers/concerns/error_handler.rb index a1810bfc68..10474a7c42 100644 --- a/app/controllers/concerns/error_handler.rb +++ b/app/controllers/concerns/error_handler.rb @@ -64,9 +64,9 @@ def record_invalid(exception) respond_to do |format| format.json { render json: { - errors: exception.record.errors.full_messages.first, - notice: I18n.t("client.update.failure.message") - }, + errors: exception.record.errors.full_messages.first, + notice: I18n.t("client.update.failure.message") + }, status: :unprocessable_entity } format.html { render file: "public/422.html", status: :unprocessable_entity, layout: false, alert: message } diff --git a/app/controllers/concerns/timesheet.rb b/app/controllers/concerns/timesheet.rb deleted file mode 100644 index c70aa3fc9f..0000000000 --- a/app/controllers/concerns/timesheet.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Timesheet - extend ActiveSupport::Concern - - def formatted_entries_by_date(timesheet_entries) - entries = {} - timesheet_entries.map do |entry| - entries[entry.work_date] ||= [] - entries[entry.work_date] << entry.formatted_entry - end - entries - end -end diff --git a/app/controllers/internal_api/v1/addresses_controller.rb b/app/controllers/internal_api/v1/addresses_controller.rb index be41369e8b..3f846ed8b1 100644 --- a/app/controllers/internal_api/v1/addresses_controller.rb +++ b/app/controllers/internal_api/v1/addresses_controller.rb @@ -37,7 +37,7 @@ def set_addressable end def address - @address ||= Address.find(params[:id]) + @_address ||= Address.find(params[:id]) end def address_params diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index 6301865ba0..6936bf065f 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -3,11 +3,9 @@ class InternalApi::V1::GenerateInvoiceController < InternalApi::V1::ApplicationController def index authorize client, policy_class: GenerateInvoicePolicy - render :index, locals: { - filter_options:, - new_line_item_entries:, - total_new_line_items: new_line_item_entries.total_count - }, status: :ok + render :index, + locals: GenerateInvoice::NewLineItemsService.process(client, params), + status: :ok end private @@ -15,53 +13,4 @@ def index def client @_client ||= Client.find_by(id: params[:client_id]) end - - def project - @_project ||= client.projects.kept.pluck(:id).uniq - end - - # Sending team members list for filter dropdown options - def filter_options - user_ids = TimesheetEntry.where(project_id: project).pluck(:user_id).uniq - @_filter_options ||= { - team_members: User.where(id: user_ids) - } - end - - def search_term - @search_term ||= (params[:search_term].present?) ? params[:search_term] : "*" - end - - # merging selected_entries from params with ids already present in other invoice line items - def filtered_ids - invoice_line_item_timesheet_entry_ids = InvoiceLineItem.joins(:timesheet_entry) - .where(timesheet_entries: { bill_status: "unbilled" }) - .pluck(:timesheet_entry_id).uniq - selected_entries = (params[:selected_entries].present?) ? params[:selected_entries] : [] - filtered_ids = (selected_entries + invoice_line_item_timesheet_entry_ids).uniq - end - - def unselected_time_entries_filter - { id: { not: filtered_ids } } - end - - def project_filter - { project_id: project } - end - - def unbilled_status_filter - { bill_status: "unbilled" } - end - - def new_line_item_entries - default_filter = project_filter.merge(unselected_time_entries_filter) - bill_status_filter = default_filter.merge(unbilled_status_filter) - where_clause = bill_status_filter.merge(TimeEntries::Filters.process(params)) - @_new_line_item_entries ||= TimesheetEntry.search( - search_term, - fields: [:note, :user_name], - match: :text_middle, - where: where_clause, - includes: [:user, { project: :client } ]) - end end diff --git a/app/controllers/internal_api/v1/invoices_controller.rb b/app/controllers/internal_api/v1/invoices_controller.rb index 394ff66b17..f8f96ef028 100644 --- a/app/controllers/internal_api/v1/invoices_controller.rb +++ b/app/controllers/internal_api/v1/invoices_controller.rb @@ -4,8 +4,6 @@ class InternalApi::V1::InvoicesController < InternalApi::V1::ApplicationControll before_action :load_client, only: [:create, :update] after_action :ensure_time_entries_billed, only: [:send_invoice] - include UtilityFunctions - def index authorize Invoice pagy, invoices = pagy(invoices_query, items_param: :invoices_per_page) @@ -19,14 +17,14 @@ def index invoices:, recently_updated_invoices:, pagy: pagy_metadata(pagy), - summary: calc_overdue_and_outstanding_and_draft_amount(invoices_query) + summary: current_company.overdue_and_outstanding_and_draft_amount } end def create authorize Invoice render :create, locals: { - invoice: @client.invoices.create!(invoice_params), + invoice: current_company.invoices.create!(invoice_params), client: @client } end @@ -61,6 +59,7 @@ def destroy def send_invoice authorize invoice + invoice.sending! invoice.send_to_email( subject: invoice_email_params[:subject], message: invoice_email_params[:message], @@ -112,38 +111,7 @@ def invoices_query def from_to_date(from_to) if from_to - range_from_timeframe(from_to[:date_range], from_to[:from], from_to[:to]) - end - end - - def calc_overdue_and_outstanding_and_draft_amount(invoices) - # TODO: Need to write similar method to current_company.overdue_and_outstanding_and_draft_amount - currency = current_company.base_currency - sent_amount = 0 - viewed_amount = 0 - overdue_amount = 0 - draft_amount = 0 - - invoices.each do |invoice| - case invoice.status - when "sent" - sent_amount = sent_amount + invoice.amount - when "viewed" - viewed_amount = viewed_amount + invoice.amount - when "overdue" - overdue_amount = overdue_amount + invoice.amount - when "draft" - draft_amount = draft_amount + invoice.amount - end + DateRangeService.new(timeframe: from_to[:date_range], from: from_to[:from], to: from_to[:to]).process end - - outstanding_amount = sent_amount + viewed_amount + overdue_amount - - { - overdue_amount:, - outstanding_amount:, - draft_amount:, - currency: - } end end diff --git a/app/controllers/internal_api/v1/payments/providers_controller.rb b/app/controllers/internal_api/v1/payments/providers_controller.rb index a154fec5dc..9412fdcbd6 100644 --- a/app/controllers/internal_api/v1/payments/providers_controller.rb +++ b/app/controllers/internal_api/v1/payments/providers_controller.rb @@ -21,7 +21,7 @@ def update private def payments_provider - current_company.payments_providers.find(params[:id]) + @_payments_provider ||= current_company.payments_providers.find(params[:id]) end def provider_params diff --git a/app/controllers/internal_api/v1/projects/search_controller.rb b/app/controllers/internal_api/v1/projects/search_controller.rb index a95a336cbb..f0bb17af66 100644 --- a/app/controllers/internal_api/v1/projects/search_controller.rb +++ b/app/controllers/internal_api/v1/projects/search_controller.rb @@ -13,7 +13,7 @@ def index private def search_term - @search_term ||= (params[:search_term].present?) ? params[:search_term] : "" + @_search_term ||= (params[:search_term].present?) ? params[:search_term] : "" end def client_list diff --git a/app/controllers/internal_api/v1/projects_controller.rb b/app/controllers/internal_api/v1/projects_controller.rb index e8e8961259..0b83a989a3 100644 --- a/app/controllers/internal_api/v1/projects_controller.rb +++ b/app/controllers/internal_api/v1/projects_controller.rb @@ -34,11 +34,11 @@ def destroy private def current_company_clients - @_current_company_clients = current_company.clients.kept + @_current_company_clients ||= current_company.clients.kept end def current_company_users - @_current_company_users = current_company.employments.joins(:user) + @_current_company_users ||= current_company.employments.joins(:user) .select("users.id as id, users.first_name as first_name, users.last_name as last_name") end diff --git a/app/controllers/internal_api/v1/reports/accounts_aging_controller.rb b/app/controllers/internal_api/v1/reports/accounts_aging_controller.rb new file mode 100644 index 0000000000..7cb754f60b --- /dev/null +++ b/app/controllers/internal_api/v1/reports/accounts_aging_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class InternalApi::V1::Reports::AccountsAgingController < InternalApi::V1::ApplicationController + def index + authorize :report + render :index, locals: Reports::AccountsAging::FetchOverdueAmount.process(current_company), status: :ok + end +end diff --git a/app/controllers/internal_api/v1/reports/client_revenues_controller.rb b/app/controllers/internal_api/v1/reports/client_revenues_controller.rb index 2f88c7cf0c..18cd27b616 100644 --- a/app/controllers/internal_api/v1/reports/client_revenues_controller.rb +++ b/app/controllers/internal_api/v1/reports/client_revenues_controller.rb @@ -1,4 +1,3 @@ - # frozen_string_literal: true class InternalApi::V1::Reports::ClientRevenuesController < InternalApi::V1::ApplicationController @@ -26,7 +25,7 @@ def summary end def current_clients - @current_clients ||= client_ids_params.blank? ? + @_current_clients ||= client_ids_params.blank? ? current_company.clients : current_company.clients.where(id: client_ids_params) end diff --git a/app/controllers/internal_api/v1/reports/time_entries_controller.rb b/app/controllers/internal_api/v1/reports/time_entries_controller.rb index 669e342e3c..7624c980a4 100644 --- a/app/controllers/internal_api/v1/reports/time_entries_controller.rb +++ b/app/controllers/internal_api/v1/reports/time_entries_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class InternalApi::V1::Reports::TimeEntriesController < InternalApi::V1::ApplicationController - include Timesheet - def index authorize :report render :index, locals: { reports:, filter_options: }, status: :ok @@ -22,19 +20,22 @@ def download private def filter_options - @_filter_options ||= { clients: current_company.clients, team_members: current_company.users } + @_filter_options ||= { + clients: current_company.clients.order(:name), + team_members: current_company.users.order(:first_name) + } end def reports default_filter = current_company_filter.merge(this_month_filter) where_clause = default_filter.merge(TimeEntries::Filters.process(params)) group_by_clause = Reports::TimeEntries::GroupBy.process(params["group_by"]) - search_result = TimesheetEntry.search( where: where_clause, order: { work_date: :desc }, body_options: group_by_clause, - includes: [:user, { project: :client } ]) + includes: [:user, { project: :client } ] + ) Reports::TimeEntries::Result.process(search_result, params["group_by"]) end diff --git a/app/controllers/internal_api/v1/team_controller.rb b/app/controllers/internal_api/v1/team_controller.rb index 4af38aa15d..f899be36b4 100644 --- a/app/controllers/internal_api/v1/team_controller.rb +++ b/app/controllers/internal_api/v1/team_controller.rb @@ -17,11 +17,10 @@ def index def update authorize employment, policy_class: TeamPolicy - User.transaction do - employment.user.skip_reconfirmation! - employment.user.update!(user_params) - update_company_user_role - end + + Team::UpdateService.new( + user_params:, current_company:, new_role: params[:role], user: employment.user).process + render json: { user: employment.user, notice: I18n.t("team.update.success.message") @@ -40,21 +39,10 @@ def destroy private def employment - @_employment ||= current_company.employments.kept.find_by!(user_id: params[:id]) + @_employment ||= current_company.employments.includes(:user).kept.find_by!(user_id: params[:id]) end def user_params params.permit(policy(:team).permitted_attributes) end - - def update_company_user_role - current_role = current_company_role(employment.user) - - employment.user.remove_role(current_role.name.to_sym, current_company) if current_role.present? - employment.user.add_role(params[:role].downcase.to_sym, current_company) - end - - def current_company_role(user) - user.roles.find_by(resource: current_company) - end end diff --git a/app/controllers/internal_api/v1/team_members/details_controller.rb b/app/controllers/internal_api/v1/team_members/details_controller.rb index dc15af5180..5eb63c2f84 100644 --- a/app/controllers/internal_api/v1/team_members/details_controller.rb +++ b/app/controllers/internal_api/v1/team_members/details_controller.rb @@ -19,7 +19,7 @@ def update private def employment - @employment ||= Employment.find(params[:team_id]) + @_employment ||= Employment.find(params[:team_id]) end def detail_params diff --git a/app/controllers/internal_api/v1/time_tracking_controller.rb b/app/controllers/internal_api/v1/time_tracking_controller.rb index 5956a51724..36dde94518 100644 --- a/app/controllers/internal_api/v1/time_tracking_controller.rb +++ b/app/controllers/internal_api/v1/time_tracking_controller.rb @@ -2,7 +2,6 @@ # TODO: Refactoring -> can be merge with time entries controller class InternalApi::V1::TimeTrackingController < InternalApi::V1::ApplicationController - include Timesheet skip_after_action :verify_authorized def index @@ -21,7 +20,7 @@ def index 1.month.ago.beginning_of_month, 1.month.since.end_of_month ) - entries = formatted_entries_by_date(timesheet_entries) + entries = TimesheetEntriesPresenter.new(timesheet_entries).group_snippets_by_work_date entries[:currentUserRole] = current_user.primary_role current_company render json: { clients:, projects:, entries:, employees: }, status: :ok end diff --git a/app/controllers/internal_api/v1/timesheet_entry/bulk_action_controller.rb b/app/controllers/internal_api/v1/timesheet_entry/bulk_action_controller.rb index c3950cf7c9..38832d87fc 100644 --- a/app/controllers/internal_api/v1/timesheet_entry/bulk_action_controller.rb +++ b/app/controllers/internal_api/v1/timesheet_entry/bulk_action_controller.rb @@ -1,15 +1,13 @@ # frozen_string_literal: true class InternalApi::V1::TimesheetEntry::BulkActionController < InternalApi::V1::ApplicationController - include Timesheet - skip_after_action :verify_authorized, only: [:update, :destroy] after_action :verify_policy_scoped, only: [:update, :destroy] def update timesheet_entries = policy_scope(TimesheetEntry) timesheet_entries.where(id: params[:ids]).update(project_id: params[:project_id]) - entries = formatted_entries_by_date(timesheet_entries) + entries = TimesheetEntriesPresenter.new(timesheet_entries).group_snippets_by_work_date render json: { notice: I18n.t("timesheet_entry.update.message"), entries: }, status: :ok end diff --git a/app/controllers/internal_api/v1/timesheet_entry_controller.rb b/app/controllers/internal_api/v1/timesheet_entry_controller.rb index a4cfbcbc22..fef2bfe186 100644 --- a/app/controllers/internal_api/v1/timesheet_entry_controller.rb +++ b/app/controllers/internal_api/v1/timesheet_entry_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class InternalApi::V1::TimesheetEntryController < InternalApi::V1::ApplicationController - include Timesheet - skip_after_action :verify_authorized, only: [:index] after_action :verify_policy_scoped, only: [:index] @@ -12,7 +10,7 @@ def index params[:from], params[:to] ) - entries = formatted_entries_by_date(timesheet_entries) + entries = TimesheetEntriesPresenter.new(timesheet_entries).group_snippets_by_work_date entries[:currentUserRole] = current_user.primary_role current_company render json: { entries: }, status: :ok end @@ -23,15 +21,16 @@ def create timesheet_entry.user = current_company.users.find(params[:user_id]) render json: { notice: I18n.t("timesheet_entry.create.message"), - entry: timesheet_entry.formatted_entry + entry: timesheet_entry.snippet } if timesheet_entry.save! end def update authorize current_timesheet_entry current_timesheet_entry.project = current_project - render json: { notice: I18n.t("timesheet_entry.update.message"), entry: current_timesheet_entry.formatted_entry }, - status: :ok if current_timesheet_entry.update(timesheet_entry_update_params) + current_timesheet_entry.update!(timesheet_entry_params) + render json: { notice: I18n.t("timesheet_entry.update.message"), entry: current_timesheet_entry.snippet }, + status: :ok end def destroy @@ -52,8 +51,4 @@ def current_timesheet_entry def timesheet_entry_params params.require(:timesheet_entry).permit(:project_id, :duration, :work_date, :note, :bill_status) end - - def timesheet_entry_update_params - params.require(:timesheet_entry).permit(:project_id, :duration, :work_date, :note) - end end diff --git a/app/controllers/internal_api/v1/wise/currencies_controller.rb b/app/controllers/internal_api/v1/wise/currencies_controller.rb index f3d0d80ad2..3d4dc8cd69 100644 --- a/app/controllers/internal_api/v1/wise/currencies_controller.rb +++ b/app/controllers/internal_api/v1/wise/currencies_controller.rb @@ -12,6 +12,6 @@ def index private def wise_currency - @wise_currency ||= Wise::Currency.new + @_wise_currency ||= Wise::Currency.new end end diff --git a/app/controllers/internal_api/v1/wise/recipients_controller.rb b/app/controllers/internal_api/v1/wise/recipients_controller.rb index 5f5f86e2b6..a0f606780f 100644 --- a/app/controllers/internal_api/v1/wise/recipients_controller.rb +++ b/app/controllers/internal_api/v1/wise/recipients_controller.rb @@ -32,7 +32,7 @@ def update private def wise_recipient - @wise_recipient ||= Wise::Recipient.new + @_wise_recipient ||= Wise::Recipient.new end def recipient_params diff --git a/app/controllers/internal_api/v1/wise_controller.rb b/app/controllers/internal_api/v1/wise_controller.rb index e38e8694ee..236268040e 100644 --- a/app/controllers/internal_api/v1/wise_controller.rb +++ b/app/controllers/internal_api/v1/wise_controller.rb @@ -20,7 +20,7 @@ def validate_account_details private def wise - @wise ||= Wise::Api.new + @_wise ||= Wise::Api.new end def bank_requirement_params diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb deleted file mode 100644 index d18930d026..0000000000 --- a/app/helpers/dashboard_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -module DashboardHelper -end diff --git a/app/helpers/internal_api/v1/workspaces_helper.rb b/app/helpers/internal_api/v1/workspaces_helper.rb deleted file mode 100644 index 7728bd9ef3..0000000000 --- a/app/helpers/internal_api/v1/workspaces_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -module InternalApi::V1::WorkspacesHelper -end diff --git a/app/helpers/time_trackings_helper.rb b/app/helpers/time_trackings_helper.rb deleted file mode 100644 index 0b598faaca..0000000000 --- a/app/helpers/time_trackings_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -module TimeTrackingsHelper -end diff --git a/app/helpers/workspaces_helper.rb b/app/helpers/workspaces_helper.rb deleted file mode 100644 index e6df7c2c0d..0000000000 --- a/app/helpers/workspaces_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -module WorkspacesHelper -end diff --git a/app/javascript/src/StyledComponents/Avatar.tsx b/app/javascript/src/StyledComponents/Avatar.tsx index 2c63a36f78..481b453ad7 100644 --- a/app/javascript/src/StyledComponents/Avatar.tsx +++ b/app/javascript/src/StyledComponents/Avatar.tsx @@ -17,17 +17,22 @@ const Avatar = ({ name = "", classNameImg = "", classNameInitials = "", - classNameInitialsWrapper = "" + classNameInitialsWrapper = "", }: AvatarProps) => { const [initials, setInitials] = useState(null); - const DEFAULT_STYLE_IMAGE = "inline-block md:h-10 md:w-10 h-5 w-5 rounded-full"; - const DEFAULT_STYLE_INITIALS = "md:text-xl text-xs md:font-medium font-light leading-none text-white"; - const DEFAULT_STYLE_INITIALS_WRAPPER = "inline-flex md:h-10 md:w-10 h-6 w-6 rounded-full items-center justify-center bg-gray-500"; + const DEFAULT_STYLE_IMAGE = + "inline-block md:h-10 md:w-10 h-5 w-5 rounded-full"; + + const DEFAULT_STYLE_INITIALS = + "md:text-xl text-xs md:font-medium font-light leading-none text-white"; + + const DEFAULT_STYLE_INITIALS_WRAPPER = + "inline-flex md:h-10 md:w-10 h-6 w-6 rounded-full items-center justify-center bg-gray-500"; const getInitials = () => { if (name) { const parts = name.match(/\b(\w)/g); - const initials = parts.join("").slice(0,2); + const initials = parts.join("").slice(0, 2); setInitials(initials.toUpperCase()); } }; @@ -37,12 +42,13 @@ const Avatar = ({ if (url) { return ( profile_pic ); } + if (initials) { return (
@@ -64,9 +70,9 @@ const Avatar = ({ return ( avatar ); }; diff --git a/app/javascript/src/StyledComponents/Badge.tsx b/app/javascript/src/StyledComponents/Badge.tsx index 1f49229ea1..5994e246d3 100644 --- a/app/javascript/src/StyledComponents/Badge.tsx +++ b/app/javascript/src/StyledComponents/Badge.tsx @@ -2,20 +2,21 @@ import React from "react"; import classnames from "classnames"; -const DEFAULT_STYLE = "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold leading-4 tracking-wider"; +const DEFAULT_STYLE = + "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold leading-4 tracking-wider"; type BadgeProps = { - text?: string | number, - color?: string, - bgColor?: string, - className?: string -} + text?: string | number; + color?: string; + bgColor?: string; + className?: string; +}; const Badge = ({ text = "Badge", color = "text-purple-800", bgColor = "bg-purple-100", - className + className, }: BadgeProps) => ( {text} diff --git a/app/javascript/src/StyledComponents/Button.tsx b/app/javascript/src/StyledComponents/Button.tsx index 3fc5b2b6c6..559f5a26c3 100644 --- a/app/javascript/src/StyledComponents/Button.tsx +++ b/app/javascript/src/StyledComponents/Button.tsx @@ -6,10 +6,13 @@ const DEFAULT_STYLE = "rounded text-center"; const PRIMARY = "bg-miru-han-purple-1000 hover:bg-miru-han-purple-600 text-white border border-miru-han-purple-1000 hover:border-miru-han-purple-600"; -const PRIMARY_DISABLED = "bg-miru-gray-1000 text-white border border-miru-gray-1000"; + +const PRIMARY_DISABLED = + "bg-miru-gray-1000 text-white border border-miru-gray-1000"; const SECONDARY = "bg-transparent hover:bg-miru-gray-1000 text-miru-han-purple-1000 border border-miru-han-purple-1000"; + const SECONDARY_DISABLED = "bg-transparent text-miru-dark-purple-200 border border-miru-dark-purple-200"; @@ -34,7 +37,7 @@ type ButtonProps = { const BUTTON_STYLES = { primary: "primary", secondary: "secondary", - ternary: "ternary" + ternary: "ternary", }; const SIZES = { small: "small", medium: "medium", large: "large" }; @@ -45,7 +48,7 @@ const Button = ({ className = "", fullWidth = false, onClick, - children + children, }: ButtonProps) => ( + +
+ + + ); +}; + +interface Iprops { + searchAction: (val) => any; // eslint-disable-line + SearchDataRow; +} + +export default AutoSearch; diff --git a/app/javascript/src/common/ChartBar/index.tsx b/app/javascript/src/common/ChartBar/index.tsx index 85bc7f8cd4..d5017fcb4a 100644 --- a/app/javascript/src/common/ChartBar/index.tsx +++ b/app/javascript/src/common/ChartBar/index.tsx @@ -5,53 +5,62 @@ import ReactTooltip from "react-tooltip"; import { IChartBarGraph, ISingleClient } from "./interface"; -const Client = ({ element, totalMinutes, index }:ISingleClient) => { - const chartColor = ["miru-chart-green", "miru-chart-blue", "miru-chart-pink", "miru-chart-orange"]; - const chartColorIndex = index%4; +const Client = ({ element, totalMinutes, index }: ISingleClient) => { + const chartColor = [ + "miru-chart-green", + "miru-chart-blue", + "miru-chart-pink", + "miru-chart-orange", + ]; + const chartColorIndex = index % 4; const randomColor = chartColor[chartColorIndex]; - const hourPercentage = (element.minutes * 100)/totalMinutes; + const hourPercentage = (element.minutes * 100) / totalMinutes; const divStyle = { - width: `${hourPercentage}%` + width: `${hourPercentage}%`, }; return (
- +

{element.name}

-

{minToHHMM(element.minutes)}

+

{minToHHMM(element.minutes)}

+ />
); }; -const GetClientBar = ({ data, totalMinutes }:IChartBarGraph) => ( +const GetClientBar = ({ data, totalMinutes }: IChartBarGraph) => ( -

- TOTAL HOURS: {minToHHMM(totalMinutes)} +

+ TOTAL HOURS:{" "} + {minToHHMM(totalMinutes)}

-
- {data.map((element, index) => ) - } +
+ {data.map((element, index) => ( + + ))}
-
- - 0 - - - {minToHHMM(totalMinutes)} - +
+ 0 + {minToHHMM(totalMinutes)}
); diff --git a/app/javascript/src/common/ChartBar/interface.ts b/app/javascript/src/common/ChartBar/interface.ts index 0d979781f3..b2b456934a 100644 --- a/app/javascript/src/common/ChartBar/interface.ts +++ b/app/javascript/src/common/ChartBar/interface.ts @@ -3,7 +3,7 @@ interface ClientArray { } export interface IChartBar extends ClientArray { - handleSelectChange: any + handleSelectChange: any; totalMinutes: number; } diff --git a/app/javascript/src/common/CustomCheckbox.tsx b/app/javascript/src/common/CustomCheckbox.tsx index b3b09149cb..37f7c3f93b 100644 --- a/app/javascript/src/common/CustomCheckbox.tsx +++ b/app/javascript/src/common/CustomCheckbox.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React from "react"; import classnames from "classnames"; @@ -8,43 +8,49 @@ const CustomCheckbox = ({ checkboxValue, id, handleCheck, - name="", - wrapperClassName="", - labelClassName="" + name = "", + wrapperClassName = "", + labelClassName = "", }) => (
-
+
-
+
- +
- {text !== "" && ( -