diff --git a/src/Entities/Interfaces/TimeEntryInterface.ts b/src/Entities/Interfaces/TimeEntryInterface.ts new file mode 100644 index 0000000..a703035 --- /dev/null +++ b/src/Entities/Interfaces/TimeEntryInterface.ts @@ -0,0 +1,10 @@ +interface TimeEntryInterface { + id?: number, + projectId: number, + taskId: number, + date: string, + notes: string, + isRunning?: boolean +} + +export default TimeEntryInterface \ No newline at end of file diff --git a/src/Entities/ProjectCollection.ts b/src/Entities/ProjectCollection.ts index 531b118..d6df367 100644 --- a/src/Entities/ProjectCollection.ts +++ b/src/Entities/ProjectCollection.ts @@ -37,4 +37,6 @@ class ProjectCollection { }) return project } -} \ No newline at end of file +} + +export default ProjectCollection diff --git a/src/UseCases/Commands/PunchTime.ts b/src/UseCases/Commands/PunchTime.ts index a02564f..6380a86 100644 --- a/src/UseCases/Commands/PunchTime.ts +++ b/src/UseCases/Commands/PunchTime.ts @@ -1,44 +1,84 @@ import * as vscode from "vscode"; -import Harvest from "../../Entities/Harvest" -import UserInterface from "../../Entities/Interfaces/UserInterface"; -import User from "../../Entities/User" -import getUser from "../getUser"; +import TaskInterface from "../../Entities/Interfaces/TaskInterface"; +import TimeEntryInterface from "../../Entities/Interfaces/TimeEntryInterface"; +import Project from "../../Entities/Project"; +import ProjectCollection from "../../Entities/ProjectCollection"; +import saveNewTimeEntry from "../saveNewTimeEntry"; +import SetupHarvest from "../SetupHarvest" + function PunchTime (context: vscode.ExtensionContext): vscode.Disposable { return vscode.commands.registerCommand('harvest-vscode.punchTime', async () => { - const harvest = new Harvest() - let user = new User() - - const accountId: string = context.globalState.get('accountId') || '' - const accessToken: string = context.globalState.get('accessToken') || '' - - if (!accountId || !accessToken) { - vscode.window.showErrorMessage('Run "Harvest: Login" Command before trying to puch time') + let projectCollection = new ProjectCollection() + const isHarvestSetup = await SetupHarvest(context) + if (!isHarvestSetup) return + + const projectNames = projectCollection.elements.map((p: Project) => { + return p.name + }) + + const selectedProjectName = await vscode.window.showQuickPick(projectNames, { + ignoreFocusOut: true, + placeHolder: 'Choost a Project' + }) + + if (!selectedProjectName) { + vscode.window.showWarningMessage('Must select a project to push time') return } - harvest.accountId = accountId - harvest.accessToken = accessToken + const selectedProject = projectCollection.findByName(selectedProjectName) - if (!user.id) { - let userProps: UserInterface - try { - userProps = await getUser() - } catch (err) { - console.log(err) - vscode.window.showErrorMessage('Could not retrieve user data from Harvest') - return - } - if (!userProps.id) { - vscode.window.showErrorMessage('Could not retrieve user data from Harvest') - return - } - user.destructor() - user = new User(userProps) - vscode.window.showInformationMessage('Successfully authenticated with Harvest') + const taskNames = selectedProject?.tasks.map((t: TaskInterface) => { + return t.name + }) + + if (!taskNames) { + vscode.window.showWarningMessage('No tasks defined for this project.') + return } - console.log(user) + const selectedTaskName = await vscode.window.showQuickPick(taskNames, { + ignoreFocusOut: true, + placeHolder: 'Choost a Task' + }) + + if (!selectedTaskName) { + vscode.window.showWarningMessage('Must select a task to push time') + return + } + + const selectedTask = selectedProject?.tasks.find((t: TaskInterface) => { + return t.name === selectedTaskName + }) + + // console.log(selectedProject?.tasks[0]) + // console.log(selectedProject?.tasks[1]) + // console.log(selectedProject?.tasks[2]) + console.log(selectedTask) + + const notes = await vscode.window.showInputBox({ + ignoreFocusOut: true, + placeHolder: 'Notes' + }) + + if (!notes) { + vscode.window.showWarningMessage('Must add notes to puch time.') + return + } + + const newTimeEntry: TimeEntryInterface = { + projectId: selectedProject!.id, + taskId: selectedTask!.id, + date: new Date().toISOString(), + notes: notes + } + + console.log(newTimeEntry) + + const saveNewTimeEntryResponse = await saveNewTimeEntry(newTimeEntry) + + console.log(saveNewTimeEntryResponse) }) } diff --git a/src/UseCases/SetupHarvest.ts b/src/UseCases/SetupHarvest.ts new file mode 100644 index 0000000..fd7fce2 --- /dev/null +++ b/src/UseCases/SetupHarvest.ts @@ -0,0 +1,63 @@ +import * as vscode from "vscode" +import Harvest from "../Entities/Harvest" +import ProjectInterface from "../Entities/Interfaces/ProjectInterface" +import UserInterface from "../Entities/Interfaces/UserInterface" +import Project from "../Entities/Project" +import ProjectCollection from "../Entities/ProjectCollection" +import User from "../Entities/User" +import getProjectsAssignments from "./getProjectsAssignments" +import getUser from "./getUser" + +async function SetupHarvest (context: vscode.ExtensionContext): Promise { + const harvest = new Harvest() + let projectCollection = new ProjectCollection() + let user = new User() + + const accountId: string = context.globalState.get('accountId') || '' + const accessToken: string = context.globalState.get('accessToken') || '' + + if (!accountId || !accessToken) { + vscode.window.showErrorMessage('Run "Harvest: Login" Command before trying to puch time') + return false + } + + harvest.accountId = accountId + harvest.accessToken = accessToken + + if (!user.id) { + vscode.window.showInformationMessage('Authenticating with Harvest') + let userProps: UserInterface + try { + userProps = await getUser() + } catch (err) { + console.log(err) + vscode.window.showErrorMessage('Could not retrieve user data from Harvest') + return false + } + if (!userProps.id) { + vscode.window.showErrorMessage('Could not retrieve user data from Harvest') + return false + } + user.destructor() + user = new User(userProps) + } + + if (projectCollection.elements.length <= 0) { + vscode.window.showInformationMessage('Getting Projects from Harvest') + try { + let projectsData = await getProjectsAssignments() + const projects = projectsData.map((p: ProjectInterface) => { + return new Project(p) + }) + projectCollection.addMany(projects) + } catch (err) { + console.log(err) + vscode.window.showErrorMessage('Could not retrieve uer projects') + return false + } + } + + return true +} + +export default SetupHarvest diff --git a/src/UseCases/getProjectsAssignments.ts b/src/UseCases/getProjectsAssignments.ts index ba7dede..124024b 100644 --- a/src/UseCases/getProjectsAssignments.ts +++ b/src/UseCases/getProjectsAssignments.ts @@ -1,36 +1,50 @@ import axios from 'axios' import Harvest from '../Entities/Harvest' -import ErrorMessage from '../Constants/ErrorMessageInterface' -import ErrorMessages from '../Constants/ErrorMessages' import ProjectInterface from '../Entities/Interfaces/ProjectInterface' -const getProjectsAssignments = async (): Promise => { +const getProjectsAssignments = async (): Promise => { const harvest = new Harvest() - let projectsResponse: any - try { - projectsResponse = await axios.get( - 'https://api.harvestapp.com/v2/users/me/project_assignments', - { headers : harvest.headers } - ) - } catch (err) { - console.log(err) - return ErrorMessages[1] - } - const projects = projectsResponse.data.project_assignments.map((p: any) => { - return { - id: p.project.id, - name: p.project.name, - tasks: p.task_assignments.map((t: any) => { - return { - id: t.task.id, - name: t.task.name - } - }) - } + const projectResponses: any = await _getManyProjectPagesFromApi(harvest) + + let projects: ProjectInterface[] = [] + projectResponses.forEach((reponse: any) => { + const projectResponss: ProjectInterface[] = reponse.data.project_assignments.map((p: any) => { + return { + id: p.project.id, + name: p.project.name, + tasks: p.task_assignments.map((t: any) => { + return { + id: t.task.id, + name: t.task.name + } + }) + } + }) + projects = [...projects, ...projectResponss] }) return projects } +let projectPageRresonses: any = [] + +const _getManyProjectPagesFromApi = async (harvest: Harvest, nextPageUrl?: string): Promise => { + let projectsResponse: any + try { + projectsResponse = await axios.get( + nextPageUrl || 'https://api.harvestapp.com/v2/users/me/project_assignments', + { headers : harvest.headers } + ) + + projectPageRresonses.push(projectsResponse) + + if (projectsResponse.data.links.next) await _getManyProjectPagesFromApi(harvest, projectsResponse.data.links.next) + } catch (err) { + console.log(err) + } + + return projectPageRresonses +} + export default getProjectsAssignments \ No newline at end of file diff --git a/src/UseCases/saveNewTimeEntry.ts b/src/UseCases/saveNewTimeEntry.ts new file mode 100644 index 0000000..fd6719d --- /dev/null +++ b/src/UseCases/saveNewTimeEntry.ts @@ -0,0 +1,38 @@ +import axios from 'axios' +import Harvest from '../Entities/Harvest' +import TimeEntryInterface from '../Entities/Interfaces/TimeEntryInterface' + +const saveNewTimeEntry = async (timeEntry: TimeEntryInterface): Promise => { + const harvest = new Harvest() + + const body = { + project_id: timeEntry.projectId, + task_id: timeEntry.taskId, + spent_date: timeEntry.date, + notes: timeEntry.notes + } + + let timeEntryResponse: any + try { + timeEntryResponse = await axios.post( + 'https://api.harvestapp.com/v2/time_entries', + body, + { headers: harvest.headers } + ) + } catch (err) { + console.log(err) + } + + const createdTimeEntry: TimeEntryInterface = { + id: timeEntryResponse.data.id, + projectId: timeEntryResponse.data.project.id, + date: timeEntryResponse.data.spent_date, + taskId: timeEntryResponse.data.task.id, + notes: timeEntryResponse.data.notes, + isRunning: timeEntryResponse.data.is_running + } + + return createdTimeEntry +} + +export default saveNewTimeEntry diff --git a/src/test/core/ProjectsTests.ts b/src/test/core/ProjectsTests.ts index f1ed91f..b211db0 100644 --- a/src/test/core/ProjectsTests.ts +++ b/src/test/core/ProjectsTests.ts @@ -2,11 +2,12 @@ import ErrorMessage from "../../Constants/ErrorMessageInterface" import Harvest from "../../Entities/Harvest" import ProjectInterface from "../../Entities/Interfaces/ProjectInterface" import Project from "../../Entities/Project" +import ProjectCollection from "../../Entities/ProjectCollection" import getProjectsAssignments from '../../UseCases/getProjectsAssignments' import env from '../env' import UnitTest from "../UnitTestInterface" -const projectCreateInstance = async (): Promise => { +const projectCreateInstance = (): boolean => { const input: ProjectInterface = { id: 10203, name: 'Test Project', @@ -53,9 +54,69 @@ const getProjectsAssignmentsFromApi = async (): Promise => { else return false } +const projectCollectionAddOne = (): boolean => { + new ProjectCollection().destructor() + + const input: ProjectInterface = { + id: 10203, + name: 'Test Project', + tasks: [{ + id: 123, + name: 'Test Task' + }] + } + + const expectedOutput: ProjectInterface = { + id: 10203, + name: 'Test Project', + tasks: [{ + id: 123, + name: 'Test Task' + }] + } + + const project = new Project(input) + const collection = new ProjectCollection() + collection.addOne(project) + + if (JSON.stringify(collection.elements[0].props) === JSON.stringify(expectedOutput)) return true + else return false +} + +const projectCollectionAddMany = (): boolean => { + new ProjectCollection().destructor() + + const input: ProjectInterface = { + id: 10203, + name: 'Test Project', + tasks: [{ + id: 123, + name: 'Test Task' + }] + } + + const expectedOutput: ProjectInterface = { + id: 10203, + name: 'Test Project', + tasks: [{ + id: 123, + name: 'Test Task' + }] + } + + const project = new Project(input) + const collection = new ProjectCollection() + collection.addMany([project]) + + if (JSON.stringify(collection.elements[0].props) === JSON.stringify(expectedOutput)) return true + else return false +} + const unitTests: UnitTest[] = [ { name: 'Entity | Project Create Instance', test: projectCreateInstance }, { name: 'Use Case | Get Projects From Api', test: getProjectsAssignmentsFromApi }, + { name: 'Collection | Add One To Project Collection', test: projectCollectionAddOne }, + { name: 'Collection | Add Many To Project Collection', test: projectCollectionAddMany }, ] export default unitTests \ No newline at end of file