diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-21 15:21:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-21 15:21:08 +0300 |
commit | e7198b914bf1d6594909e35d3d00d0a0b260f250 (patch) | |
tree | 6fe636fedeae9c679839d5bf27dc091af037e765 /app/assets | |
parent | 3b80f22aba42e3e424de5c3dd15cc11f96aaac65 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
6 files changed, 106 insertions, 23 deletions
diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index bb38d80c1b5..9cbaa02f270 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -42,6 +42,7 @@ export default { key: field.key, label: field.label, sortable: field.sortable || false, + class: field.class || [], }; }); }, diff --git a/app/assets/javascripts/notebook/cells/output/dataframe_util.js b/app/assets/javascripts/notebook/cells/output/dataframe_util.js index 2fdaaced0b9..41149875d6c 100644 --- a/app/assets/javascripts/notebook/cells/output/dataframe_util.js +++ b/app/assets/javascripts/notebook/cells/output/dataframe_util.js @@ -1,5 +1,70 @@ import { sanitize } from '~/lib/dompurify'; +function parseItems(itemIndexes, itemColumns) { + // Fetching items: if the dataframe has a single column index, the table is simple + // 0: tr > th(index0 value) th(column0 value) th(column1 value) + // 1: tr > th(index0 value) th(column0 value) th(column1 value) + // + // But if the dataframe has multiple column indexes, it uses rowspan, and the row below won't have a value for that + // index. + // 0: tr > th(index0 value, rowspan=2) th(index1 value) th(column0 value) th(column1 value) + // 1: tr > th(index1 value) th(column0 value) th(column1 value) + // + // So, when parsing row 1, and the count of <th> elements is less than indexCount, we fill with the first + // values of row 0 + const indexCount = itemIndexes[0].length; + const rowCount = itemIndexes.length; + + const filledIndexes = itemIndexes.map((row, rowIndex) => { + const indexesInRow = row.length; + if (indexesInRow === indexCount) { + return row; + } + return itemIndexes[rowIndex - 1].slice(0, -indexesInRow).concat(row); + }); + + const items = Array(rowCount); + + for (let row = 0; row < rowCount; row += 1) { + items[row] = { + ...Object.fromEntries(filledIndexes[row].map((value, counter) => [`index${counter}`, value])), + ...Object.fromEntries(itemColumns[row].map((value, counter) => [`column${counter}`, value])), + }; + } + return items; +} + +function labelsToFields(labels, isIndex = true) { + return labels.map((label, counter) => ({ + key: isIndex ? `index${counter}` : `column${counter}`, + label, + sortable: true, + class: isIndex ? 'gl-font-weight-bold' : '', + })); +} + +function parseFields(columnAndIndexLabels, indexCount, columnCount) { + // Fetching the labels: if the dataframe has a single column index, it will be in the format: + // thead + // tr + // th(index0 label) th(column0 label) th(column1 label) + // + // If there are multiple index columns, it the header will actually have two rows: + // thead + // tr + // th() th() th(column 0 label) th(column1 label) + // tr + // th(index0 label) th(index1 label) th() th() + + const columnLabels = columnAndIndexLabels[0].slice(-columnCount); + const indexLabels = columnAndIndexLabels[columnAndIndexLabels.length - 1].slice(0, indexCount); + + const indexFields = labelsToFields(indexLabels, true); + const columnFields = labelsToFields(columnLabels, false); + + return [...indexFields, ...columnFields]; +} + /** * Converts a dataframe in the output of a Jupyter Notebook cell to a json object * @@ -13,27 +78,26 @@ export function convertHtmlTableToJson(input, domParser) { if (!htmlDoc) return { fields: [], items: [] }; - const columnNames = [...htmlDoc.querySelectorAll('table > thead th')].map( - (head) => head.innerText, + const columnAndIndexLabels = [...htmlDoc.querySelectorAll('table > thead tr')].map((row) => + [...row.querySelectorAll('th')].map((item) => item.innerText), ); - if (!columnNames) return { fields: [], items: [] }; + if (columnAndIndexLabels.length === 0) return { fields: [], items: [] }; - const itemValues = [...htmlDoc.querySelectorAll('table > tbody > tr')].map((row) => + const tableRows = [...htmlDoc.querySelectorAll('table > tbody > tr')]; + + const itemColumns = tableRows.map((row) => [...row.querySelectorAll('td')].map((item) => item.innerText), ); - return { - fields: columnNames.map((column) => ({ - key: column === '' ? 'index' : column, - label: column, - sortable: true, - })), - items: itemValues.map((values, itemIndex) => ({ - index: itemIndex, - ...Object.fromEntries(values.map((value, index) => [columnNames[index + 1], value])), - })), - }; + const itemIndexes = tableRows.map((row) => + [...row.querySelectorAll('th')].map((item) => item.innerText), + ); + + const fields = parseFields(columnAndIndexLabels, itemIndexes[0].length, itemColumns[0].length); + const items = parseItems(itemIndexes, itemColumns); + + return { fields, items }; } export function isDataframe(output) { diff --git a/app/assets/javascripts/pages/import/phabricator/new/index.js b/app/assets/javascripts/pages/import/phabricator/new/index.js deleted file mode 100644 index 0bb70a7364e..00000000000 --- a/app/assets/javascripts/pages/import/phabricator/new/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import { initNewProjectUrlSelect } from '~/projects/new'; - -initNewProjectUrlSelect(); diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index 1d5ff5eb16f..d46e9983a44 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -158,6 +158,8 @@ export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath( 'user/compliance/license_compliance/index', ); +export const CLUSTER_IMAGE_SCANNING_NAME = s__('ciReport|Cluster Image Scanning'); + export const SCANNER_NAMES_MAP = { SAST: SAST_SHORT_NAME, SAST_IAC: SAST_IAC_NAME, @@ -167,7 +169,8 @@ export const SCANNER_NAMES_MAP = { COVERAGE_FUZZING: COVERAGE_FUZZING_NAME, SECRET_DETECTION: SECRET_DETECTION_NAME, DEPENDENCY_SCANNING: DEPENDENCY_SCANNING_NAME, - BAS: BAS_SHORT_NAME, + BREACH_AND_ATTACK_SIMULATION: BAS_NAME, + CLUSTER_IMAGE_SCANNING: CLUSTER_IMAGE_SCANNING_NAME, GENERIC: s__('ciReport|Manually added'), }; diff --git a/app/assets/javascripts/vue_shared/components/file_finder/index.vue b/app/assets/javascripts/vue_shared/components/file_finder/index.vue index 680f229d5e8..5c892377f40 100644 --- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue +++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue @@ -221,7 +221,12 @@ export default { </script> <template> - <div v-if="visible" class="file-finder-overlay" @mousedown.self="toggle(false)"> + <div + v-if="visible" + data-testid="overlay" + class="file-finder-overlay" + @mousedown.self="toggle(false)" + > <div class="dropdown-menu diff-file-changes file-finder show"> <div :class="{ 'has-value': showClearInputButton }" class="dropdown-input"> <input @@ -231,6 +236,7 @@ export default { type="search" class="dropdown-input-field" autocomplete="off" + data-testid="search-input" @keydown="onKeydown($event)" @keyup="onKeyup($event)" /> @@ -241,6 +247,7 @@ export default { /> <gl-icon name="close" + data-testid="clear-search-input" class="dropdown-input-clear" role="button" :aria-label="__('Clear search input')" diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 361a9ad477b..0e11c04afb1 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -13,6 +13,7 @@ import { } from '@gitlab/ui'; import noAccessSvg from '@gitlab/svgs/dist/illustrations/analytics/no-access.svg'; import * as Sentry from '@sentry/browser'; +import { fetchPolicies } from '~/lib/graphql'; import { s__ } from '~/locale'; import { parseBoolean } from '~/lib/utils/common_utils'; import { getParameterByName, updateHistory, setUrlParams } from '~/lib/utils/url_utility'; @@ -149,7 +150,13 @@ export default { error() { this.setEmptyState(); }, - result() { + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + notifyOnNetworkStatusChange: true, + result(res) { + // need to handle this when the res is loading: true, netWorkStatus: 1, partial: true + if (!res.data) { + return; + } if (isEmpty(this.workItem)) { this.setEmptyState(); } @@ -211,7 +218,7 @@ export default { }, computed: { workItemLoading() { - return this.$apollo.queries.workItem.loading; + return isEmpty(this.workItem) && this.$apollo.queries.workItem.loading; }, workItemType() { return this.workItem.workItemType?.name; @@ -544,7 +551,11 @@ export default { {{ workItemBreadcrumbReference }} </li> </ul> - <div v-else-if="!error" class="gl-mr-auto" data-testid="work-item-type"> + <div + v-else-if="!error && !workItemLoading" + class="gl-mr-auto" + data-testid="work-item-type" + > <work-item-type-icon :work-item-icon-name="workItemIconName" :work-item-type="workItemType && workItemType.toUpperCase()" |