Skip to content

Commit ad1d88e

Browse files
ImCesartgriesser
andauthored
feat: add remove project button (#18381)
Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
1 parent 5bf6d95 commit ad1d88e

File tree

8 files changed

+95
-14
lines changed

8 files changed

+95
-14
lines changed

packages/data-context/src/actions/ProjectActions.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { MutationAddProjectArgs, MutationAppCreateConfigFileArgs, SpecType } from '@packages/graphql/src/gen/nxs.gen'
22
import type { FindSpecs, FoundBrowser, FoundSpec, FullConfig, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions } from '@packages/types'
33
import path from 'path'
4-
import type { Maybe } from '../data/coreDataShape'
4+
import type { Maybe, ProjectShape } from '../data/coreDataShape'
55

66
import type { DataContext } from '..'
77

@@ -16,6 +16,7 @@ export interface ProjectApiShape {
1616
initializeProject(args: LaunchArgs, options: OpenProjectLaunchOptions, browsers: FoundBrowser[]): Promise<unknown>
1717
launchProject(browser: FoundBrowser, spec: Cypress.Spec, options: LaunchOpts): void
1818
insertProjectToCache(projectRoot: string): void
19+
removeProjectFromCache(projectRoot: string): void
1920
getProjectRootsFromCache(): Promise<string[]>
2021
clearLatestProjectsCache(): Promise<unknown>
2122
}
@@ -33,6 +34,14 @@ export class ProjectActions {
3334
return
3435
}
3536

37+
private get projects () {
38+
return this.ctx.coreData.app.projects
39+
}
40+
41+
private set projects (projects: ProjectShape[]) {
42+
this.ctx.coreData.app.projects = projects
43+
}
44+
3645
async setActiveProject (projectRoot: string) {
3746
this.ctx.coreData.app.activeProject = {
3847
projectRoot,
@@ -66,14 +75,14 @@ export class ProjectActions {
6675
}
6776

6877
async loadProjects () {
69-
const projectRoots = await this.ctx._apis.projectApi.getProjectRootsFromCache()
78+
const projectRoots = await this.api.getProjectRootsFromCache()
7079

71-
this.ctx.coreData.app.projects = [
72-
...this.ctx?.coreData?.app?.projects,
80+
this.projects = [
81+
...this.projects,
7382
...projectRoots.map((projectRoot) => ({ projectRoot })),
7483
]
7584

76-
return this.ctx.coreData.app.projects
85+
return this.projects
7786
}
7887

7988
async initializeActiveProject () {
@@ -107,10 +116,10 @@ export class ProjectActions {
107116
throw new Error(`Cannot add ${args.path} as a project, it is not a directory`)
108117
}
109118

110-
const found = this.ctx.projectsList.find((x) => x.projectRoot === args.path)
119+
const found = this.projects.find((x) => x.projectRoot === args.path)
111120

112121
if (!found) {
113-
this.ctx.coreData.app.projects.push({ projectRoot: args.path })
122+
this.projects.push({ projectRoot: args.path })
114123
this.api.insertProjectToCache(args.path)
115124
}
116125

@@ -136,8 +145,15 @@ export class ProjectActions {
136145
return this.api.launchProject(browser, spec, {})
137146
}
138147

139-
removeProject () {
140-
//
148+
removeProject (projectRoot: string) {
149+
const found = this.ctx.projectsList.find((x) => x.projectRoot === projectRoot)
150+
151+
if (!found) {
152+
throw new Error(`Cannot remove ${projectRoot}, it is not a known project`)
153+
}
154+
155+
this.projects = this.projects.filter((project) => project.projectRoot !== projectRoot)
156+
this.api.removeProjectFromCache(projectRoot)
141157
}
142158

143159
syncProjects () {

packages/frontend-shared/cypress/support/mock-graphql/stubgql-Mutation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ export const stubMutation: MaybeResolver<Mutation> = {
3535

3636
return {}
3737
},
38+
removeProject (source, args, ctx) {
39+
ctx.app.projects = ctx.app.projects.filter((p) => p.projectRoot !== args.path)
40+
41+
return ctx.app
42+
},
3843
}

packages/graphql/schemas/schema.graphql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ type Mutation {
297297
"""Set the current navigation item"""
298298
navigationMenuSetItem(type: NavItem!): NavigationMenu
299299

300+
"""Remove project from projects array and cache"""
301+
removeProject(path: String!): App!
302+
300303
"""Set active project to run tests on"""
301304
setActiveProject(path: String!): App!
302305

packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,19 @@ export const mutation = mutationType({
170170
},
171171
})
172172

173+
t.nonNull.field('removeProject', {
174+
type: 'App',
175+
description: 'Remove project from projects array and cache',
176+
args: {
177+
path: nonNull(stringArg()),
178+
},
179+
async resolve (_root, args, ctx) {
180+
await ctx.actions.project.removeProject(args.path)
181+
182+
return ctx.appData
183+
},
184+
})
185+
173186
t.nonNull.field('setActiveProject', {
174187
type: 'App',
175188
description: 'Set active project to run tests on',

packages/launchpad/src/global/GlobalPage.vue

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
v-for="project in filteredProjects"
1717
:key="project.id"
1818
:gql="project"
19+
@removeProject="handleRemoveProject"
1920
/>
2021
</div>
2122
</template>
@@ -28,13 +29,13 @@
2829
</template>
2930

3031
<script setup lang="ts">
31-
import { computed, ref, Ref } from 'vue'
32+
import { computed, ref } from 'vue'
3233
import { gql, useMutation } from '@urql/vue'
3334
import WelcomeGuide from './WelcomeGuide.vue'
3435
import GlobalProjectCard from './GlobalProjectCard.vue'
3536
import GlobalPageHeader from './GlobalPageHeader.vue'
3637
import GlobalEmpty from './GlobalEmpty.vue'
37-
import { GlobalPageFragment, GlobalPage_AddProjectDocument } from '../generated/graphql'
38+
import { GlobalPageFragment, GlobalPage_AddProjectDocument, GlobalPage_RemoveProjectDocument } from '../generated/graphql'
3839
3940
gql`
4041
mutation GlobalPage_addProject($path: String!, $open: Boolean = true) {
@@ -59,12 +60,28 @@ fragment GlobalPage on App {
5960
}
6061
`
6162
63+
gql`
64+
mutation GlobalPage_RemoveProject($path: String!) {
65+
removeProject(path: $path) {
66+
projects {
67+
id
68+
}
69+
}
70+
}
71+
`
72+
6273
const addProject = useMutation(GlobalPage_AddProjectDocument)
6374
6475
function handleAddProject (path: string) {
6576
addProject.executeMutation({ path })
6677
}
6778
79+
const removeProject = useMutation(GlobalPage_RemoveProjectDocument)
80+
81+
function handleRemoveProject (path: string) {
82+
removeProject.executeMutation({ path })
83+
}
84+
6885
const props = defineProps<{
6986
gql: GlobalPageFragment,
7087
}>()

packages/launchpad/src/global/GlobalProjectCard.spec.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@ import GlobalProjectCard from './GlobalProjectCard.vue'
22
import { GlobalProjectCardFragmentDoc } from '../generated/graphql-test'
33

44
describe('<GlobalProjectCard />', () => {
5-
it('renders', () => {
5+
beforeEach(() => {
6+
const removeProjectSpy = cy.spy().as('removeProjectSpy')
7+
68
cy.mountFragment(GlobalProjectCardFragmentDoc, {
79
render: (gqlValue) => (
810
<div class="p-12 overflow-auto resize-x max-w-600px">
9-
<GlobalProjectCard gql={gqlValue} />
11+
<GlobalProjectCard gql={gqlValue} onRemoveProject={removeProjectSpy} />
1012
</div>
1113
),
1214
})
15+
})
1316

17+
it('renders', () => {
1418
cy.findByText('Some Test Title').should('be.visible')
1519
cy.findByText('/usr/local/dev/projects/some-test-title').should('be.visible')
1620
})
21+
22+
it('emits removeProject event on click', () => {
23+
cy.get('[data-testid=removeProjectButton]')
24+
.click()
25+
.get('@removeProjectSpy').should('have.been.called')
26+
})
1727
})

packages/launchpad/src/global/GlobalProjectCard.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,24 @@
1313
</p>
1414
</button>
1515
</div>
16+
<button
17+
class="h-10"
18+
data-testid="removeProjectButton"
19+
@click="$emit('removeProject', props.gql.projectRoot)"
20+
>
21+
<Icon
22+
icon="ant-design:close-circle-outlined"
23+
width="1.5em"
24+
height="1.5em"
25+
class="text-gray-600"
26+
/>
27+
</button>
1628
</div>
1729
</template>
1830

1931
<script setup lang="ts">
2032
import { gql, useMutation } from '@urql/vue'
33+
import { Icon } from '@iconify/vue'
2134
import { GlobalProjectCardFragment, GlobalProjectCard_SetActiveProjectDocument } from '../generated/graphql'
2235
2336
gql`
@@ -60,6 +73,7 @@ const props = defineProps<{
6073
6174
const emit = defineEmits<{
6275
(event: 'projectSelected', project: GlobalProjectCardFragment): void
76+
(event: 'removeProject', path: string): void
6377
}>()
6478
</script>
6579

packages/server/lib/gui/events.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { openProject } from '../open_project'
3232
import specsUtil from '../util/specs'
3333

3434
import { setDataContext, startGraphQLServer } from '@packages/graphql/src/server'
35-
import { getProjectRoots, insertProject, removeLatestProjects } from '@packages/server/lib/cache'
35+
import { getProjectRoots, insertProject, removeLatestProjects, removeProject } from '@packages/server/lib/cache'
3636
import { checkAuthQuery } from '@packages/graphql/src/stitching/remoteGraphQLCalls'
3737
import type { FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions } from '@packages/types'
3838
import type { EventEmitter } from 'events'
@@ -534,6 +534,9 @@ module.exports = {
534534
insertProjectToCache (projectRoot: string) {
535535
insertProject(projectRoot)
536536
},
537+
removeProjectFromCache (projectRoot: string) {
538+
removeProject(projectRoot)
539+
},
537540
getProjectRootsFromCache () {
538541
return getProjectRoots()
539542
},

0 commit comments

Comments
 (0)