mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-01 21:21:50 +08:00
Merge branch 'develop' into e2e
This commit is contained in:
commit
6235a03595
26 changed files with 231 additions and 86 deletions
|
@ -1,6 +1,6 @@
|
|||
env:
|
||||
- DOCKER_COMPOSE_VERSION=1.23.2
|
||||
|
||||
- DOCKER_COMPOSE_VERSION=v2.22.0
|
||||
dist: jammy
|
||||
sudo: required
|
||||
language: ruby
|
||||
addons:
|
||||
|
@ -10,9 +10,9 @@ services:
|
|||
- docker
|
||||
before_install:
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
- make docker
|
||||
- make docker-ci
|
||||
script:
|
||||
- make tests-ci
|
||||
|
|
18
Makefile
18
Makefile
|
@ -24,6 +24,9 @@ heroku:
|
|||
docker:
|
||||
@docker-compose build
|
||||
|
||||
docker-ci:
|
||||
@docker-compose --progress plain build web
|
||||
|
||||
docker-production:
|
||||
@docker-compose -f docker-compose.production.yml build --build-arg BUILD_TIMESTAMP=$(BUILD_TIMESTAMP)
|
||||
|
||||
|
@ -81,10 +84,17 @@ integration-tests:
|
|||
@$(MAKE) rails cmd="bundle exec cucumber"
|
||||
|
||||
tests-ci:
|
||||
@docker-compose run --rm web bash -c "bundle install && yarn install"
|
||||
@docker-compose up -d webpack
|
||||
@docker-compose ps
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false -e MAIL_FROM=MAIL_FROM -e MAIL_REPLYTO=MAIL_REPLYTO -e RAILS_ENV=test -e MAIL_SERVER_URL=localhost:3000 -e ENABLE_RECAPTCHA=false -e ENABLE_USER_CONFIRMATION=false -e ENABLE_USER_REGISTRATION=true -e CORE_API_RATE_LIMIT=1000000 --rm web bash -c "rake db:create && rake db:migrate && yarn install && bundle exec rspec"
|
||||
@docker-compose run --rm web bash -c "bundle install"
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false \
|
||||
-e MAIL_FROM=MAIL_FROM \
|
||||
-e MAIL_REPLYTO=MAIL_REPLYTO \
|
||||
-e RAILS_ENV=test \
|
||||
-e MAIL_SERVER_URL=localhost:3000 \
|
||||
-e ENABLE_RECAPTCHA=false \
|
||||
-e ENABLE_USER_CONFIRMATION=false \
|
||||
-e ENABLE_USER_REGISTRATION=true \
|
||||
-e CORE_API_RATE_LIMIT=1000000 \
|
||||
--rm web bash -c "rake db:create && rake db:migrate && bundle exec rspec ./spec/requests/api/"
|
||||
|
||||
console:
|
||||
@$(MAKE) rails cmd="rails console"
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.29.0.1
|
||||
1.29.1.1
|
||||
|
|
|
@ -430,7 +430,7 @@ var ProjectsIndex = (function() {
|
|||
view_mode: $('.projects-index').data('view-mode'),
|
||||
sort: projectsCurrentSort,
|
||||
search: projectsViewSearch,
|
||||
members: membersFilter,
|
||||
members: membersFilter && membersFilter.map(m => m.value),
|
||||
created_on_from: createdOnFromFilter,
|
||||
created_on_to: createdOnToFilter,
|
||||
folders_search: lookInsideFolders,
|
||||
|
@ -549,6 +549,8 @@ var ProjectsIndex = (function() {
|
|||
var datePicker = $field.data('DateTimePicker');
|
||||
if (datePicker && datePicker.date()) {
|
||||
return datePicker.date()._d.toUTCString();
|
||||
} else if ($field.val()) {
|
||||
return moment($field.val(), $field.data('dateFormat'))._d.toUTCString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -565,8 +567,8 @@ var ProjectsIndex = (function() {
|
|||
let $textFilter = $('#textSearchFilterInput', $projectsFilter);
|
||||
|
||||
function getFilterValues() {
|
||||
createdOnFromFilter = selectDate($createdOnFromFilter) || $createdOnFromFilter.val();
|
||||
createdOnToFilter = selectDate($createdOnToFilter) || $createdOnToFilter.val();
|
||||
createdOnFromFilter = selectDate($createdOnFromFilter);
|
||||
createdOnToFilter = selectDate($createdOnToFilter);
|
||||
membersFilter = dropdownSelector.getData($('.members-filter'));
|
||||
lookInsideFolders = $foldersCB.prop('checked') || '';
|
||||
archivedOnFromFilter = selectDate($archivedOnFromFilter) || $archivedOnFromFilter.val();
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
width: 100%;
|
||||
|
||||
.dataTables_scrollHead {
|
||||
overflow: visible !important;
|
||||
flex-shrink: 0;
|
||||
|
||||
thead {
|
||||
.sci-checkbox-container {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@layer components {
|
||||
|
||||
.sci-label {
|
||||
@apply text-sm font-medium text-sn-grey;
|
||||
@apply text-sm font-medium text-sn-dark-grey;
|
||||
}
|
||||
|
||||
.sci-input-container-v2 {
|
||||
|
|
|
@ -12,7 +12,6 @@ module Api
|
|||
timestamps_filter(
|
||||
@inventory_column.repository_status_items
|
||||
)
|
||||
.repository_status_items
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
|
||||
|
|
|
@ -137,14 +137,13 @@ module ApplicationHelper
|
|||
# and outputs a popover with user information
|
||||
def smart_annotation_filter_users(text, team, base64_encoded_imgs: false)
|
||||
sa_user = /\[\@(.*?)~([0-9a-zA-Z]+)\]/
|
||||
new_text = text.gsub(sa_user) do |el|
|
||||
text.gsub(sa_user) do |el|
|
||||
match = el.match(sa_user)
|
||||
user = User.find_by_id(match[2].base62_decode)
|
||||
next unless user
|
||||
|
||||
popover_for_user_name(user, team, false, false, base64_encoded_imgs)
|
||||
end
|
||||
sanitize_input(new_text)
|
||||
end
|
||||
|
||||
# Generate smart annotation link for one user object
|
||||
|
|
|
@ -210,6 +210,14 @@
|
|||
@publish="publishProtocol"
|
||||
@cancel="closePublishModal"
|
||||
/>
|
||||
<clipboardPasteModal v-if="showClipboardPasteModal"
|
||||
:image="pasteImages"
|
||||
:objects="steps"
|
||||
:objectType="'step'"
|
||||
:selectedObjectId="firstObjectInViewport()"
|
||||
@files="uploadFilesToStep"
|
||||
@cancel="showClipboardPasteModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -221,6 +229,8 @@
|
|||
import Tinymce from '../shared/tinymce.vue'
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
|
||||
import PublishProtocol from './modals/publish_protocol.vue'
|
||||
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
|
||||
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
|
||||
|
||||
import UtilsMixin from '../mixins/utils.js'
|
||||
import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
|
||||
|
@ -234,8 +244,8 @@
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol},
|
||||
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver],
|
||||
components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol, clipboardPasteModal},
|
||||
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
|
||||
computed: {
|
||||
inRepository() {
|
||||
return this.protocol.attributes.in_repository
|
||||
|
@ -245,7 +255,7 @@
|
|||
},
|
||||
urls() {
|
||||
return this.protocol.attributes.urls || {}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -433,6 +443,16 @@
|
|||
$('.my_module-name .view-mode').trigger('click');
|
||||
$('.my_module-name .input-field').focus();
|
||||
}, 300)
|
||||
},
|
||||
uploadFilesToStep(file, stepId) {
|
||||
this.$children.find(child => child.step?.id == stepId).uploadFiles(file);
|
||||
},
|
||||
firstObjectInViewport() {
|
||||
let step = $('.step-container:not(.locked)').toArray().find(element => {
|
||||
const { top, bottom } = element.getBoundingClientRect()
|
||||
return bottom > 0 && top < window.innerHeight
|
||||
})
|
||||
return step ? step.dataset.id : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
@drop.prevent="dropFile"
|
||||
@dragenter.prevent="dragEnter($event)"
|
||||
@dragover.prevent
|
||||
:class="{ 'draging-file': dragingFile, 'editing-name': editingName }"
|
||||
:data-id="step.id"
|
||||
:class="{ 'draging-file': dragingFile, 'editing-name': editingName, 'locked': !urls.update_url }"
|
||||
>
|
||||
<div class="drop-message" @dragleave.prevent="!showFileModal ? dragingFile = false : null">
|
||||
{{ i18n.t('protocols.steps.drop_message', { position: step.attributes.position + 1 }) }}
|
||||
|
@ -134,12 +135,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<deleteStepModal v-if="confirmingDelete" @confirm="deleteStep" @cancel="closeDeleteModal"/>
|
||||
<clipboardPasteModal v-if="showClipboardPasteModal"
|
||||
:parent="step"
|
||||
:image="pasteImages"
|
||||
@files="uploadFiles"
|
||||
@cancel="showClipboardPasteModal = false"
|
||||
/>
|
||||
<ReorderableItemsModal v-if="reordering"
|
||||
:title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })"
|
||||
:items="reorderableElements"
|
||||
|
@ -162,7 +157,6 @@
|
|||
import Checklist from '../shared/content/checklist.vue'
|
||||
import deleteStepModal from './modals/delete_step.vue'
|
||||
import Attachments from '../shared/content/attachments.vue'
|
||||
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
|
||||
import MenuDropdown from '../shared/menu_dropdown.vue'
|
||||
|
||||
|
@ -202,7 +196,6 @@
|
|||
attachmentsReady: false,
|
||||
confirmingDelete: false,
|
||||
showFileModal: false,
|
||||
showClipboardPasteModal: false,
|
||||
showCommentsSidebar: false,
|
||||
dragingFile: false,
|
||||
reordering: false,
|
||||
|
@ -226,7 +219,6 @@
|
|||
StepText,
|
||||
Checklist,
|
||||
deleteStepModal,
|
||||
clipboardPasteModal,
|
||||
Attachments,
|
||||
StorageUsage,
|
||||
ReorderableItemsModal,
|
||||
|
@ -542,10 +534,6 @@
|
|||
rect.right <= (window.innerWidth || $(window).width())
|
||||
);
|
||||
},
|
||||
copyPasteImageModal(pasteImages) {
|
||||
this.pasteImages = pasteImages;
|
||||
this.showClipboardPasteModal = true;
|
||||
},
|
||||
insertElement(element) {
|
||||
let position = element.attributes.position;
|
||||
this.elements = this.elements.map( s => {
|
||||
|
|
|
@ -46,8 +46,10 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const textHeight = this.$refs.textRef.scrollHeight
|
||||
this.expandable = textHeight > 60 // 60px
|
||||
if (this.$refs.textRef) {
|
||||
const textHeight = this.$refs.textRef.scrollHeight
|
||||
this.expandable = textHeight > 60 // 60px
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
@drop.prevent="dropFile"
|
||||
@dragenter.prevent="dragEnter($event)"
|
||||
@dragover.prevent
|
||||
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile }"
|
||||
:data-id="result.id"
|
||||
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile, 'locked': locked }"
|
||||
>
|
||||
<div class="text-xl items-center flex flex-col text-sn-blue h-full justify-center left-0 absolute top-0 w-full"
|
||||
v-if="dragingFile"
|
||||
|
|
|
@ -32,6 +32,14 @@
|
|||
@result:drag_enter="dragEnter"
|
||||
/>
|
||||
</div>
|
||||
<clipboardPasteModal v-if="showClipboardPasteModal"
|
||||
:image="pasteImages"
|
||||
:objects="results"
|
||||
:objectType="'result'"
|
||||
:selectedObjectId="firstObjectInViewport()"
|
||||
@files="uploadFilesToResult"
|
||||
@cancel="showClipboardPasteModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -43,10 +51,13 @@
|
|||
import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
|
||||
import moduleNameObserver from '../mixins/moduleNameObserver';
|
||||
|
||||
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
|
||||
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
|
||||
|
||||
export default {
|
||||
name: 'Results',
|
||||
components: { ResultsToolbar, Result },
|
||||
mixins: [stackableHeadersMixin, moduleNameObserver],
|
||||
components: { ResultsToolbar, Result, clipboardPasteModal },
|
||||
mixins: [stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
|
||||
props: {
|
||||
url: { type: String, required: true },
|
||||
canCreate: { type: String, required: true },
|
||||
|
@ -136,6 +147,16 @@
|
|||
},
|
||||
dragEnter(id) {
|
||||
this.activeDragResult = id;
|
||||
},
|
||||
uploadFilesToResult(file, resultId) {
|
||||
this.$children.find(child => child.result?.id == resultId).uploadFiles(file);
|
||||
},
|
||||
firstObjectInViewport() {
|
||||
let result = $('.result-wrapper:not(.locked)').toArray().find(element => {
|
||||
const { top, bottom } = element.getBoundingClientRect()
|
||||
return bottom > 0 && top < window.innerHeight
|
||||
})
|
||||
return result ? result.dataset.id : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,29 +12,68 @@
|
|||
<h4 class="modal-title">{{i18n.t('assets.from_clipboard.modal_title')}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><strong>{{i18n.t('assets.from_clipboard.image_preview')}}</strong></p>
|
||||
<canvas style="border:1px solid grey;max-width:400px;max-height:300px" id="clipboardPreview" />
|
||||
<p><strong>{{i18n.t('assets.from_clipboard.file_name')}}</strong></p>
|
||||
<div class="input-group">
|
||||
<input id="clipboardImageName" type="text" class="form-control"
|
||||
<label class="sci-label">{{i18n.t('assets.from_clipboard.image_preview')}}</label>
|
||||
<div class="flex justify-center w-full">
|
||||
<canvas class="max-h-80 max-w-lg rounded border border-solid border-sn-light-grey" ref="preview" />
|
||||
</div>
|
||||
<div class="w-full py-6">
|
||||
<label class="sci-label">{{i18n.t(`assets.from_clipboard.select_${objectType}`)}}</label>
|
||||
<SelectSearch
|
||||
:value="target"
|
||||
@change="setTarget"
|
||||
:options="targets"
|
||||
:isLoading="false"
|
||||
:placeholder="
|
||||
i18n.t(`protocols.steps.modals.move_element.${objectType}.search_placeholder`)
|
||||
"
|
||||
:searchPlaceholder="
|
||||
i18n.t(`protocols.steps.modals.move_element.${objectType}.search_placeholder`)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<label class="sci-label">{{i18n.t('assets.from_clipboard.file_name')}}</label>
|
||||
<div class="sci-input-container-v2">
|
||||
<input id="clipboardImageName" type="text" class="sci-input-field !pr-16" v-model="fileName"
|
||||
:placeholder="i18n.t('assets.from_clipboard.file_name_placeholder')" aria-describedby="image-name">
|
||||
<span class="input-group-addon" id="image-name"></span>
|
||||
<span class="absolute right-2.5 text-sn-grey ">
|
||||
.{{ extension }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="cancel">{{i18n.t('general.cancel')}}</button>
|
||||
<button type="button" class="btn btn-success" @click="uploadImage">{{i18n.t('assets.from_clipboard.add_image')}}</button>
|
||||
<button type="button" class="btn btn-success" :disabled="!valid" @click="uploadImage">{{i18n.t('assets.from_clipboard.add_image')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SelectSearch from "../../select_search.vue";
|
||||
|
||||
export default {
|
||||
name: 'clipboardPasteModal',
|
||||
props: {
|
||||
parent: Object,
|
||||
image: DataTransferItem
|
||||
objects: Array,
|
||||
image: DataTransferItem,
|
||||
objectType: String,
|
||||
selectedObjectId: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
target: null,
|
||||
targets: [],
|
||||
fileName: '',
|
||||
extension: '',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SelectSearch
|
||||
},
|
||||
computed: {
|
||||
valid() {
|
||||
return this.target && this.fileName.length > 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.modal).modal('show');
|
||||
|
@ -42,15 +81,26 @@
|
|||
$(this.$refs.modal).on('hidden.bs.modal', () => {
|
||||
this.$emit('cancel');
|
||||
});
|
||||
|
||||
if (this.selectedObjectId) this.target = this.selectedObjectId;
|
||||
this.targets = this.objects.map((object) => {
|
||||
return [
|
||||
object.id,
|
||||
object.attributes.name
|
||||
]
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setTarget(target) {
|
||||
this.target = target;
|
||||
},
|
||||
cancel() {
|
||||
$(this.$refs.modal).modal('hide');
|
||||
},
|
||||
appendImage(item) {
|
||||
let imageBlob = item.getAsFile();
|
||||
if (imageBlob) {
|
||||
var canvas = document.getElementById('clipboardPreview');
|
||||
var canvas = this.$refs.preview;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
|
@ -63,12 +113,12 @@
|
|||
let extension = imageBlob.name.slice(
|
||||
(Math.max(0, imageBlob.name.lastIndexOf('.')) || Infinity) + 1
|
||||
);
|
||||
$('#image-name').html('.' + extension); // add extension near file name
|
||||
this.extension = extension;
|
||||
this.imageBlob = imageBlob;
|
||||
}
|
||||
},
|
||||
uploadImage() {
|
||||
let newName = $('#clipboardImageName').val();
|
||||
let newName = this.fileName;
|
||||
let imageBlog = this.imageBlob;
|
||||
// check if the name is set
|
||||
if (newName && newName.length > 0) {
|
||||
|
@ -82,7 +132,7 @@
|
|||
this.imageBlob = new File([blob], name, { type: imageBlog.type });
|
||||
}
|
||||
$(this.$refs.modal).modal('hide');
|
||||
this.$emit('files', this.imageBlob);
|
||||
this.$emit('files', this.imageBlob, this.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
showClipboardPasteModal: false,
|
||||
pasteImages: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('paste', this.handlePaste);
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener('paste', this.handlePaste);
|
||||
},
|
||||
methods: {
|
||||
handlePaste(e) {
|
||||
if ( e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' ) return;
|
||||
this.pasteImages = this.getImagesFromClipboard(e);
|
||||
if (this.pasteImages && this.firstObjectInViewport()) this.showClipboardPasteModal = true;
|
||||
},
|
||||
getImagesFromClipboard(e) {
|
||||
let image = null;
|
||||
if (e.clipboardData) {
|
||||
for (let i = 0; i < e.clipboardData.items.length; i++) {
|
||||
if (e.clipboardData.items[i].type.indexOf('image') !== -1) {
|
||||
image = e.clipboardData.items[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -18,8 +18,8 @@
|
|||
data-placement="top"
|
||||
data-toggle="popover"
|
||||
data-content=""
|
||||
data-full-name="<%= user.full_name %>"
|
||||
data-email="<%= user.email %>"
|
||||
data-full-name="<%= escape_input(user.full_name) %>"
|
||||
data-email="<%= escape_input(user.email) %>"
|
||||
data-popover-html="<%= popover_html %>"
|
||||
data-user-avatar-popover-absolute-url="<%= user_avatar_absolute_url(
|
||||
user,
|
||||
|
@ -27,7 +27,7 @@
|
|||
base64_encoded_imgs
|
||||
) %>"
|
||||
>
|
||||
<%= user.full_name %>
|
||||
<%= escape_input(user.full_name) %>
|
||||
</a>
|
||||
<% unless skip_user_status || user_still_in_team %>
|
||||
<%= I18n.t('atwho.res.removed') %>
|
||||
|
|
|
@ -325,6 +325,7 @@ class Constants
|
|||
config[:attributes][:all] << 'id'
|
||||
config[:attributes][:all] << 'contenteditable'
|
||||
config[:attributes]['img'] << 'data-mce-token'
|
||||
config[:attributes]['img'] << 'data-source-type'
|
||||
config[:protocols]['img']['src'] << 'data'
|
||||
INPUT_SANITIZE_CONFIG = Sanitize::Config.freeze_config(config)
|
||||
|
||||
|
|
|
@ -3454,9 +3454,11 @@ en:
|
|||
drop_label: 'Drop to add to Step'
|
||||
file_label: 'File'
|
||||
from_clipboard:
|
||||
modal_title: 'Add image from clipboard'
|
||||
image_preview: 'Image preview'
|
||||
add_image: 'Add'
|
||||
modal_title: 'Insert image from clipboard'
|
||||
image_preview: 'Pasted image preview'
|
||||
select_step: 'Select to which step you’d like to attach image'
|
||||
select_result: 'Select to which result you’d like to attach image'
|
||||
add_image: 'Insert'
|
||||
file_name: 'File name'
|
||||
file_name_placeholder: 'Image'
|
||||
placeholder:
|
||||
|
|
|
@ -7,6 +7,9 @@ FactoryBot.define do
|
|||
description { Faker::Lorem.sentence }
|
||||
space_taken { 1048576 }
|
||||
without_templates { true }
|
||||
after(:create) do |team|
|
||||
team.created_by.update(current_team_id: team.id)
|
||||
end
|
||||
trait :with_members do
|
||||
users { create_list :user, 3 }
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ RSpec.describe "Api::V1::ExperimentsController", type: :request do
|
|||
|
||||
it 'Response with correct experiments, only active' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_experiments_path(team_id: @teams.first.id,
|
||||
get api_v1_team_project_experiments_path(team_id: @team1.id,
|
||||
project_id: @valid_project, filter: { archived: false }), headers: @valid_headers
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body[:data].pluck('attributes').pluck('archived').none?).to be(true)
|
||||
|
@ -60,7 +60,7 @@ RSpec.describe "Api::V1::ExperimentsController", type: :request do
|
|||
|
||||
it 'Response with correct experiments, only archived' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_experiments_path(team_id: @teams.first.id,
|
||||
get api_v1_team_project_experiments_path(team_id: @team1.id,
|
||||
project_id: @valid_project, filter: { archived: true }), headers: @valid_headers
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body[:data].pluck('attributes').pluck('archived').all?).to be(true)
|
||||
|
|
|
@ -6,14 +6,21 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
before :all do
|
||||
@user = create(:user)
|
||||
@another_user = create(:user)
|
||||
@first_project_owner_user = create(:user)
|
||||
@team = create(:team, created_by: @user)
|
||||
@normal_user_role = create :normal_user_role
|
||||
create_user_assignment(@team, @normal_user_role, @another_user)
|
||||
create_user_assignment(@team, @normal_user_role, @first_project_owner_user)
|
||||
@own_project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @team)
|
||||
@owner_role = UserRole.find_by(name: I18n.t('user_roles.predefined.owner'))
|
||||
create_user_assignment(@own_project, @owner_role, @first_project_owner_user)
|
||||
@invalid_project =
|
||||
create(:project, name: Faker::Name.unique.name, created_by: @another_user, team: @team, visibility: :hidden)
|
||||
|
||||
@valid_headers = { 'Authorization': 'Bearer ' + generate_token(@user.id) }
|
||||
@valid_headers = { Authorization: "Bearer #{generate_token(@user.id)}" }
|
||||
@valid_headers_first_project_owner_user = {
|
||||
Authorization: "Bearer #{generate_token(@first_project_owner_user.id)}"
|
||||
}
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
|
@ -29,15 +36,23 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
)
|
||||
end
|
||||
|
||||
it 'When invalid request, user in not an owner of the project' do
|
||||
it 'When invalid request, user is not an owner of the team and do not have access to project' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_users_path(team_id: @team.id, project_id: @invalid_project.id),
|
||||
headers: @valid_headers
|
||||
headers: @valid_headers_first_project_owner_user
|
||||
expect(response).to have_http_status(403)
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body['errors'][0]).to include('status': 403)
|
||||
end
|
||||
|
||||
it 'When invalid request, user is an owner of the team and do not have access to project' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_users_path(team_id: @team.id, project_id: @invalid_project.id),
|
||||
headers: @valid_headers
|
||||
expect(response).to have_http_status(200)
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
end
|
||||
|
||||
it 'When invalid request, non existing project' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_users_path(team_id: @team.id, project_id: -1), headers: @valid_headers
|
||||
|
@ -66,9 +81,9 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
get api_v1_team_project_user_path(
|
||||
team_id: @team.id, project_id: @invalid_project.id, id: -1
|
||||
), headers: @valid_headers
|
||||
expect(response).to have_http_status(403)
|
||||
expect(response).to have_http_status(404)
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body['errors'][0]).to include('status': 403)
|
||||
expect(hash_body['errors'][0]).to include('status': 404)
|
||||
end
|
||||
|
||||
it 'When invalid request, non existing project' do
|
||||
|
@ -161,7 +176,7 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
project_id: @invalid_project.id
|
||||
),
|
||||
params: request_body.to_json,
|
||||
headers: @valid_headers
|
||||
headers: @valid_headers_first_project_owner_user
|
||||
)
|
||||
|
||||
expect(response).to have_http_status(403)
|
||||
|
@ -180,7 +195,7 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
api_v1_team_project_user_path(
|
||||
team_id: @own_project.team.id,
|
||||
project_id: @own_project.id,
|
||||
id: @own_project.user_assignments.first.id
|
||||
id: @own_project.user_assignments.last.id
|
||||
),
|
||||
params: request_body.to_json,
|
||||
headers: @valid_headers
|
||||
|
@ -246,7 +261,7 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
}
|
||||
end
|
||||
|
||||
it 'renders 403' do
|
||||
it 'renders 404' do
|
||||
patch(
|
||||
api_v1_team_project_user_path(
|
||||
team_id: @invalid_project.team.id,
|
||||
|
@ -257,7 +272,7 @@ RSpec.describe "Api::V1::ProjectUserAssignmentsController", type: :request do
|
|||
headers: @valid_headers
|
||||
)
|
||||
|
||||
expect(response).to have_http_status(403)
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe 'Api::V1::ProjectsController', type: :request do
|
|||
project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @team1)
|
||||
end
|
||||
2.times do
|
||||
project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @teams.first, archived: true)
|
||||
project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @team1, archived: true)
|
||||
end
|
||||
|
||||
# unaccessable_projects
|
||||
|
@ -44,14 +44,14 @@ RSpec.describe 'Api::V1::ProjectsController', type: :request do
|
|||
|
||||
it 'Response with correct projects, only active' do
|
||||
hash_body = nil
|
||||
get api_v1_team_projects_path(team_id: @teams.first.id, filter: { archived: false }),
|
||||
get api_v1_team_projects_path(team_id: @team1.id, filter: { archived: false }),
|
||||
headers: @valid_headers
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body[:data].pluck('attributes').pluck('archived').none?).to be(true)
|
||||
expect(hash_body[:data]).to match(
|
||||
JSON.parse(
|
||||
ActiveModelSerializers::SerializableResource
|
||||
.new(@teams.first.projects.active, each_serializer: Api::V1::ProjectSerializer)
|
||||
.new(@team1.projects.active, each_serializer: Api::V1::ProjectSerializer)
|
||||
.to_json
|
||||
)['data']
|
||||
)
|
||||
|
@ -59,14 +59,14 @@ RSpec.describe 'Api::V1::ProjectsController', type: :request do
|
|||
|
||||
it 'Response with correct projects, only archived' do
|
||||
hash_body = nil
|
||||
get api_v1_team_projects_path(team_id: @teams.first.id, filter: { archived: true }),
|
||||
get api_v1_team_projects_path(team_id: @team1.id, filter: { archived: true }),
|
||||
headers: @valid_headers
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body[:data].pluck('attributes').pluck('archived').all?).to be(true)
|
||||
expect(hash_body[:data]).to match(
|
||||
JSON.parse(
|
||||
ActiveModelSerializers::SerializableResource
|
||||
.new(@teams.first.projects.archived, each_serializer: Api::V1::ProjectSerializer)
|
||||
.new(@team1.projects.archived, each_serializer: Api::V1::ProjectSerializer)
|
||||
.to_json
|
||||
)['data']
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe 'Api::V1::ProtocolTemplateController', type: :request do
|
|||
@protocol_published = create(:protocol, :in_repository_published_version, team: @team, added_by: @user, parent: @protocol_published_original, version_number: 2)
|
||||
@protocol_published_draft = create(:protocol, :in_repository_draft, team: @team, added_by: @user,
|
||||
parent: @protocol_published_original, version_number: 3, name: @protocol_published_original.name)
|
||||
|
||||
|
||||
@protocol_draft_second_team = create(:protocol, :in_repository_draft, team: @team2, added_by: @another_user)
|
||||
|
||||
@valid_headers = { 'Authorization': 'Bearer ' + generate_token(@user.id) }
|
||||
|
|
|
@ -129,14 +129,14 @@ RSpec.describe 'Api::V1::ResultsController', type: :request do
|
|||
attributes: {
|
||||
text: 'Result text 1 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAA'\
|
||||
'AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE'\
|
||||
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg==" data-mce-token="a1">'
|
||||
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg==" data-mce-token="1">'
|
||||
} },
|
||||
{ type: 'tiny_mce_assets',
|
||||
attributes: {
|
||||
file_data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAA'\
|
||||
'AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE'\
|
||||
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg==',
|
||||
file_token: 'a1',
|
||||
file_token: '1',
|
||||
file_name: 'test.png'
|
||||
} }
|
||||
]
|
||||
|
@ -198,7 +198,7 @@ RSpec.describe 'Api::V1::ResultsController', type: :request do
|
|||
|
||||
it 'Response correct with old TinyMCE images' do
|
||||
hash_body = nil
|
||||
@valid_tinymce_hash_body[:included][0][:attributes][:text] = 'Result text 1 [~tiny_mce_id:a1]'
|
||||
@valid_tinymce_hash_body[:included][0][:attributes][:text] = 'Result text 1 [~tiny_mce_id:1]'
|
||||
post api_v1_team_project_experiment_task_results_path(
|
||||
team_id: @team1.id,
|
||||
project_id: @valid_project,
|
||||
|
|
|
@ -63,7 +63,7 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['data'].map { |item| item['id'] }).to(
|
||||
eq([@my_module_repository_row.id.to_s])
|
||||
eq([@repository_row.id.to_s])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -76,14 +76,14 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id,
|
||||
id: @my_module_repository_row.id
|
||||
id: @repository_row.id
|
||||
),
|
||||
headers: @valid_headers
|
||||
)
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['data']['id']).to(
|
||||
eq(@my_module_repository_row.id.to_s)
|
||||
eq(@repository_row.id.to_s)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -112,7 +112,7 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id,
|
||||
id: @my_module_repository_row.id
|
||||
id: @repository_row.id
|
||||
),
|
||||
params: request_body.to_json,
|
||||
headers: @valid_headers)
|
||||
|
@ -130,7 +130,7 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
hash_including(
|
||||
data: hash_including(
|
||||
type: 'inventory_items',
|
||||
attributes: hash_including(stock_consumption: "100.0") )
|
||||
attributes: hash_including(stock_consumption: '100.0'))
|
||||
)
|
||||
)
|
||||
end
|
||||
|
@ -143,7 +143,7 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id,
|
||||
id: @my_module_repository_row.id
|
||||
id: @repository_row.id
|
||||
),
|
||||
params: request_body.to_json,
|
||||
headers: @valid_headers)
|
||||
|
|
|
@ -52,7 +52,7 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
it 'Response with correct tasks, only active' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_experiment_tasks_path(
|
||||
team_id: @teams.first.id,
|
||||
team_id: @team1.id,
|
||||
project_id: @valid_project,
|
||||
experiment_id: @valid_experiment,
|
||||
filter: { archived: false }
|
||||
|
@ -71,7 +71,7 @@ RSpec.describe 'Api::V1::TasksController', type: :request do
|
|||
it 'Response with correct tasks, only archived' do
|
||||
hash_body = nil
|
||||
get api_v1_team_project_experiment_tasks_path(
|
||||
team_id: @teams.first.id,
|
||||
team_id: @team1.id,
|
||||
project_id: @valid_project,
|
||||
experiment_id: @valid_experiment,
|
||||
filter: { archived: true }
|
||||
|
|
Loading…
Reference in a new issue