Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions app/schedule/syncGitAgents.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
module.exports = (app) => {
const autoSyncInterval = app.config.agentMarket?.autoSyncInterval;

module.exports = () => {
return {
schedule: {
interval: autoSyncInterval || '1h',
cron: '0 0 * * * *', // 每小时整点执行
type: 'worker', // 仅由单个 worker 执行,避免多 worker 并发冲突
immediate: false, // 应用启动后等待到达周期再执行
disable: !autoSyncInterval || autoSyncInterval === '0',
},
// 定时轮询同步所有已配置 GitLab 仓库的 Agent 插件
async task(ctx) {
Expand Down
12 changes: 11 additions & 1 deletion app/service/skills.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,19 @@ class SkillsService extends Service {
await SkillsItem.increment('downloads', {
by: 1,
where: { slug: value, is_delete: 0 },
// Do not bump updated_at — downloads must not reorder "recent" sort.
// 不触发 updated_at 变更,下载量增加不应影响最近更新时间与排序
silent: true,
attributes: {
updated_at: this.app.Sequelize.col('updated_at'),
},
});
if (this.skillCache) {
const target =
this.skillCache.bySlug?.[value] || this.skillCache.byInstallKey?.[value];
if (target) {
target.downloads = (target.downloads || 0) + 1;
}
}
} catch (error) {
this.ctx.logger.warn(
`[skills] increment downloads failed for ${value}: ${error.message}`
Expand Down
4 changes: 2 additions & 2 deletions app/web/pages/agents/detail/AgentDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ const AgentDetailContent: React.FC<AgentDetailContentProps> = ({ name, history }
<div className="agent-sync-meta-item">
<span className="meta-label">
最近刷新时间
<Tooltip title="最近一次向 GitLab 发起检查(确认远端是否有新提交)的时间">
<Tooltip title="最近一次向 GitLab 发起检查(确认远端是否有新提交)的时间,定时间隔:1小时">
<QuestionCircleOutlined className="meta-tip-icon" />
</Tooltip>
</span>
Expand All @@ -581,7 +581,7 @@ const AgentDetailContent: React.FC<AgentDetailContentProps> = ({ name, history }
<div className="agent-sync-meta-item">
<span className="meta-label">
最近同步代码时间
<Tooltip title="最近一次成功拉取新代码变动并完成同步入库的时间">
<Tooltip title="最近一次成功拉取新代码变动并完成同步入库的时间,定时间隔:1小时">
<QuestionCircleOutlined className="meta-tip-icon" />
</Tooltip>
</span>
Expand Down
6 changes: 6 additions & 0 deletions app/web/pages/skills/detail/SkillDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ const SkillDetailContent: React.FC<SkillDetailContentProps> = ({
}
};

// 点击下载仅递增下载量,不更新最近更新时间
const handleDownload = () => {
setDetail((prev) => (prev ? { ...prev, downloads: (prev.downloads || 0) + 1 } : prev));
};

useEffect(() => {
setUiSelectedFilePath('');
setSelectedFilePath('');
Expand Down Expand Up @@ -312,6 +317,7 @@ const SkillDetailContent: React.FC<SkillDetailContentProps> = ({
agentTerminalCommand={agentTerminalCommand}
manualDownloadUrl={manualDownloadUrl}
contributor={detail?.contributor || ''}
onDownload={handleDownload}
/>
) : null}
</div>
Expand Down
7 changes: 6 additions & 1 deletion app/web/pages/skills/detail/components/SkillInstallPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface SkillInstallPanelProps {
agentTerminalCommand: string;
manualDownloadUrl: string;
contributor?: string;
onDownload?: () => void;
}

const renderTerminalCommand = (command: string, copyMessage: string) => (
Expand Down Expand Up @@ -77,6 +78,7 @@ export const SkillInstallPanel: React.FC<SkillInstallPanelProps> = ({
agentTerminalCommand,
manualDownloadUrl,
contributor = '',
onDownload,
}) => (
<aside className="detail-right-sidebar">
<section className="install-panel">
Expand Down Expand Up @@ -179,7 +181,10 @@ export const SkillInstallPanel: React.FC<SkillInstallPanelProps> = ({
<Button
type="default"
className="download-btn"
onClick={() => safeOpenUrl(manualDownloadUrl)}
onClick={() => {
onDownload?.();
safeOpenUrl(manualDownloadUrl);
}}
>
<DetailIcon src={downloadIcon} className="is-download" />
下载 .zip
Expand Down
1 change: 0 additions & 1 deletion config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ module.exports = (app) => {
storageDir: process.env.AGENT_MARKET_STORAGE_DIR || '/data/doraemon/agent-market',
gitlabToken: process.env.GITLAB_TOKEN || '',
gitlabHostWhitelist: ['gitlab.prod.dtstack.cn'],
autoSyncInterval: process.env.AGENT_MARKET_AUTO_SYNC_INTERVAL || '1h',
};

exports.middleware = ['access'];
Expand Down
13 changes: 13 additions & 0 deletions test/agent-detail-layout.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,16 @@ test('Agent 详情页提供当前原始 ZIP 下载入口(图标与 hover 提
assert.match(style, /\.agent-side-download-btn/);
assert.doesNotMatch(component, /agent-archive-download/);
});

test('Agent 详情页展示刷新和同步代码时间的 hover 提示包含定时间隔', () => {
const { component } = readDetailFiles();

assert.match(
component,
/Tooltip title="最近一次向 GitLab 发起检查(确认远端是否有新提交)的时间,定时间隔:1小时"/
);
assert.match(
component,
/Tooltip title="最近一次成功拉取新代码变动并完成同步入库的时间,定时间隔:1小时"/
);
});
36 changes: 5 additions & 31 deletions test/agent-schedule-sync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,16 @@ const assert = require('node:assert/strict');

const syncScheduleFactory = require('../app/schedule/syncGitAgents');

test('syncGitAgents 定时任务默认配置 1h 且工作在 worker 模式', () => {
const mockApp = {
config: {
agentMarket: {
autoSyncInterval: '1h',
},
},
};
const scheduleDef = syncScheduleFactory(mockApp);
assert.equal(scheduleDef.schedule.interval, '1h');
test('syncGitAgents 定时任务配置每小时整点 cron 且工作在 worker 模式', () => {
const scheduleDef = syncScheduleFactory();
assert.equal(scheduleDef.schedule.cron, '0 0 * * * *');
assert.equal(scheduleDef.schedule.type, 'worker');
assert.equal(scheduleDef.schedule.disable, false);
});

test('syncGitAgents 支持禁用定时任务', () => {
const mockApp = {
config: {
agentMarket: {
autoSyncInterval: '',
},
},
};
const scheduleDef = syncScheduleFactory(mockApp);
assert.equal(scheduleDef.schedule.disable, true);
assert.equal(scheduleDef.schedule.immediate, false);
});

test('syncGitAgents 定时任务调用 service.agents.syncAllGitAgents', async () => {
let called = false;
const mockApp = {
config: {
agentMarket: {
autoSyncInterval: '5m',
},
},
};
const scheduleDef = syncScheduleFactory(mockApp);
const scheduleDef = syncScheduleFactory();
const mockCtx = {
logger: {
info() {},
Expand Down
56 changes: 56 additions & 0 deletions test/skills-increment-downloads.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const test = require('node:test');
const assert = require('node:assert/strict');

const SkillsService = require('../app/service/skills');

test('incrementDownloads increments downloads and preserves updated_at', async () => {
let incrementCalledWith = null;

const mockSkillsItem = {
increment: async (field, options) => {
incrementCalledWith = { field, options };
return [1];
},
};

const mockApp = {
model: {
SkillsItem: mockSkillsItem,
},
Sequelize: {
col: (colName) => `COL(${colName})`,
},
};

const mockCtx = {
logger: {
warn: () => {},
},
};

const service = Object.create(SkillsService.prototype);
service.app = mockApp;
service.ctx = mockCtx;
service.ensureStorageReady = async () => {};
service.skillCache = {
bySlug: {
'test-skill': {
slug: 'test-skill',
downloads: 3,
updatedAt: '2026-10-20T10:00:00.000Z',
},
},
};

await service.incrementDownloads('test-skill');

assert.equal(incrementCalledWith.field, 'downloads');
assert.equal(incrementCalledWith.options.by, 1);
assert.deepEqual(incrementCalledWith.options.where, { slug: 'test-skill', is_delete: 0 });
assert.equal(incrementCalledWith.options.silent, true);
assert.equal(incrementCalledWith.options.attributes.updated_at, 'COL(updated_at)');

// 验证内存缓存中的下载量已递增,且 updatedAt 保持不变
assert.equal(service.skillCache.bySlug['test-skill'].downloads, 4);
assert.equal(service.skillCache.bySlug['test-skill'].updatedAt, '2026-10-20T10:00:00.000Z');
});
Loading