Merge pull request 'Adds support for log-line groups' (#3337) from Mai-Lapyst/forgejo:actions-add-logline-groups into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3337 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
		
				commit
				
					
						d0f378d719
					
				
			
		
					 2 changed files with 161 additions and 44 deletions
				
			
		
							
								
								
									
										105
									
								
								web_src/js/components/RepoActionView.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								web_src/js/components/RepoActionView.test.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
import {mount, flushPromises} from '@vue/test-utils';
 | 
			
		||||
import RepoActionView from './RepoActionView.vue';
 | 
			
		||||
 | 
			
		||||
test('processes ##[group] and ##[endgroup]', async () => {
 | 
			
		||||
  Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
 | 
			
		||||
  vi.spyOn(global, 'fetch').mockImplementation((url, opts) => {
 | 
			
		||||
    const artifacts_value = {
 | 
			
		||||
      artifacts: [],
 | 
			
		||||
    };
 | 
			
		||||
    const stepsLog_value = [
 | 
			
		||||
      {
 | 
			
		||||
        step: 0,
 | 
			
		||||
        cursor: 0,
 | 
			
		||||
        lines: [
 | 
			
		||||
          {index: 1, message: '##[group]Test group', timestamp: 0},
 | 
			
		||||
          {index: 2, message: 'A test line', timestamp: 0},
 | 
			
		||||
          {index: 3, message: '##[endgroup]', timestamp: 0},
 | 
			
		||||
          {index: 4, message: 'A line outside the group', timestamp: 0},
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
    const jobs_value = {
 | 
			
		||||
      state: {
 | 
			
		||||
        run: {
 | 
			
		||||
          status: 'success',
 | 
			
		||||
          commit: {
 | 
			
		||||
            pusher: {},
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        currentJob: {
 | 
			
		||||
          steps: [
 | 
			
		||||
            {
 | 
			
		||||
              summary: 'Test Job',
 | 
			
		||||
              duration: '1s',
 | 
			
		||||
              status: 'success',
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      logs: {
 | 
			
		||||
        stepsLog: opts.body?.includes('"cursor":null') ? stepsLog_value : [],
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Promise.resolve({
 | 
			
		||||
      ok: true,
 | 
			
		||||
      json: vi.fn().mockResolvedValue(
 | 
			
		||||
        url.endsWith('/artifacts') ? artifacts_value : jobs_value,
 | 
			
		||||
      ),
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const wrapper = mount(RepoActionView, {
 | 
			
		||||
    props: {
 | 
			
		||||
      jobIndex: '1',
 | 
			
		||||
      locale: {
 | 
			
		||||
        approve: '',
 | 
			
		||||
        cancel: '',
 | 
			
		||||
        rerun: '',
 | 
			
		||||
        artifactsTitle: '',
 | 
			
		||||
        areYouSure: '',
 | 
			
		||||
        confirmDeleteArtifact: '',
 | 
			
		||||
        rerun_all: '',
 | 
			
		||||
        showTimeStamps: '',
 | 
			
		||||
        showLogSeconds: '',
 | 
			
		||||
        showFullScreen: '',
 | 
			
		||||
        downloadLogs: '',
 | 
			
		||||
        status: {
 | 
			
		||||
          unknown: '',
 | 
			
		||||
          waiting: '',
 | 
			
		||||
          running: '',
 | 
			
		||||
          success: '',
 | 
			
		||||
          failure: '',
 | 
			
		||||
          cancelled: '',
 | 
			
		||||
          skipped: '',
 | 
			
		||||
          blocked: '',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  await flushPromises();
 | 
			
		||||
  await wrapper.get('.job-step-summary').trigger('click');
 | 
			
		||||
  await flushPromises();
 | 
			
		||||
 | 
			
		||||
  // Test if header was loaded correctly
 | 
			
		||||
  expect(wrapper.get('.step-summary-msg').text()).toEqual('Test Job');
 | 
			
		||||
 | 
			
		||||
  // Check if 3 lines where rendered
 | 
			
		||||
  expect(wrapper.findAll('.job-log-line').length).toEqual(3);
 | 
			
		||||
 | 
			
		||||
  // Check if line 1 contains the group header
 | 
			
		||||
  expect(wrapper.get('.job-log-line:nth-of-type(1) > details.log-msg').text()).toEqual('Test group');
 | 
			
		||||
 | 
			
		||||
  // Check if right after the header line exists a log list
 | 
			
		||||
  expect(wrapper.find('.job-log-line:nth-of-type(1) + .job-log-list.hidden').exists()).toBe(true);
 | 
			
		||||
 | 
			
		||||
  // Check if inside the loglist exist exactly one log line
 | 
			
		||||
  expect(wrapper.findAll('.job-log-list > .job-log-line').length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
  // Check if inside the loglist is an logline with our second logline
 | 
			
		||||
  expect(wrapper.get('.job-log-list > .job-log-line > .log-msg').text()).toEqual('A test line');
 | 
			
		||||
 | 
			
		||||
  // Check if after the log list exists another log line
 | 
			
		||||
  expect(wrapper.get('.job-log-list + .job-log-line > .log-msg').text()).toEqual('A line outside the group');
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -110,34 +110,6 @@ const sfc = {
 | 
			
		|||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    // get the active container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
 | 
			
		||||
    getLogsContainer(idx) {
 | 
			
		||||
      const el = this.$refs.logs[idx];
 | 
			
		||||
      return el._stepLogsActiveContainer ?? el;
 | 
			
		||||
    },
 | 
			
		||||
    // begin a log group
 | 
			
		||||
    beginLogGroup(idx) {
 | 
			
		||||
      const el = this.$refs.logs[idx];
 | 
			
		||||
 | 
			
		||||
      const elJobLogGroup = document.createElement('div');
 | 
			
		||||
      elJobLogGroup.classList.add('job-log-group');
 | 
			
		||||
 | 
			
		||||
      const elJobLogGroupSummary = document.createElement('div');
 | 
			
		||||
      elJobLogGroupSummary.classList.add('job-log-group-summary');
 | 
			
		||||
 | 
			
		||||
      const elJobLogList = document.createElement('div');
 | 
			
		||||
      elJobLogList.classList.add('job-log-list');
 | 
			
		||||
 | 
			
		||||
      elJobLogGroup.append(elJobLogGroupSummary);
 | 
			
		||||
      elJobLogGroup.append(elJobLogList);
 | 
			
		||||
      el._stepLogsActiveContainer = elJobLogList;
 | 
			
		||||
    },
 | 
			
		||||
    // end a log group
 | 
			
		||||
    endLogGroup(idx) {
 | 
			
		||||
      const el = this.$refs.logs[idx];
 | 
			
		||||
      el._stepLogsActiveContainer = null;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // show/hide the step logs for a step
 | 
			
		||||
    toggleStepLogs(idx) {
 | 
			
		||||
      this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
 | 
			
		||||
| 
						 | 
				
			
			@ -153,8 +125,18 @@ const sfc = {
 | 
			
		|||
    approveRun() {
 | 
			
		||||
      POST(`${this.run.link}/approve`);
 | 
			
		||||
    },
 | 
			
		||||
    // show/hide the step logs for a group
 | 
			
		||||
    toggleGroupLogs(event) {
 | 
			
		||||
      const line = event.target.parentElement;
 | 
			
		||||
      const list = line.nextSibling;
 | 
			
		||||
      if (event.newState === 'open') {
 | 
			
		||||
        list.classList.remove('hidden');
 | 
			
		||||
      } else {
 | 
			
		||||
        list.classList.add('hidden');
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    createLogLine(line, startTime, stepIndex) {
 | 
			
		||||
    createLogLine(line, startTime, stepIndex, group) {
 | 
			
		||||
      const div = document.createElement('div');
 | 
			
		||||
      div.classList.add('job-log-line');
 | 
			
		||||
      div.setAttribute('id', `jobstep-${stepIndex}-${line.index}`);
 | 
			
		||||
| 
						 | 
				
			
			@ -180,9 +162,19 @@ const sfc = {
 | 
			
		|||
      logTimeSeconds.textContent = `${seconds}s`;
 | 
			
		||||
      toggleElem(logTimeSeconds, this.timeVisible['log-time-seconds']);
 | 
			
		||||
 | 
			
		||||
      const logMessage = document.createElement('span');
 | 
			
		||||
      logMessage.className = 'log-msg';
 | 
			
		||||
      let logMessage = document.createElement('span');
 | 
			
		||||
      logMessage.innerHTML = renderAnsi(line.message);
 | 
			
		||||
      if (group.isHeader) {
 | 
			
		||||
        const details = document.createElement('details');
 | 
			
		||||
        details.addEventListener('toggle', this.toggleGroupLogs);
 | 
			
		||||
        const summary = document.createElement('summary');
 | 
			
		||||
        summary.append(logMessage);
 | 
			
		||||
        details.append(summary);
 | 
			
		||||
        logMessage = details;
 | 
			
		||||
      }
 | 
			
		||||
      logMessage.className = 'log-msg';
 | 
			
		||||
      logMessage.style.paddingLeft = `${group.depth}em`;
 | 
			
		||||
 | 
			
		||||
      div.append(logTimeStamp);
 | 
			
		||||
      div.append(logMessage);
 | 
			
		||||
      div.append(logTimeSeconds);
 | 
			
		||||
| 
						 | 
				
			
			@ -191,10 +183,38 @@ const sfc = {
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    appendLogs(stepIndex, logLines, startTime) {
 | 
			
		||||
      const groupStack = [];
 | 
			
		||||
      const container = this.$refs.logs[stepIndex];
 | 
			
		||||
      for (const line of logLines) {
 | 
			
		||||
        // TODO: group support: ##[group]GroupTitle , ##[endgroup]
 | 
			
		||||
        const el = this.getLogsContainer(stepIndex);
 | 
			
		||||
        el.append(this.createLogLine(line, startTime, stepIndex));
 | 
			
		||||
        const el = groupStack.length > 0 ? groupStack[groupStack.length - 1] : container;
 | 
			
		||||
        const group = {
 | 
			
		||||
          depth: groupStack.length,
 | 
			
		||||
          isHeader: false,
 | 
			
		||||
        };
 | 
			
		||||
        if (line.message.startsWith('##[group]')) {
 | 
			
		||||
          group.isHeader = true;
 | 
			
		||||
 | 
			
		||||
          const logLine = this.createLogLine(
 | 
			
		||||
            {
 | 
			
		||||
              ...line,
 | 
			
		||||
              message: line.message.substring(9),
 | 
			
		||||
            },
 | 
			
		||||
            startTime, stepIndex, group,
 | 
			
		||||
          );
 | 
			
		||||
          logLine.setAttribute('data-group', group.index);
 | 
			
		||||
          el.append(logLine);
 | 
			
		||||
 | 
			
		||||
          const list = document.createElement('div');
 | 
			
		||||
          list.classList.add('job-log-list');
 | 
			
		||||
          list.classList.add('hidden');
 | 
			
		||||
          list.setAttribute('data-group', group.index);
 | 
			
		||||
          groupStack.push(list);
 | 
			
		||||
          el.append(list);
 | 
			
		||||
        } else if (line.message.startsWith('##[endgroup]')) {
 | 
			
		||||
          groupStack.pop();
 | 
			
		||||
        } else {
 | 
			
		||||
          el.append(this.createLogLine(line, startTime, stepIndex, group));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -878,15 +898,7 @@ export function initRepositoryActionView() {
 | 
			
		|||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* TODO: group support
 | 
			
		||||
 | 
			
		||||
.job-log-group {
 | 
			
		||||
 | 
			
		||||
.job-log-list.hidden {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
.job-log-group-summary {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.job-log-list {
 | 
			
		||||
 | 
			
		||||
} */
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue