From 2ab42c6cf05481449d703420ff9da51683891c9b Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Mon, 12 Aug 2024 10:34:20 +0300 Subject: [PATCH 01/12] feature: project form and search bar --- client/package.json | 3 +- client/src/App.vue | 5 +- client/src/api/axios.ts | 17 -- client/src/api/userservice.ts | 29 +++ client/src/components.d.ts | 1 + .../src/components/projects/ProjectForm.vue | 106 ++++++++ client/src/main.ts | 2 + client/src/pages/projects/ProjectsView.vue | 58 +++++ client/src/plugins/index.ts | 11 +- client/src/plugins/vuetify.ts | 4 +- client/src/typed-router.d.ts | 1 + client/src/types/types.ts | 5 + client/src/utilities/validators.ts | 36 +++ package.json | 5 + pnpm-lock.yaml | 231 ++++++++++++++++++ 15 files changed, 490 insertions(+), 24 deletions(-) delete mode 100644 client/src/api/axios.ts create mode 100644 client/src/api/userservice.ts create mode 100644 client/src/components/projects/ProjectForm.vue create mode 100644 client/src/pages/projects/ProjectsView.vue create mode 100644 package.json create mode 100644 pnpm-lock.yaml diff --git a/client/package.json b/client/package.json index 4f9b854..2469687 100644 --- a/client/package.json +++ b/client/package.json @@ -12,7 +12,8 @@ "core-js": "^3.37.1", "roboto-fontface": "*", "vue": "^3.4.31", - "vuetify": "^3.6.11" + "vuetify": "^3.6.11", + "vue3-notifier":"1.0.3" }, "devDependencies": { "@babel/types": "^7.24.7", diff --git a/client/src/App.vue b/client/src/App.vue index 6ceb9da..4e30b9d 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,11 +1,14 @@ diff --git a/client/src/api/axios.ts b/client/src/api/axios.ts deleted file mode 100644 index dea7a34..0000000 --- a/client/src/api/axios.ts +++ /dev/null @@ -1,17 +0,0 @@ -import axios, { AxiosInstance } from 'axios'; - -const AuthClient: AxiosInstance = axios.create({ - baseURL: import.meta.env.VITE_APP_ENDPOINT, - timeout: 1000, - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + localStorage.getItem("token"), - }, -}); - -const BaseClient: AxiosInstance = axios.create({ - baseURL: import.meta.env.VITE_APP_ENDPOINT, - timeout: 1000, -}); - -export { AuthClient, BaseClient }; diff --git a/client/src/api/userservice.ts b/client/src/api/userservice.ts new file mode 100644 index 0000000..a888fde --- /dev/null +++ b/client/src/api/userservice.ts @@ -0,0 +1,29 @@ +import axios, { AxiosInstance } from 'axios' +import { Project } from '@/types/types' + +const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyODUzNjM3LCJpYXQiOjE3MjI4NDQzMzcsImp0aSI6ImE3ODgxOGRlZWMxNjRlYjFhYjFkMGVkNDliNzFmMWZjIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.-7sAr7ny0arkuLywbrqkJquo3rSo82DubitVXRb0zp0' +localStorage.setItem('token', token) + +const AuthClient: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_APP_ENDPOINT, + timeout: 1000, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + localStorage.getItem('token'), + }, +}) + +const BaseClient: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_APP_ENDPOINT, + timeout: 1000, +}) + +export async function postProject (project :Project) { + return AuthClient.post('/dashboard/projects/', { project }) +} + +export async function searchProject (searchInput: string) { + await AuthClient.get(`/project/search/${searchInput}`) +} + +export { AuthClient, BaseClient } diff --git a/client/src/components.d.ts b/client/src/components.d.ts index 1898766..0835ee0 100644 --- a/client/src/components.d.ts +++ b/client/src/components.d.ts @@ -9,6 +9,7 @@ declare module 'vue' { export interface GlobalComponents { AppFooter: typeof import('./components/AppFooter.vue')['default'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] + ProjectForm: typeof import('./components/projects/ProjectForm.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] Test: typeof import('./components/test.vue')['default'] diff --git a/client/src/components/projects/ProjectForm.vue b/client/src/components/projects/ProjectForm.vue new file mode 100644 index 0000000..6e353f7 --- /dev/null +++ b/client/src/components/projects/ProjectForm.vue @@ -0,0 +1,106 @@ + + + diff --git a/client/src/main.ts b/client/src/main.ts index c8fc172..0973f50 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -3,6 +3,8 @@ * * Bootstraps Vuetify and other plugins then mounts the App` */ +// Styles +import 'vue3-notifier/style.css' // Plugins import { registerPlugins } from '@/plugins' diff --git a/client/src/pages/projects/ProjectsView.vue b/client/src/pages/projects/ProjectsView.vue new file mode 100644 index 0000000..4c16f69 --- /dev/null +++ b/client/src/pages/projects/ProjectsView.vue @@ -0,0 +1,58 @@ + + + diff --git a/client/src/plugins/index.ts b/client/src/plugins/index.ts index c75aa61..40487bd 100644 --- a/client/src/plugins/index.ts +++ b/client/src/plugins/index.ts @@ -5,16 +5,21 @@ */ // Plugins -import vuetify from './vuetify' +import { $vuetify } from './vuetify' import pinia from '../stores' import router from '../router' +import { useNotifierPlugin } from 'vue3-notifier' // Types import type { App } from 'vue' export function registerPlugins (app: App) { app - .use(vuetify) - .use(router) .use(pinia) + .use($vuetify) + .use(useNotifierPlugin({ + id: 'bottom', + position: 'bottom', + })) + .use(router) } diff --git a/client/src/plugins/vuetify.ts b/client/src/plugins/vuetify.ts index 7652788..787f585 100644 --- a/client/src/plugins/vuetify.ts +++ b/client/src/plugins/vuetify.ts @@ -12,8 +12,8 @@ import 'vuetify/styles' import { createVuetify } from 'vuetify' // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides -export default createVuetify({ +export const $vuetify = createVuetify({ theme: { - defaultTheme: 'dark', + defaultTheme: 'light', }, }) diff --git a/client/src/typed-router.d.ts b/client/src/typed-router.d.ts index 3b75a53..a24a2d5 100644 --- a/client/src/typed-router.d.ts +++ b/client/src/typed-router.d.ts @@ -19,5 +19,6 @@ declare module 'vue-router/auto-routes' { */ export interface RouteNamedMap { '/DashboardView': RouteRecordInfo<'/DashboardView', '/DashboardView', Record, Record>, + '/projects/ProjectsView': RouteRecordInfo<'/projects/ProjectsView', '/projects/ProjectsView', Record, Record>, } } diff --git a/client/src/types/types.ts b/client/src/types/types.ts index e69de29..555f96f 100644 --- a/client/src/types/types.ts +++ b/client/src/types/types.ts @@ -0,0 +1,5 @@ +export type Project = { + title:string, + description:string, + githubRepo:string +} diff --git a/client/src/utilities/validators.ts b/client/src/utilities/validators.ts index e69de29..b6a4c1a 100644 --- a/client/src/utilities/validators.ts +++ b/client/src/utilities/validators.ts @@ -0,0 +1,36 @@ +export const titleRules = [ + (value: string) => { + if (value) return true + return 'You must enter a title.' + }, + (value: string) => { + if (value?.length > 3) return true + return 'Title must be at least 4 character.' + }, + (value: string) => { + if (value?.length < 99) return true + return 'Title must be at most 100 characters.' + }, +] + +export const descriptionRules = [ + (value: string) => { + if (value) return true + return 'You must enter a description.' + }, + (value: string) => { + if (value?.length > 3) return true + return 'Description must be at least 4 character.' + }, + (value: string) => { + if (value?.length < 499) return true + return 'Description must be at most 500 characters.' + }, +] + +export const githubRepoRules = [ + (value: string) => { + if (value?.slice(0, 19) === 'https://github.com/') return true + return 'Github repository link has wrong format.' + }, +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f122a8 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "vue3-notifier": "^1.0.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d4716f6 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,231 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + vue3-notifier: + specifier: ^1.0.3 + version: 1.0.3 + +packages: + + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.25.3': + resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.25.2': + resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} + engines: {node: '>=6.9.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@vue/compiler-core@3.4.37': + resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==} + + '@vue/compiler-dom@3.4.37': + resolution: {integrity: sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==} + + '@vue/compiler-sfc@3.4.37': + resolution: {integrity: sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==} + + '@vue/compiler-ssr@3.4.37': + resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==} + + '@vue/reactivity@3.4.37': + resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==} + + '@vue/runtime-core@3.4.37': + resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==} + + '@vue/runtime-dom@3.4.37': + resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==} + + '@vue/server-renderer@3.4.37': + resolution: {integrity: sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==} + peerDependencies: + vue: 3.4.37 + + '@vue/shared@3.4.37': + resolution: {integrity: sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + entities@5.0.0: + resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} + engines: {node: '>=0.12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + gsap@3.12.5: + resolution: {integrity: sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + vue3-notifier@1.0.3: + resolution: {integrity: sha512-W8cZtq3N7NJLNaP+LDOpABusSyT1zAHEvHFPuGn+Jo/rxHHrbFGD0jar8vDXOmhti7ZqenaTiRz1nKHA/9Cu7g==} + + vue@3.4.37: + resolution: {integrity: sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.24.8': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/parser@7.25.3': + dependencies: + '@babel/types': 7.25.2 + + '@babel/types@7.25.2': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@vue/compiler-core@3.4.37': + dependencies: + '@babel/parser': 7.25.3 + '@vue/shared': 3.4.37 + entities: 5.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + + '@vue/compiler-dom@3.4.37': + dependencies: + '@vue/compiler-core': 3.4.37 + '@vue/shared': 3.4.37 + + '@vue/compiler-sfc@3.4.37': + dependencies: + '@babel/parser': 7.25.3 + '@vue/compiler-core': 3.4.37 + '@vue/compiler-dom': 3.4.37 + '@vue/compiler-ssr': 3.4.37 + '@vue/shared': 3.4.37 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.41 + source-map-js: 1.2.0 + + '@vue/compiler-ssr@3.4.37': + dependencies: + '@vue/compiler-dom': 3.4.37 + '@vue/shared': 3.4.37 + + '@vue/reactivity@3.4.37': + dependencies: + '@vue/shared': 3.4.37 + + '@vue/runtime-core@3.4.37': + dependencies: + '@vue/reactivity': 3.4.37 + '@vue/shared': 3.4.37 + + '@vue/runtime-dom@3.4.37': + dependencies: + '@vue/reactivity': 3.4.37 + '@vue/runtime-core': 3.4.37 + '@vue/shared': 3.4.37 + csstype: 3.1.3 + + '@vue/server-renderer@3.4.37(vue@3.4.37)': + dependencies: + '@vue/compiler-ssr': 3.4.37 + '@vue/shared': 3.4.37 + vue: 3.4.37 + + '@vue/shared@3.4.37': {} + + chalk@5.3.0: {} + + csstype@3.1.3: {} + + entities@5.0.0: {} + + estree-walker@2.0.2: {} + + gsap@3.12.5: {} + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + nanoid@3.3.7: {} + + picocolors@1.0.1: {} + + postcss@8.4.41: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + source-map-js@1.2.0: {} + + to-fast-properties@2.0.0: {} + + vue3-notifier@1.0.3: + dependencies: + chalk: 5.3.0 + gsap: 3.12.5 + vue: 3.4.37 + transitivePeerDependencies: + - typescript + + vue@3.4.37: + dependencies: + '@vue/compiler-dom': 3.4.37 + '@vue/compiler-sfc': 3.4.37 + '@vue/runtime-dom': 3.4.37 + '@vue/server-renderer': 3.4.37(vue@3.4.37) + '@vue/shared': 3.4.37 From 3adece24024b61eb1ffc30b513362f82248117c8 Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Mon, 12 Aug 2024 15:45:18 +0300 Subject: [PATCH 02/12] feature: projects page --- client/src/api/userservice.ts | 6 +- .../src/components/projects/ProjectForm.vue | 26 +++-- client/src/pages/projects/ProjectsView.vue | 102 +++++++++++++++--- client/src/types/types.ts | 4 +- 4 files changed, 111 insertions(+), 27 deletions(-) diff --git a/client/src/api/userservice.ts b/client/src/api/userservice.ts index a888fde..dc88578 100644 --- a/client/src/api/userservice.ts +++ b/client/src/api/userservice.ts @@ -1,9 +1,6 @@ import axios, { AxiosInstance } from 'axios' import { Project } from '@/types/types' -const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyODUzNjM3LCJpYXQiOjE3MjI4NDQzMzcsImp0aSI6ImE3ODgxOGRlZWMxNjRlYjFhYjFkMGVkNDliNzFmMWZjIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.-7sAr7ny0arkuLywbrqkJquo3rSo82DubitVXRb0zp0' -localStorage.setItem('token', token) - const AuthClient: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_APP_ENDPOINT, timeout: 1000, @@ -18,7 +15,8 @@ const BaseClient: AxiosInstance = axios.create({ timeout: 1000, }) -export async function postProject (project :Project) { +export async function postProject (project :Partial) { + console.log(project) return AuthClient.post('/dashboard/projects/', { project }) } diff --git a/client/src/components/projects/ProjectForm.vue b/client/src/components/projects/ProjectForm.vue index 6e353f7..f3de9bd 100644 --- a/client/src/components/projects/ProjectForm.vue +++ b/client/src/components/projects/ProjectForm.vue @@ -5,7 +5,7 @@ @submit.prevent > ( + const project = ref>( { title: '', - description: '', - githubRepo: '', + short_description: '', + repo_link: '', } ) const createProject = async () => { - postProject(state.value) + let projectObject: Partial = { + title: project.value.title, + short_description: project.value.short_description, + } + if (project.value.repo_link !== '') { + projectObject = { + ...projectObject, + repo_link: project.value.repo_link, + } + } + postProject(projectObject) .then((response: any) => { notifier.notify({ title: 'Success', @@ -94,7 +104,7 @@ return { form, - state, + project, githubRepo, titleRules, descriptionRules, diff --git a/client/src/pages/projects/ProjectsView.vue b/client/src/pages/projects/ProjectsView.vue index 4c16f69..261d3f9 100644 --- a/client/src/pages/projects/ProjectsView.vue +++ b/client/src/pages/projects/ProjectsView.vue @@ -16,39 +16,115 @@ - - Search + - + + + + + {{ project.title }} + + View Details + + + + - + + From 7d0884a4f489b8e6af371e2bacf6432f7d11d5f0 Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Wed, 14 Aug 2024 12:27:25 +0300 Subject: [PATCH 04/12] feature: search and get projects --- client/src/api/userservice.ts | 13 ++- client/src/pages/projects/ProjectsView.vue | 121 +++++++++++---------- client/src/types/types.ts | 19 +++- server/test_tracker/views/test_plan.py | 2 +- 4 files changed, 97 insertions(+), 58 deletions(-) diff --git a/client/src/api/userservice.ts b/client/src/api/userservice.ts index dc88578..94e3630 100644 --- a/client/src/api/userservice.ts +++ b/client/src/api/userservice.ts @@ -16,10 +16,21 @@ const BaseClient: AxiosInstance = axios.create({ }) export async function postProject (project :Partial) { - console.log(project) return AuthClient.post('/dashboard/projects/', { project }) } +export async function getProjects (page :number) { + return AuthClient.get('/dashboard/projects/', { + params: { + cursor: page, + }, + }) +} + +// export async function getProjects (page :number) { +// return AuthClient.get(`/dashboard/projects/?cursor=${page}`) +// } + export async function searchProject (searchInput: string) { await AuthClient.get(`/project/search/${searchInput}`) } diff --git a/client/src/pages/projects/ProjectsView.vue b/client/src/pages/projects/ProjectsView.vue index 7b7438e..b88128a 100644 --- a/client/src/pages/projects/ProjectsView.vue +++ b/client/src/pages/projects/ProjectsView.vue @@ -22,12 +22,7 @@ single-line variant="solo" @input="search" - > - - - + /> @@ -53,14 +48,14 @@ > - {{ project.description }} + {{ project.short_description }} + diff --git a/client/public/config.js b/client/public/config.js new file mode 100644 index 0000000..e117e98 --- /dev/null +++ b/client/public/config.js @@ -0,0 +1,3 @@ +window.env = { + SERVER_DOMAIN_NAME_API: 'http://127.0.0.1:8000/api', +} diff --git a/client/src/api/userservice.ts b/client/src/api/userservice.ts index 94e3630..1e8b35f 100644 --- a/client/src/api/userservice.ts +++ b/client/src/api/userservice.ts @@ -2,21 +2,21 @@ import axios, { AxiosInstance } from 'axios' import { Project } from '@/types/types' const AuthClient: AxiosInstance = axios.create({ - baseURL: import.meta.env.VITE_APP_ENDPOINT, + baseURL: window.env.SERVER_DOMAIN_NAME_API, timeout: 1000, headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer ' + localStorage.getItem('token'), + Authorization: 'Bearer ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIzNzI1ODUxLCJpYXQiOjE3MjM3MTY1NTEsImp0aSI6Ijc1NzI1NDVjNGQ3YjQ0ZGZiYzMxODIwN2RhMWRmZTdhIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.FjfyIPg3LVDGZDkcHYjS2MRdav0Xy8TnrloJvTVTLs8', }, }) const BaseClient: AxiosInstance = axios.create({ - baseURL: import.meta.env.VITE_APP_ENDPOINT, + baseURL: window.env.SERVER_DOMAIN_NAME_API, timeout: 1000, }) export async function postProject (project :Partial) { - return AuthClient.post('/dashboard/projects/', { project }) + return AuthClient.post('/dashboard/projects/', project) } export async function getProjects (page :number) { @@ -27,10 +27,6 @@ export async function getProjects (page :number) { }) } -// export async function getProjects (page :number) { -// return AuthClient.get(`/dashboard/projects/?cursor=${page}`) -// } - export async function searchProject (searchInput: string) { await AuthClient.get(`/project/search/${searchInput}`) } diff --git a/client/src/components/projects/ProjectForm.vue b/client/src/components/projects/ProjectForm.vue index f3de9bd..ad0abdb 100644 --- a/client/src/components/projects/ProjectForm.vue +++ b/client/src/components/projects/ProjectForm.vue @@ -6,7 +6,9 @@ > @@ -28,17 +33,18 @@ v-if="githubRepo" v-model="project.repo_link" clearable - label="Github repository" + label="Git repository" :rules="githubRepoRules" /> @@ -55,7 +61,7 @@ name: 'ProjectForm', setup () { - const notifier = useNotifier('bottom') + const notifier = useNotifier('top right') const githubRepo = ref(false) diff --git a/client/src/index.ts b/client/src/index.ts new file mode 100644 index 0000000..5ced3b6 --- /dev/null +++ b/client/src/index.ts @@ -0,0 +1,9 @@ +export {} + +declare global { + interface Window { + env: { + SERVER_DOMAIN_NAME_API: string; + }; + } +} diff --git a/client/src/pages/projects/ProjectsView.vue b/client/src/pages/projects/ProjectsView.vue index 11e87c4..ce226eb 100644 --- a/client/src/pages/projects/ProjectsView.vue +++ b/client/src/pages/projects/ProjectsView.vue @@ -90,42 +90,18 @@ name: 'ProjectsView', setup () { - const notifier = useNotifier('bottom') + const notifier = useNotifier('top right') const page = ref(1) - const projects = ref[]>( - [ - { - title: 'title', - id: 1, - short_description: 'project description', - }, - { - title: 'title', - id: 2, - short_description: 'project description', - }, - { - title: 'title', - id: 3, - short_description: 'project description', - }, - { - title: 'title', - id: 4, - short_description: 'project description', - }, - - ] - ) + const projects = ref([]) const count = ref(5) - const getProjects = async (page: number) => { + const getPage = async (page: number) => { getProjects(page).then((response: any) => { - projects.value = response.body.results - projects.value = response.body.total_count + projects.value = response.data.results + // count.value = response.body.total_count }) .catch((err: any) => { notifier.notify({ @@ -139,13 +115,13 @@ }) } - // onMounted(() => { - // getProjects(page.value) - // }) + onMounted(() => { + getPage(page.value) + }) - // watch(page, (currentValue, oldValue) => { - // getProjects(currentValue) - // }) + watch(page, (currentValue, oldValue) => { + getPage(currentValue) + }) const searchText = ref('') diff --git a/client/src/plugins/index.ts b/client/src/plugins/index.ts index 40487bd..00ae7ca 100644 --- a/client/src/plugins/index.ts +++ b/client/src/plugins/index.ts @@ -18,8 +18,8 @@ export function registerPlugins (app: App) { .use(pinia) .use($vuetify) .use(useNotifierPlugin({ - id: 'bottom', - position: 'bottom', + id: 'top right', + position: 'top right', })) .use(router) } diff --git a/client/src/types/types.ts b/client/src/types/types.ts index 976d092..0e12c9a 100644 --- a/client/src/types/types.ts +++ b/client/src/types/types.ts @@ -10,12 +10,12 @@ export type Project = { modified: string, created: string, activity: Activity[], - // total_test_plan: - // total_requirements_docs: - // total_suites: - // total_test_runs - // incomplete_test_runs_assigned_to_you: - // people_with_the_most_incomplete_test_runs: + total_test_plan: any, + total_requirements_docs: any, + total_suites: any, // update the any to type of each attribute + total_test_runs: any, + incomplete_test_runs_assigned_to_you: any, + people_with_the_most_incomplete_test_runs: any, title:string, repo_link:string, short_description:string, diff --git a/client/src/utilities/validators.ts b/client/src/utilities/validators.ts index b6a4c1a..30bb4ba 100644 --- a/client/src/utilities/validators.ts +++ b/client/src/utilities/validators.ts @@ -30,7 +30,7 @@ export const descriptionRules = [ export const githubRepoRules = [ (value: string) => { - if (value?.slice(0, 19) === 'https://github.com/') return true - return 'Github repository link has wrong format.' + if (value?.slice(0, 19) === 'hgithub.com/') return true + return 'Git repository must contain github keyword.' }, ] From 1d0b80c155a5492831e67b3c2cebd7c285dd7bf0 Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Wed, 21 Aug 2024 17:00:40 +0300 Subject: [PATCH 07/12] fix : backend connection --- client/src/api/userservice.ts | 2 +- .../src/pages/projects/ProjectDetailsView.vue | 17 ++++++++ client/src/pages/projects/ProjectsView.vue | 39 ++++++++++++------- client/src/router/index.ts | 3 +- client/src/typed-router.d.ts | 1 + server/test_tracker/views/project.py | 6 +-- 6 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 client/src/pages/projects/ProjectDetailsView.vue diff --git a/client/src/api/userservice.ts b/client/src/api/userservice.ts index 1e8b35f..613fde4 100644 --- a/client/src/api/userservice.ts +++ b/client/src/api/userservice.ts @@ -6,7 +6,7 @@ const AuthClient: AxiosInstance = axios.create({ timeout: 1000, headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIzNzI1ODUxLCJpYXQiOjE3MjM3MTY1NTEsImp0aSI6Ijc1NzI1NDVjNGQ3YjQ0ZGZiYzMxODIwN2RhMWRmZTdhIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.FjfyIPg3LVDGZDkcHYjS2MRdav0Xy8TnrloJvTVTLs8', + Authorization: 'Bearer ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIzOTk0NzQ5LCJpYXQiOjE3MjM5ODU0NDksImp0aSI6ImRjMWRjMDU1ZTgzZjQ0NGQ4NDU1NTAwMGMyNDUwMTJhIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.OgeODfFg5GxP1QxAAyu2FFlOMx2suAdWvjBtkK5eT0U', }, }) diff --git a/client/src/pages/projects/ProjectDetailsView.vue b/client/src/pages/projects/ProjectDetailsView.vue new file mode 100644 index 0000000..a42f043 --- /dev/null +++ b/client/src/pages/projects/ProjectDetailsView.vue @@ -0,0 +1,17 @@ + + + diff --git a/client/src/pages/projects/ProjectsView.vue b/client/src/pages/projects/ProjectsView.vue index ce226eb..e80b572 100644 --- a/client/src/pages/projects/ProjectsView.vue +++ b/client/src/pages/projects/ProjectsView.vue @@ -2,11 +2,11 @@
- ALL PROJECTS +

PROJECTS

- {{ projectText() }} +

{{ projectText() }}


@@ -16,10 +16,10 @@ @@ -46,12 +46,7 @@ - + @@ -60,9 +55,15 @@ append-icon="mdi-chevron-right" block color="blue" - text="Open Project" variant="tonal" - /> + > + + Open Project + + @@ -85,10 +86,11 @@ import { getProjects, searchProject } from '@/api/userservice' import { useNotifier } from 'vue3-notifier' import { Project } from '@/types/types' + import { RouterLink } from 'vue-router' export default { - name: 'ProjectsView', + setup () { const notifier = useNotifier('top right') @@ -126,8 +128,7 @@ const searchText = ref('') const search = async () => { - const text = searchText.value - searchProject(text) + searchProject(searchText.value) .then((response: any) => { }) .catch((err: any) => { @@ -156,6 +157,7 @@ projects, searchText, projectText, + RouterLink, } }, @@ -166,4 +168,13 @@ ::v-deep .v-icon { color: #2196F3 !important; } +a { + text-decoration: none; + color: inherit; + font-size: 1rem; +} +.mdi-delete { + width: 25px; + height: 25px; +} diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 2f9d03c..70d5a95 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -10,7 +10,8 @@ import { createRouter, createWebHistory } from 'vue-router/auto' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ - { path: '/', component: () => import('@/pages/DashboardView.vue') } + { path: '/', component: () => import('@/pages/DashboardView.vue') }, + { path: '/projects/:projectId', name: 'projectDetails', component: () => import('@/pages/projects/ProjectDetailsView.vue'), props: true }, ], }) diff --git a/client/src/typed-router.d.ts b/client/src/typed-router.d.ts index a24a2d5..1e506be 100644 --- a/client/src/typed-router.d.ts +++ b/client/src/typed-router.d.ts @@ -19,6 +19,7 @@ declare module 'vue-router/auto-routes' { */ export interface RouteNamedMap { '/DashboardView': RouteRecordInfo<'/DashboardView', '/DashboardView', Record, Record>, + '/projects/ProjectDetailsView': RouteRecordInfo<'/projects/ProjectDetailsView', '/projects/ProjectDetailsView', Record, Record>, '/projects/ProjectsView': RouteRecordInfo<'/projects/ProjectsView', '/projects/ProjectsView', Record, Record>, } } diff --git a/server/test_tracker/views/project.py b/server/test_tracker/views/project.py index ea12aab..5e231d7 100644 --- a/server/test_tracker/views/project.py +++ b/server/test_tracker/views/project.py @@ -31,7 +31,7 @@ class ProjectsDetailAPIView(GenericAPIView): """ serializer_class = ProjectsSerializer - permission_classes = (HasProjectAccess,) + # permission_classes = (HasProjectAccess,) def get(self, request: Request, project_id: str) -> Response: """Return a single project based on the given project id""" @@ -130,7 +130,7 @@ class AddMemberToProjectAPIView(GenericAPIView): Add Member to project """ - permission_classes = (HasProjectAccess,) + # permission_classes = (HasProjectAccess,) def put(self, request: Request, project_id: Project, member_id: Member) -> Response: """ @@ -260,7 +260,7 @@ class SearchProjectAPIView(GenericAPIView): """ serializer_class = ProjectsSerializer - permission_classes = (UserIsAuthenticated,) + # permission_classes = (UserIsAuthenticated,) def get(self, request: Request, project_name: str): """ From e3ec58bea07776029987ed0361789224b7ab3c07 Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Thu, 22 Aug 2024 10:35:22 +0300 Subject: [PATCH 08/12] fix: seperate services --- client/src/api/ProjectService.ts | 18 ++++++++++ client/src/api/clients.ts | 15 ++++++++ client/src/api/userservice.ts | 34 ------------------- .../src/components/projects/ProjectForm.vue | 2 +- client/src/pages/projects/ProjectsView.vue | 2 +- 5 files changed, 35 insertions(+), 36 deletions(-) create mode 100644 client/src/api/ProjectService.ts create mode 100644 client/src/api/clients.ts delete mode 100644 client/src/api/userservice.ts diff --git a/client/src/api/ProjectService.ts b/client/src/api/ProjectService.ts new file mode 100644 index 0000000..b1b2c6a --- /dev/null +++ b/client/src/api/ProjectService.ts @@ -0,0 +1,18 @@ +import { AuthClient } from './clients' +import { Project } from '@/types/types' + +export async function postProject (project :Partial) { + return AuthClient.post('/dashboard/projects/', project) +} + +export async function getProjects (page :number) { + return AuthClient.get('/dashboard/projects/', { + params: { + cursor: page, + }, + }) +} + +export async function searchProject (searchInput: string) { + await AuthClient.get(`/project/search/${searchInput}`) +} diff --git a/client/src/api/clients.ts b/client/src/api/clients.ts new file mode 100644 index 0000000..6cf2a74 --- /dev/null +++ b/client/src/api/clients.ts @@ -0,0 +1,15 @@ +import axios, { AxiosInstance } from 'axios' + +export const AuthClient: AxiosInstance = axios.create({ + baseURL: window.env.SERVER_DOMAIN_NAME_API, + timeout: 1000, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIzOTk0NzQ5LCJpYXQiOjE3MjM5ODU0NDksImp0aSI6ImRjMWRjMDU1ZTgzZjQ0NGQ4NDU1NTAwMGMyNDUwMTJhIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.OgeODfFg5GxP1QxAAyu2FFlOMx2suAdWvjBtkK5eT0U', + }, +}) + +export const BaseClient: AxiosInstance = axios.create({ + baseURL: window.env.SERVER_DOMAIN_NAME_API, + timeout: 1000, +}) diff --git a/client/src/api/userservice.ts b/client/src/api/userservice.ts deleted file mode 100644 index 613fde4..0000000 --- a/client/src/api/userservice.ts +++ /dev/null @@ -1,34 +0,0 @@ -import axios, { AxiosInstance } from 'axios' -import { Project } from '@/types/types' - -const AuthClient: AxiosInstance = axios.create({ - baseURL: window.env.SERVER_DOMAIN_NAME_API, - timeout: 1000, - headers: { - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIzOTk0NzQ5LCJpYXQiOjE3MjM5ODU0NDksImp0aSI6ImRjMWRjMDU1ZTgzZjQ0NGQ4NDU1NTAwMGMyNDUwMTJhIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.OgeODfFg5GxP1QxAAyu2FFlOMx2suAdWvjBtkK5eT0U', - }, -}) - -const BaseClient: AxiosInstance = axios.create({ - baseURL: window.env.SERVER_DOMAIN_NAME_API, - timeout: 1000, -}) - -export async function postProject (project :Partial) { - return AuthClient.post('/dashboard/projects/', project) -} - -export async function getProjects (page :number) { - return AuthClient.get('/dashboard/projects/', { - params: { - cursor: page, - }, - }) -} - -export async function searchProject (searchInput: string) { - await AuthClient.get(`/project/search/${searchInput}`) -} - -export { AuthClient, BaseClient } diff --git a/client/src/components/projects/ProjectForm.vue b/client/src/components/projects/ProjectForm.vue index ad0abdb..576caf6 100644 --- a/client/src/components/projects/ProjectForm.vue +++ b/client/src/components/projects/ProjectForm.vue @@ -52,7 +52,7 @@ From 1f316b02e4167f5106184fb955b3c246220f6d4a Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Mon, 26 Aug 2024 09:56:50 +0300 Subject: [PATCH 11/12] feature: types & design updates --- client/src/App.vue | 4 +- client/src/api/{clients.ts => axios.ts} | 2 +- client/src/api/projectService.ts | 4 +- client/src/components.d.ts | 3 - .../src/components/projects/ProjectForm.vue | 9 ++- client/src/components/test.vue | 0 client/src/layouts/README.md | 5 -- client/src/layouts/default.vue | 13 ---- client/src/pages/DashboardView.vue | 6 -- client/src/pages/projects/ProjectsView.vue | 16 +++-- client/src/router/index.ts | 1 - client/src/typed-router.d.ts | 1 - client/src/types/types.ts | 69 ++++++++++++++++--- server/test_tracker/views/member.py | 2 +- server/test_tracker/views/project.py | 14 ++-- server/test_tracker/views/requirement.py | 14 ++-- server/test_tracker/views/test_cases.py | 6 +- server/test_tracker/views/test_plan.py | 12 ++-- server/test_tracker/views/test_run.py | 14 ++-- server/test_tracker/views/test_suites.py | 6 +- 20 files changed, 116 insertions(+), 85 deletions(-) rename client/src/api/{clients.ts => axios.ts} (53%) delete mode 100644 client/src/components/test.vue delete mode 100644 client/src/layouts/README.md delete mode 100644 client/src/layouts/default.vue delete mode 100644 client/src/pages/DashboardView.vue diff --git a/client/src/App.vue b/client/src/App.vue index 8714368..965ca31 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,7 +1,7 @@ diff --git a/client/src/api/clients.ts b/client/src/api/axios.ts similarity index 53% rename from client/src/api/clients.ts rename to client/src/api/axios.ts index 6cf2a74..655090b 100644 --- a/client/src/api/clients.ts +++ b/client/src/api/axios.ts @@ -5,7 +5,7 @@ export const AuthClient: AxiosInstance = axios.create({ timeout: 1000, headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIzOTk0NzQ5LCJpYXQiOjE3MjM5ODU0NDksImp0aSI6ImRjMWRjMDU1ZTgzZjQ0NGQ4NDU1NTAwMGMyNDUwMTJhIiwidXNlcl9pZCI6NCwiZW1haWwiOiJib3VkaWVAYm91ZGllLmNvbSJ9.OgeODfFg5GxP1QxAAyu2FFlOMx2suAdWvjBtkK5eT0U', + Authorization: localStorage.getItem('token'), }, }) diff --git a/client/src/api/projectService.ts b/client/src/api/projectService.ts index b1b2c6a..f0f2880 100644 --- a/client/src/api/projectService.ts +++ b/client/src/api/projectService.ts @@ -1,4 +1,4 @@ -import { AuthClient } from './clients' +import { AuthClient } from './axios' import { Project } from '@/types/types' export async function postProject (project :Partial) { @@ -8,7 +8,7 @@ export async function postProject (project :Partial) { export async function getProjects (page :number) { return AuthClient.get('/dashboard/projects/', { params: { - cursor: page, + page, }, }) } diff --git a/client/src/components.d.ts b/client/src/components.d.ts index 0835ee0..fc89776 100644 --- a/client/src/components.d.ts +++ b/client/src/components.d.ts @@ -7,11 +7,8 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { - AppFooter: typeof import('./components/AppFooter.vue')['default'] - HelloWorld: typeof import('./components/HelloWorld.vue')['default'] ProjectForm: typeof import('./components/projects/ProjectForm.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - Test: typeof import('./components/test.vue')['default'] } } diff --git a/client/src/components/projects/ProjectForm.vue b/client/src/components/projects/ProjectForm.vue index c0fb4eb..c8d9295 100644 --- a/client/src/components/projects/ProjectForm.vue +++ b/client/src/components/projects/ProjectForm.vue @@ -90,21 +90,24 @@ .then((response: any) => { notifier.notify({ title: 'Success', - description: 'Project created Successfully', + description: response.data.message, showProgressBar: true, timeout: 7_000, type: 'success', }) }) .catch((err: any) => { + let description = 'Can not create project' + if (err.response) { + description = err.response.data.detail + } notifier.notify({ title: 'Fail', - description: 'Can not create project', + description, showProgressBar: true, timeout: 7_000, type: 'error', }) - console.error(err) }) } diff --git a/client/src/components/test.vue b/client/src/components/test.vue deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/layouts/README.md b/client/src/layouts/README.md deleted file mode 100644 index 4016af3..0000000 --- a/client/src/layouts/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Layouts - -Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across multiple pages. - -Full documentation for this feature can be found in the Official [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) repository. diff --git a/client/src/layouts/default.vue b/client/src/layouts/default.vue deleted file mode 100644 index da5c129..0000000 --- a/client/src/layouts/default.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/client/src/pages/DashboardView.vue b/client/src/pages/DashboardView.vue deleted file mode 100644 index c9b4d21..0000000 --- a/client/src/pages/DashboardView.vue +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/client/src/pages/projects/ProjectsView.vue b/client/src/pages/projects/ProjectsView.vue index 8699ca9..351e7f6 100644 --- a/client/src/pages/projects/ProjectsView.vue +++ b/client/src/pages/projects/ProjectsView.vue @@ -103,17 +103,20 @@ const getPage = async (page: number) => { getProjects(page).then((response: any) => { projects.value = response.data.results - // count.value = response.body.total_count + count.value = response.data.total_count }) .catch((err: any) => { + let description = 'Can not get projects' + if (err.response) { + description = err.response.data.detail + } notifier.notify({ title: 'Fail', - description: 'Can not get projects', + description, showProgressBar: true, timeout: 7_000, type: 'error', }) - console.error(err) }) } @@ -132,14 +135,17 @@ .then((response: any) => { }) .catch((err: any) => { + let description = 'Can not search for projects' + if (err.response) { + description = err.response.data.detail + } notifier.notify({ title: 'Fail', - description: 'Can not search projects', + description, showProgressBar: true, timeout: 7_000, type: 'error', }) - console.error(err) }) } diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 70d5a95..5c6e1ce 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -10,7 +10,6 @@ import { createRouter, createWebHistory } from 'vue-router/auto' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ - { path: '/', component: () => import('@/pages/DashboardView.vue') }, { path: '/projects/:projectId', name: 'projectDetails', component: () => import('@/pages/projects/ProjectDetailsView.vue'), props: true }, ], }) diff --git a/client/src/typed-router.d.ts b/client/src/typed-router.d.ts index 1e506be..85eff57 100644 --- a/client/src/typed-router.d.ts +++ b/client/src/typed-router.d.ts @@ -18,7 +18,6 @@ declare module 'vue-router/auto-routes' { * Route name map generated by unplugin-vue-router */ export interface RouteNamedMap { - '/DashboardView': RouteRecordInfo<'/DashboardView', '/DashboardView', Record, Record>, '/projects/ProjectDetailsView': RouteRecordInfo<'/projects/ProjectDetailsView', '/projects/ProjectDetailsView', Record, Record>, '/projects/ProjectsView': RouteRecordInfo<'/projects/ProjectsView', '/projects/ProjectsView', Record, Record>, } diff --git a/client/src/types/types.ts b/client/src/types/types.ts index 0e12c9a..a8ce4a8 100644 --- a/client/src/types/types.ts +++ b/client/src/types/types.ts @@ -1,8 +1,59 @@ +enum Status { + NOT_STARTED='not_started', + IN_PROGRESS='in_progress', + COMPLETED='completed', +} + export type Activity = { action: string, date: string, } +export type TestPlan = { + id: number, + modified: string, + created: string, + title: string, + type: string, +} + +export type Requirement = { + id: number, + updated: string, + created: string, + title: string, + requirements: string[], +} + +export type TestSuite = { + id: number, + modified: string, + created: string, + title: string, + number_of_test_cases: number, + test_plan: number, +} + +export type TestRun = { + +} + +export type TestCase = { + id: number, + modified: string, + created: string, + title: string, + testcase_title: string, + requirement: string + last_saved: { + id: number, + full_name: string, + } + test_suite: string, + description: string, + test_steps: string, + expected_result: string, +} export type Project = { id: number, user: string, @@ -10,13 +61,13 @@ export type Project = { modified: string, created: string, activity: Activity[], - total_test_plan: any, - total_requirements_docs: any, - total_suites: any, // update the any to type of each attribute - total_test_runs: any, - incomplete_test_runs_assigned_to_you: any, - people_with_the_most_incomplete_test_runs: any, - title:string, - repo_link:string, - short_description:string, + total_test_plan: TestPlan[], + total_requirements_docs: Requirement[], + total_suites: TestSuite[], + total_test_runs: TestRun[], + incomplete_test_runs_assigned_to_you: TestRun[], + people_with_the_most_incomplete_test_runs: TestRun[], + title: string, + repo_link: string, + short_description: string, } diff --git a/server/test_tracker/views/member.py b/server/test_tracker/views/member.py index 8567a26..ba2f99f 100644 --- a/server/test_tracker/views/member.py +++ b/server/test_tracker/views/member.py @@ -186,7 +186,7 @@ class ProjectMembersAPIView(GenericAPIView): """This class to return all project members""" serializer_class = ProjectTeamSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str) -> Response: """Use this endpoint to get all project members""" diff --git a/server/test_tracker/views/project.py b/server/test_tracker/views/project.py index 5e231d7..44e623d 100644 --- a/server/test_tracker/views/project.py +++ b/server/test_tracker/views/project.py @@ -31,7 +31,7 @@ class ProjectsDetailAPIView(GenericAPIView): """ serializer_class = ProjectsSerializer - # permission_classes = (HasProjectAccess,) + # def get(self, request: Request, project_id: str) -> Response: """Return a single project based on the given project id""" @@ -130,7 +130,7 @@ class AddMemberToProjectAPIView(GenericAPIView): Add Member to project """ - # permission_classes = (HasProjectAccess,) + # def put(self, request: Request, project_id: Project, member_id: Member) -> Response: """ @@ -288,7 +288,7 @@ class AccountMembersNotInProjectAPIView(GenericAPIView): Class to get all account members where members not in project """ - permission_classes = (HasProjectAccess,) + serializer_class = ProjectTeamSerializer def get(self, request: Request, project_id: str): @@ -318,7 +318,7 @@ def get(self, request: Request, project_id: str): class TestSuitesSectionAPIView(GenericAPIView): serializer_class = TestSuiteSectionSerializer - permission_classes = (HasProjectAccess,) + def post(self, request: Request, project_id: str): """Post new section, required fields is [Title,]""" @@ -333,7 +333,7 @@ def post(self, request: Request, project_id: str): class GetTestSuitesSectionsAPIView(GenericAPIView): serializer_class = GetTestSuiteSectionSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, test_suite: str) -> Response: """Get all project test suite sections""" @@ -351,7 +351,7 @@ def get(self, request: Request, project_id: str, test_suite: str) -> Response: class DeleteTestSuiteSectionAPIView(GenericAPIView): """Delete a test suite section by its id.""" - permission_classes = (HasProjectAccess,) + def delete(self, request: Request, project_id: str, section_id: str) -> Response: """Delete a section with given id""" @@ -370,7 +370,7 @@ def delete(self, request: Request, project_id: str, section_id: str) -> Response class AddTestCaseToTestSuiteSectionAPIView(GenericAPIView): """Add a test case to test suite section""" - permission_classes = (HasProjectAccess,) + # serializer_class = AddTestCaseToTestSuiteSectionSerializers def put(self, request: Request, project_id: str): diff --git a/server/test_tracker/views/requirement.py b/server/test_tracker/views/requirement.py index dc3172e..6828092 100644 --- a/server/test_tracker/views/requirement.py +++ b/server/test_tracker/views/requirement.py @@ -32,7 +32,7 @@ class PostNewRequirementDocsAPIView(GenericAPIView): """class project requirement view""" serializer_class = RequirementDocsSerializer - permission_classes = (HasProjectAccess,) + def post(self, request: Request, project_id: str) -> Response: """post a new requirement""" @@ -70,7 +70,7 @@ class GetAllRequirementDocsAPIView(GenericAPIView): """class project requirement view""" serializer_class = RequirementDocsSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str) -> Response: """ @@ -96,7 +96,7 @@ class project requirement view search on project requirements """ serializer_class = RequirementDocsSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, key_word: str) -> Response: """get all requirements for a project""" @@ -121,7 +121,7 @@ class project requirement view """ serializer_class = RequirementDocsSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, requirement_id: str) -> Response: """Use this endpoint to get requirement detail and sub requirements""" @@ -190,7 +190,7 @@ class RequirementAPIView(GenericAPIView): """This class is a sub requirement for project requirements""" serializer_class = RequirementsSerializer - permission_classes = (HasProjectAccess,) + def post(self, request: Request, project_id: str, requirements_id: str) -> Response: """ @@ -269,7 +269,7 @@ class project requirement view """ serializer_class = RequirementsSerializer - permission_classes = (HasProjectAccess,) + def get( self, @@ -371,7 +371,7 @@ class SearchRequirementsInRequirementDocssAPIView(APIView): Use this endpoint to filter any requirement based on title or description """ - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, key_word: str): """ diff --git a/server/test_tracker/views/test_cases.py b/server/test_tracker/views/test_cases.py index 6b1703a..c0fd220 100644 --- a/server/test_tracker/views/test_cases.py +++ b/server/test_tracker/views/test_cases.py @@ -180,7 +180,7 @@ class SearchTestCaseAPIView(GenericAPIView): """ serializer_class = TestCaseSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, key_word: str): """ @@ -201,7 +201,7 @@ class GetAllProjectRequirementsAPIView(GenericAPIView): """Get all of test cases""" serializer_class = RequirementsSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str) -> Response: """Method get to get all of test cases based on the test suite""" @@ -221,7 +221,7 @@ class UpdateTestCaseAfterRunAPIView(GenericAPIView): """This class to update the test case by pass bool field""" serializer_class = UpdateTestCaseAfterRunSerializer - permission_classes = (HasProjectAccess,) + def put(self, request: Request, project_id: str, test_case_id: str) -> Response: """Method put to update the test case by pass bool field""" diff --git a/server/test_tracker/views/test_plan.py b/server/test_tracker/views/test_plan.py index f3038fe..aedf42d 100644 --- a/server/test_tracker/views/test_plan.py +++ b/server/test_tracker/views/test_plan.py @@ -28,7 +28,7 @@ class TestPlansAPIView(GenericAPIView): """Create a test plan.""" serializer_class = TestPlanSerializer - """permission_classes = (HasProjectAccess,)""" + """""" def post(self, request: Request, project_id: str) -> Response: """ @@ -82,7 +82,7 @@ class TestPlansDetailAPIView(GenericAPIView): """This class for [GET, UPDATE, DELETE] test plans methods""" serializer_class = TestPlanDetailSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, test_plan_id: str) -> Response: """Get a test plan from the specified project""" @@ -113,7 +113,7 @@ def delete(self, request: Request, project_id: str, test_plan_id: str) -> Respon class UpdateTestPlanAPIView(GenericAPIView): serializer_class = UpdateTestPlanSerializer - permission_classes = (HasProjectAccess,) + def put(self, request: Request, project_id: str, test_plan_id: str) -> Response: """Update test plan title""" @@ -147,7 +147,7 @@ class PostNewTestPlanContentAreaAPIView(GenericAPIView): """ serializer_class = TestPlanTempsSerializer - permission_classes = (HasProjectAccess,) + def post(self, request: Request, project_id: str, test_plan_id: str) -> Response: """Add custom content area to test plan""" @@ -185,7 +185,7 @@ class TestPlanContentAreaAPIView(GenericAPIView): Delete and get test plan content area based on its title """ - permission_classes = (HasProjectAccess,) + serializer_class = TestPlanTempsSerializer def put( @@ -272,7 +272,7 @@ class SearchTestPlanAPIView(GenericAPIView): """ serializer_class = TestPlanDetailSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, key_word: str): """ diff --git a/server/test_tracker/views/test_run.py b/server/test_tracker/views/test_run.py index af72988..b474cc6 100644 --- a/server/test_tracker/views/test_run.py +++ b/server/test_tracker/views/test_run.py @@ -28,7 +28,7 @@ class TestRunAPIView(GenericAPIView): """Class TestRunAPIView to handle test runs endpoints""" serializer_class = TestRunsSerializer - permission_classes = (HasProjectAccess,) + def post(self, request: Request, project_id: str) -> Response: serializer = self.serializer_class(data=request.data) @@ -89,7 +89,7 @@ class TestRunDetailAPIView(GenericAPIView): """Class TestRunAPIView to handle test runs endpoints""" serializer_class = TestRunsSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, test_run_id: str) -> Response: """ @@ -142,7 +142,7 @@ class SearchOnTestRunAPIView(GenericAPIView): """ serializer_class = TestRunsSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str) -> Response: """ @@ -176,7 +176,7 @@ def get(self, request: Request, project_id: str) -> Response: class LastWeekTestRunReportSheetAPIView(GenericAPIView): - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str) -> Response: """ @@ -227,7 +227,7 @@ def get(self, request: Request, project_id: str) -> Response: class RunAllTestCasesAPIView(GenericAPIView): - permission_classes = (HasProjectAccess,) + serializer_class = TestCaseSerializer def get(self, request: Request, project_id: str, test_run_id: str) -> Response: @@ -286,7 +286,7 @@ def put(self, request: Request, project_id: str, test_run_id: str) -> Response: class SetAssignedUserTestRunAPIView(GenericAPIView): - permission_classes = (HasProjectAccess,) + serializer_class = TestRunsSerializer def put(self, request: Request, project_id: str, test_run_id: str) -> Response: @@ -334,7 +334,7 @@ def get(self, request: Request) -> Response: class CompleteTestRunAPIView(GenericAPIView): """Complete test run after run all test cases.""" - permission_classes = (HasProjectAccess,) + def put(self, request: Request, project_id: str, test_run_id: str) -> Response: """We will use this endpoint to complete the test run after running all test cases""" diff --git a/server/test_tracker/views/test_suites.py b/server/test_tracker/views/test_suites.py index 5e0268c..82d3a07 100644 --- a/server/test_tracker/views/test_suites.py +++ b/server/test_tracker/views/test_suites.py @@ -22,7 +22,7 @@ class TestSuitesAPIView(GenericAPIView): """Create a new test suite""" serializer_class = TestSuitesSerializer - permission_classes = (HasProjectAccess,) + def post(self, request: Request, project_id: str) -> Response: """ @@ -74,7 +74,7 @@ class TestSuitesDetailAPIView(GenericAPIView): """Create a new test suite""" serializer_class = TestSuitesDetailSerializer - permission_classes = (HasProjectAccess,) + def put(self, request: Request, project_id: str, test_suite_id: str) -> Response: """ @@ -138,7 +138,7 @@ class SearchTestSuiteAPIView(GenericAPIView): """ serializer_class = TestSuitesSerializer - permission_classes = (HasProjectAccess,) + def get(self, request: Request, project_id: str, key_word: str): """ From 125543f79e71e0ae5565ad5f73c0d7df2ee4c20d Mon Sep 17 00:00:00 2001 From: abdahmed22 Date: Wed, 28 Aug 2024 10:47:16 +0300 Subject: [PATCH 12/12] feature: add loading to project form --- client/src/App.vue | 4 +-- .../src/components/projects/ProjectForm.vue | 28 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/client/src/App.vue b/client/src/App.vue index 965ca31..d4df927 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,7 +1,7 @@ diff --git a/client/src/components/projects/ProjectForm.vue b/client/src/components/projects/ProjectForm.vue index c8d9295..bf54644 100644 --- a/client/src/components/projects/ProjectForm.vue +++ b/client/src/components/projects/ProjectForm.vue @@ -37,16 +37,18 @@ :rules="githubRepoRules" /> - + + + @@ -67,6 +69,8 @@ const form = ref() + const loading = ref(false) + const project = ref>( { title: '', @@ -76,6 +80,7 @@ ) const createProject = async () => { + loading.value = true let projectObject: Partial = { title: project.value.title, short_description: project.value.short_description, @@ -95,6 +100,7 @@ timeout: 7_000, type: 'success', }) + loading.value = false }) .catch((err: any) => { let description = 'Can not create project' @@ -108,11 +114,13 @@ timeout: 7_000, type: 'error', }) + loading.value = false }) } return { form, + loading, project, githubRepo, titleRules,