Skip to content

Commit 7ea1c08

Browse files
youngbeom-shin申永范-UX
andauthored
feat(tags): add hardware and sdk tags support (#1340)
* feat(tags): add hardware and sdk tags support * feat(tags): add hardware and sdk tags support * feat(tests): improve ApplicationSpaceSettings tests - Use flushPromises for async updates - Replace component click emits with method calls - Update mock for ElMessageBox - Ensure repo refresh on cloud resource update - Adjust test descriptions for clarity --------- Co-authored-by: 申永范-UX <[email protected]>
1 parent 6e6cbb3 commit 7ea1c08

File tree

12 files changed

+240
-74
lines changed

12 files changed

+240
-74
lines changed

frontend/src/components/__tests__/application_spaces/ApplicationSpaceSettings.spec.js

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { describe, it, expect, beforeEach, vi } from 'vitest'
2-
import { mount } from '@vue/test-utils'
2+
import { mount, flushPromises } from '@vue/test-utils'
33
import ApplicationSpaceSettings from '@/components/application_spaces/ApplicationSpaceSettings.vue'
44
import { ElMessage } from 'element-plus'
55

6-
vi.mock('element-plus', () => ({
7-
ElMessage: {
8-
success: vi.fn(),
9-
warning: vi.fn()
10-
}
11-
}))
6+
vi.mock('element-plus', () => {
7+
const ElMessage = vi.fn()
8+
ElMessage.success = vi.fn()
9+
ElMessage.warning = vi.fn()
10+
const ElMessageBox = vi.fn(() => Promise.resolve())
11+
return { ElMessage, ElMessageBox }
12+
})
1213

1314
vi.mock('../../../packs/useFetchApi', () => ({
1415
default: () => ({
1516
post: () => ({
1617
json: () =>
1718
Promise.resolve({
18-
response: { ok: true },
19+
response: { value: { ok: true, status: 200 } },
1920
data: { value: { msg: 'Success' } },
2021
error: { value: null }
2122
})
@@ -53,14 +54,12 @@ const createWrapper = (props = {}) => {
5354
applicationSpaceDesc: 'Test Description',
5455
default_branch: 'main',
5556
appStatus: 'Running',
56-
sdk: 'gradio', // Add default sdk
5757
...props
5858
},
5959
global: {
6060
mocks: {
6161
$t: (key, params) => {
6262
if (key === 'application_spaces.edit.updateSuccess') return 'Success'
63-
if (key === 'application_spaces.sdkType') return 'SDK Type'
6463
if (key === 'all.unknown') return 'Unknown'
6564
return key
6665
}
@@ -85,21 +84,87 @@ describe('ApplicationSpaceSettings', () => {
8584

8685
it('displays space path correctly', () => {
8786
const pathElements = wrapper.findAll('.bg-gray-50')
88-
expect(pathElements).toHaveLength(2) // SDK type + space path
89-
expect(pathElements[1].text()).toBe('test/application_space') // second element is the space path
87+
expect(pathElements).toHaveLength(1)
88+
expect(pathElements[0].text()).toBe('test/application_space')
9089
})
9190

9291
it('updates application space nickname when button is clicked', async () => {
9392
const wrapper = createWrapper()
9493
await wrapper.setData({ theApplicationSpaceNickname: 'New Name' })
95-
await wrapper.findComponent('[data-test="update-nickname"]').vm.$emit('click')
94+
await wrapper.vm.updateNickname()
95+
await flushPromises()
9696
expect(ElMessage.success).toHaveBeenCalledWith('Success')
9797
})
9898

9999
it('update application space description when button is clicked', async () => {
100100
const wrapper = createWrapper()
101101
await wrapper.setData({ theApplicationSpaceDesc: 'New Description' })
102-
await wrapper.findComponent('[data-test="update-description"]').vm.$emit('click')
102+
await wrapper.vm.updateApplicationSpaceDesc()
103+
await flushPromises()
103104
expect(ElMessage.success).toHaveBeenCalledWith('Success')
104105
})
106+
107+
it('updates cloud resource and refreshes repo', async () => {
108+
const wrapper = createWrapper({ cloudResource: '1' })
109+
await wrapper.setData({ theCloudResource: '2' })
110+
await wrapper.vm.updateApplicationSpaceCloudResource()
111+
expect(ElMessage.success).toHaveBeenCalled()
112+
expect(mockFetchRepoDetail).toHaveBeenCalled()
113+
})
114+
115+
it('updates env and refreshes repo detail', async () => {
116+
const wrapper = createWrapper()
117+
await wrapper.setData({ envJSON: '{"A":"1"}', secretJSON: '{"B":"2"}' })
118+
await wrapper.find('[data-test="update-mcp-env"]').trigger('click')
119+
expect(ElMessage.success).toHaveBeenCalled()
120+
expect(mockFetchRepoDetail).toHaveBeenCalled()
121+
})
122+
123+
it('stops space successfully', async () => {
124+
const wrapper = createWrapper({ appStatus: 'Running' })
125+
const ok = await wrapper.vm.stopSpace()
126+
expect(ok).toBe(true)
127+
expect(ElMessage.success).toHaveBeenCalled()
128+
})
129+
130+
it('restarts space successfully', async () => {
131+
const wrapper = createWrapper({ appStatus: 'Stopped' })
132+
const ok = await wrapper.vm.restartSpace()
133+
expect(ok).toBe(true)
134+
expect(ElMessage.success).toHaveBeenCalled()
135+
})
136+
137+
it('change visibility confirmed by ElMessageBox', async () => {
138+
const wrapper = createWrapper()
139+
await wrapper.vm.changeVisibility('Private')
140+
await flushPromises()
141+
expect(ElMessage.success).toHaveBeenCalled()
142+
expect(mockFetchRepoDetail).toHaveBeenCalled()
143+
})
144+
145+
it('change visibility canceled reverts selection and warns', async () => {
146+
const { ElMessageBox } = await import('element-plus')
147+
ElMessageBox.mockImplementationOnce(() => Promise.reject())
148+
const wrapper = createWrapper()
149+
await wrapper.vm.changeVisibility('Private')
150+
await flushPromises()
151+
expect(ElMessage).toHaveBeenCalled()
152+
})
153+
154+
it('emits showSpaceLogs when error banner link clicked', async () => {
155+
const wrapper = createWrapper({ appStatus: 'DeployFailed' })
156+
await wrapper.find('p.text-brand-700').trigger('click')
157+
expect(wrapper.emitted('showSpaceLogs')).toBeTruthy()
158+
})
159+
160+
it('updates docker variables when variables exist', async () => {
161+
const wrapper = createWrapper({ variables: { FOO: 'bar' } })
162+
await wrapper.setData({ theVariables: { FOO: 'baz' } })
163+
const btn = wrapper.find('[data-test="update-varibles"]')
164+
expect(btn.exists()).toBe(true)
165+
await btn.trigger('click')
166+
expect(ElMessage.success).toHaveBeenCalled()
167+
expect(mockFetchRepoDetail).toHaveBeenCalled()
168+
})
105169
})
170+

frontend/src/components/application_spaces/ApplicationSpaceSettings.vue

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -124,29 +124,6 @@
124124

125125
<el-divider />
126126

127-
<!-- 展示SDK类型 -->
128-
<div class="flex xl:flex-col gap-[32px]">
129-
<div class="w-[380px] sm:w-full flex flex-col">
130-
<div class="text-sm text-gray-700 leading-5 font-medium">
131-
{{ $t('application_spaces.sdkType') }}
132-
</div>
133-
<div class="text-sm text-gray-600 leading-5">
134-
{{ $t('application_spaces.sdkTypeTips') }}
135-
</div>
136-
</div>
137-
<div class="flex flex-col gap-1.5">
138-
<p class="text-gray-700 text-sm">
139-
{{ $t('application_spaces.sdkType') }}
140-
</p>
141-
<div
142-
class="w-[512px] sm:w-full rounded-md bg-gray-50 px-3.5 py-2.5 border">
143-
{{ theSdk || $t('all.unknown') }}
144-
</div>
145-
</div>
146-
</div>
147-
148-
<el-divider />
149-
150127
<!-- 展示英文名 -->
151128
<div class="flex xl:flex-col gap-[32px]">
152129
<div class="w-[380px] sm:w-full flex flex-col">
@@ -226,8 +203,8 @@
226203
</div>
227204

228205
<!-- docker space variables -->
229-
<el-divider v-if="theSdk === 'docker'"/>
230-
<div v-if="theSdk === 'docker'">
206+
<el-divider v-if="theVariables && Object.keys(theVariables).length > 0"/>
207+
<div v-if="theVariables && Object.keys(theVariables).length > 0">
231208
<div class="flex xl:flex-col gap-8">
232209
<div class="w-[380px] sm:w-full flex flex-col">
233210
<div class="text-sm text-gray-700 leading-5 font-medium">
@@ -463,7 +440,6 @@
463440
appStatus: String,
464441
cloudResource: String,
465442
coverImage: String,
466-
sdk: String,
467443
variables: Object
468444
},
469445
@@ -475,7 +451,6 @@
475451
applicationSpacePath: this.path,
476452
theApplicationSpaceNickname: this.applicationSpaceNickname || '',
477453
theApplicationSpaceDesc: this.applicationSpaceDesc || '',
478-
theSdk: this.sdk || '',
479454
theVariables: this.variables || {},
480455
theCloudResource: /^\d+$/.test(this.cloudResource)
481456
? Number(this.cloudResource)

frontend/src/components/shared/HeaderTags.vue

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,46 @@
164164
target="other"
165165
@view-more-targets="viewMoreTargets"
166166
/>
167+
<!-- Hardware -->
168+
<div
169+
v-if="hardwareTags?.length"
170+
class="text-sm font-normal text-gray-700"
171+
>
172+
{{ $t('all.hardware') }}:
173+
</div>
174+
<div
175+
v-for="tag in theHardwareTags.theTags"
176+
class="bg-white text-sm font-normal text-gray-700 px-2 py-1 rounded-sm cursor-pointer flex items-center border border-gray-300 gap-1 hover:bg-gray-50"
177+
>
178+
<SvgIcon name="hardware_icon" />
179+
{{ tag.name }}
180+
</div>
181+
<MoreTags
182+
v-if="theHardwareTags.moreTags"
183+
:num="hardwareTags.length - 3"
184+
target="hardware"
185+
@view-more-targets="viewMoreTargets"
186+
/>
187+
<!-- SDK -->
188+
<div
189+
v-if="sdkTags?.length"
190+
class="text-sm font-normal text-gray-700"
191+
>
192+
{{ $t('all.sdk') }}:
193+
</div>
194+
<div
195+
v-for="tag in theSdkTags.theTags"
196+
class="bg-white text-sm font-normal text-gray-700 px-2 py-1 rounded-sm cursor-pointer flex items-center border border-gray-300 gap-1 hover:bg-gray-50"
197+
>
198+
<SvgIcon name="space_mcp" v-if="tag.name === 'mcp_server'" />
199+
{{ tag.name }}
200+
</div>
201+
<MoreTags
202+
v-if="theSdkTags.moreTags"
203+
:num="sdkTags.length - 3"
204+
target="sdk"
205+
@view-more-targets="viewMoreTargets"
206+
/>
167207
<!-- License -->
168208
<div
169209
v-if="licenseTags?.length"
@@ -177,8 +217,7 @@
177217
class="bg-white text-sm font-normal text-gray-700 px-2 py-1 rounded-2xl cursor-pointer flex items-center border border-gray-300 gap-1 hover:bg-gray-50"
178218
>
179219
<SvgIcon name="repo_header_license_icon" />
180-
License:
181-
<p class="text-gray-700">{{ tag.name }}</p>
220+
{{ tag.name }}
182221
</a>
183222
<MoreTags
184223
v-if="theLicenseTags.moreTags"
@@ -250,6 +289,14 @@
250289
return props.tags.scene_tags
251290
})
252291
292+
const sdkTags = computed(() => {
293+
return props.tags.sdk_tags
294+
})
295+
296+
const hardwareTags = computed(() => {
297+
return props.tags.hardware_tags
298+
})
299+
253300
//先定义一个生产参数的方法
254301
const createTagRefs = (tagType) => {
255302
const moreTags = ref(tagType.value?.length > 3)
@@ -266,6 +313,8 @@
266313
const theProgramLanguageTags = ref(createTagRefs(programLanguageTags))
267314
const theRunmodeTags = ref(createTagRefs(runmodeTags))
268315
const theSceneTags = ref(createTagRefs(sceneTags))
316+
const theSdkTags = ref(createTagRefs(sdkTags))
317+
const theHardwareTags = ref(createTagRefs(hardwareTags))
269318
270319
watch(() => props.tags, () => {
271320
theTaskTags.value = createTagRefs(taskTags)
@@ -277,6 +326,8 @@
277326
theProgramLanguageTags.value = createTagRefs(programLanguageTags)
278327
theRunmodeTags.value = createTagRefs(runmodeTags)
279328
theSceneTags.value = createTagRefs(sceneTags)
329+
theSdkTags.value = createTagRefs(sdkTags)
330+
theHardwareTags.value = createTagRefs(hardwareTags)
280331
})
281332
282333
const tagGroups = {
@@ -315,6 +366,14 @@
315366
scene: {
316367
source: sceneTags,
317368
target: theSceneTags
369+
},
370+
sdk: {
371+
source: sdkTags,
372+
target: theSdkTags
373+
},
374+
hardware: {
375+
source: hardwareTags,
376+
target: theHardwareTags
318377
}
319378
}
320379

frontend/src/components/shared/RepoDetail.vue

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,12 @@
159159
}
160160
161161
const handleRepoTags = (repoData) => {
162-
if (repoData.tags) {
163-
return buildTags(repoData.tags)
162+
// 为 buildTags 函数添加 repoType 信息
163+
const dataWithRepoType = {
164+
...repoData,
165+
repoType: props.repoType
164166
}
165-
166-
if (props.repoType === 'code' && repoData.license) {
167-
return buildTags([
168-
{
169-
name: repoData.license,
170-
category: 'license',
171-
built_in: true
172-
}
173-
])
174-
}
175-
176-
return {}
167+
return buildTags(dataWithRepoType)
177168
}
178169
179170
const getUrlParams = () => {

frontend/src/components/shared/RepoHeader.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@
270270

271271
<!-- repo tags -->
272272
<HeaderTags
273-
v-if="repoType === 'model' || repoType === 'dataset' || repoType === 'code' || repoType === 'mcp'"
273+
v-if="repoType === 'model' || repoType === 'dataset' || repoType === 'code' || repoType === 'mcp' || repoType === 'space'"
274274
:tags="tags"
275275
:prefix="`${repoType}s/`" />
276276
</template>

frontend/src/locales/en_js/all.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const all = {
1717
programLanguage: "Programming Language",
1818
runmode: "Runmode",
1919
scene: "Scene",
20+
sdk: "SDK",
21+
hardware: "Hardware",
2022
others: "Others",
2123
contributors: "Contributors",
2224
historyCommits: "History commits",

frontend/src/locales/en_js/application_spaces.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ export const application_spaces = {
66
nameTips: "Applied to the space path, cannot be changed after creation",
77
nickname: "Space alias",
88
namespaceName: "Space name",
9-
sdkType: "SDK Type",
10-
sdkTypeTips: "SDK type used by the application space, cannot be changed after creation",
119
desc: "Space description",
1210
stoppedDesc: "This Space has been paused by its owner.",
1311
sleepingDesc: "This Space has entered sleeping state due to no operation for a long time.",

frontend/src/locales/zh_hant_js/all.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const all = {
1717
programLanguage: "程式語言",
1818
runmode: "執行模式",
1919
scene: "場景",
20+
sdk: "SDK",
21+
hardware: "雲資源",
2022
others: "其他",
2123
contributors: "貢獻者",
2224
historyCommits: "歷史提交",

frontend/src/locales/zh_hant_js/application_spaces.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ export const application_spaces = {
66
nameTips: "應用於空間路徑,創建後不可更改",
77
nickname: "應用空間別名",
88
namespaceName: "命名空間/應用空間名稱",
9-
sdkType: "SDK類型",
10-
sdkTypeTips: "應用空間使用的SDK類型,創建後不可更改",
119
desc: "應用空間介紹",
1210
stoppedDesc: "此空間已被其所有者暫停。",
1311
sleepingDesc: "由於長時間未操作,該空間已進入休眠狀態。",

frontend/src/locales/zh_js/all.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const all = {
1717
programLanguage: "编程语言",
1818
runmode: "运行模式",
1919
scene: "场景",
20+
sdk: "SDK",
21+
hardware: "云资源",
2022
others: "其他",
2123
contributors: "贡献者",
2224
historyCommits: "历史提交",

0 commit comments

Comments
 (0)